diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1bfc167d9..87b66dc14 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,16 +1,24 @@ version: 2 # Version updates for GitHub Actions only; CVEs are handled by Dependabot -# security updates (enabled repo-wide). +# security updates (enabled repo-wide), which are unaffected by the schedule +# and ignore rules below. updates: - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" + interval: "monthly" open-pull-requests-limit: 5 groups: github-actions: patterns: - "*" + # Only open PRs for major version bumps; skip the churn of minor/patch + # version updates (security fixes still come through regardless). + ignore: + - dependency-name: "*" + update-types: + - "version-update:semver-minor" + - "version-update:semver-patch" labels: - "dependencies" - "github-actions" diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index f3916a33d..02b991ae2 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -2,15 +2,30 @@ name: Primus-CI-TAS on: workflow_dispatch: + inputs: + full_tests: + description: "Run the full test matrix (every model E2E, including @pytest.mark.weekly siblings, slow unit tests and the MaxText models)" + type: boolean + default: false push: branches: - main tags: - "v*" pull_request: + # Re-list the implicit defaults (opened/synchronize/reopened) and add + # ready_for_review so flipping a Draft PR to Ready triggers its first run. + types: [opened, synchronize, reopened, ready_for_review] + schedule: + # Saturday 18:00 UTC (Sunday 02:00 UTC+8) — the weekend full run. Kept far + # from Primus-Benchmark's daily 16:00 UTC cron; it also uses other runners. + - cron: "0 18 * * 6" concurrency: - group: ${{ github.workflow }}-${{ github.head_ref || github.event.merge_group.head_ref || github.ref }} + # Scheduled runs get their own group: they share `github.ref` (refs/heads/main) + # with pushes to main, and cancel-in-progress would otherwise let any merge + # kill a multi-hour weekend run. + group: ${{ github.workflow }}-${{ github.event_name == 'schedule' && 'weekly' || github.head_ref || github.event.merge_group.head_ref || github.ref }} cancel-in-progress: true # Default to read-only. No job uses GITHUB_TOKEN for writes (Docker Hub push @@ -20,12 +35,17 @@ permissions: contents: read env: - PRIMUS_TURBO_COMMIT: a04a233cbfb468dbe21600cbf9db70953428b25c # feat: force use nt layout gemm in bwd (#386) - PRIMUS_TURBO_AITER_COMMIT: 0f3c58e6edb6754940bcf9fd5f09ccb6f389f52e # v0.14.0.post1 - ROCSHMEM_COMMIT: 17ff985c026f9f97f85068647e863ab541dd5645 # Update version to 3.2.0 for 7.2.0 rocm release (#351) (#355) + # Test tier, the single switch every test step derives its pytest args from. + # "0" (PR/push) keeps one model E2E per architecture and feature path; "1" + # (weekend cron, or a manual run with full_tests=true) adds the + # @pytest.mark.weekly siblings and the slow unit tests. + PRIMUS_CI_FULL: ${{ (github.event_name == 'schedule' || github.event.inputs.full_tests == 'true') && '1' || '0' }} + PRIMUS_TURBO_COMMIT: 2f622de6a29ab711925e8fe6bcc763be76ac2699 # fix: loss NaN issue (Primus-Turbo bump); fix: fixed flydsl version (#446) + PRIMUS_TURBO_AITER_COMMIT: 0f3c58e6edb6754940bcf9fd5f09ccb6f389f52e # AITER v0.1.14.post1 (tag commit) — required by Primus-Turbo main aiter_utils.py + #ROCSHMEM_COMMIT: 17ff985c026f9f97f85068647e863ab541dd5645 # Update version to 3.2.0 for 7.2.0 rocm release (#351) (#355) UCCL_COMMIT: 5afb4117893c58cc0c8557d9286336141a301053 # [EP]: fix fp8 error of internode_ll on amd gfx950 arch. (#710) - TRITON_COMMIT: 88b227e23f0445f3f695bad05bbf1a363b4f50e0 - BASE_IMAGE: docker.io/rocm/primus:v26.3 + TRITON_COMMIT: 09500db9f0fe66fd176d1f080e2017b37e7e995d # pinned triton-lang/triton commit built from source in Dockerfile + BASE_IMAGE: docker.io/rocm/primus:v26.4 MAXTEXT_BASE_IMAGE: docker.io/rocm/jax-training:maxtext-v26.2 jobs: @@ -52,6 +72,24 @@ jobs: run: pre-commit run --all-files --show-diff-on-failure - name: Check version/commit pin consistency run: python tools/ci/check_version_consistency.py + # The three submodules the drift check reads a schema from; `submodules: + # recursive` on the checkout would also clone Megatron-Bridge & friends + # for nothing. A fetch failure is deliberately left to the check step, + # which names the backend it could not check instead of dropping a bare + # git error; drift stays warn-only, but a backend it could not check + # fails the job, since a check that did not run must not read as a pass. + - name: Fetch backend schema sources + continue-on-error: true + run: >- + git submodule update --init --depth 1 + third_party/torchtitan third_party/maxtext third_party/Megatron-LM + - name: Check backend config schema drift + # `pipefail` is not on by default, and without it `tee` would report + # success for a check that failed -- the same silent pass this step + # exists to stop. + run: | + set -o pipefail + python tools/ci/check_config_schema.py --warn-only | tee -a "$GITHUB_STEP_SUMMARY" # Flag PRs that introduce known-vulnerable dependencies. Warn-only during # burn-in; drop warn-only to turn it into a hard gate once the team is ready. @@ -69,7 +107,11 @@ jobs: build-docker: needs: [code-lint] + # Skip on Draft PRs (keep code-lint ungated); the event_name guard preserves + # push/tag and workflow_dispatch runs where github.event.pull_request is absent. + if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }} runs-on: build-docker + # runs-on: build-docker-crusoe strategy: matrix: python-version: ["3.12"] @@ -92,6 +134,11 @@ jobs: echo "IMAGE_TAG=pr-${{ github.event.pull_request.number }}" >> $GITHUB_ENV elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then echo "IMAGE_TAG=latest" >> $GITHUB_ENV + elif [[ "${{ github.event_name }}" == "schedule" ]]; then + # Own tag so the weekend run cannot race a concurrent main push over + # ":latest" (both would build the same commit, but not necessarily + # at the same time). + echo "IMAGE_TAG=weekly" >> $GITHUB_ENV elif [[ "${{ github.event_name }}" == "release" ]]; then TAG_NAME="${{ github.ref }}" TAG="${TAG_NAME#refs/tags/}" @@ -104,20 +151,33 @@ jobs: # echo "> Login to ROCm Docker Hub" # docker login -u rocmshared -p ${{ secrets.ROCM_DOCKER_HUB_TOKEN }} + # This build-docker runner lives under /apps, which the Docker daemon + # cannot see (only its bind-mount /mnt/apps_proxy is visible to docker). + # Use the /mnt/apps_proxy view of GITHUB_WORKSPACE for every docker + # build context / -f path so BuildKit can read them. + CWS="${GITHUB_WORKSPACE}" + if [[ "${GITHUB_WORKSPACE}" == /apps/* ]]; then + CWS="/mnt/apps_proxy${GITHUB_WORKSPACE#/apps}" + fi + echo "> Build Docker Image with tag: ${{ env.IMAGE_TAG }}" echo "> Build dependencies" start_time=$(date +%s) - docker build -f $GITHUB_WORKSPACE/.github/workflows/docker/Dockerfile \ + # Optional build-args, off by default (kept as an array so a commented-out + # element here can't break the docker build \-continuation below). + EXTRA_BUILD_ARGS=() + # EXTRA_BUILD_ARGS+=(--build-arg ROCSHMEM_COMMIT=${ROCSHMEM_COMMIT}) + docker build -f $CWS/.github/workflows/docker/Dockerfile \ --network=host \ -t tasimage/primus:${{env.IMAGE_TAG}} \ --build-arg BASE_IMAGE=${BASE_IMAGE} \ --build-arg PRIMUS_TURBO_COMMIT=${PRIMUS_TURBO_COMMIT} \ --build-arg PRIMUS_TURBO_AITER_COMMIT=${PRIMUS_TURBO_AITER_COMMIT} \ - --build-arg ROCSHMEM_COMMIT=${ROCSHMEM_COMMIT} \ --build-arg PRIMUS_TURBO_FRAMEWORK=PYTORCH \ --build-arg UCCL_COMMIT=${UCCL_COMMIT} \ --build-arg TRITON_COMMIT=${TRITON_COMMIT} \ - $GITHUB_WORKSPACE/.github/workflows/docker + "${EXTRA_BUILD_ARGS[@]}" \ + $CWS/.github/workflows/docker end_time=$(date +%s) elapsed=$((end_time - start_time)) echo "⏱️ [build primus docker] Total elapsed time: ${elapsed} seconds" @@ -127,18 +187,19 @@ jobs: docker push docker.io/tasimage/primus:${{env.IMAGE_TAG}} # docker login -u rocmshared -p ${{ secrets.ROCM_DOCKER_HUB_TOKEN }} - # Primus v26.3 already includes AINIC under /workspace. Re-enable this + # Primus v26.4 already includes AINIC under /workspace. Re-enable this # Dockerfile.ainic build only when we need to refresh the tasimage -ainic image. echo "> Build Docker Image with tag: ${{ env.IMAGE_TAG }}-ainic" start_time=$(date +%s) - mkdir -p $GITHUB_WORKSPACE/.github/workflows/docker/ainic - cp /apps/tas/0_public/primus_docker_ci/ainic/ainic_bundle_1.117.5-a-77.tar.gz $GITHUB_WORKSPACE/.github/workflows/docker/ainic/ || { echo "Error: Failed to copy ainic bundle"; exit 1; } - docker build -f $GITHUB_WORKSPACE/.github/workflows/docker/Dockerfile.ainic \ + mkdir -p $CWS/.github/workflows/docker/ainic + cp /apps/tas/0_public/primus_docker_ci/ainic/ainic_bundle_1.117.5-a-77.tar.gz $CWS/.github/workflows/docker/ainic/ || { echo "Error: Failed to copy ainic bundle"; exit 1; } + # cp /shared_nfs/tas/primus_github_runner/build-docker/ainic/ainic_bundle_1.117.5-a-77.tar.gz $GITHUB_WORKSPACE/.github/workflows/docker/ainic/ || { echo "Error: Failed to copy ainic bundle"; exit 1; } + docker build -f $CWS/.github/workflows/docker/Dockerfile.ainic \ --network=host \ -t tasimage/primus:${{env.IMAGE_TAG}}-ainic \ --build-arg BASE_IMAGE=docker.io/tasimage/primus:${{env.IMAGE_TAG}} \ --build-arg AINIC_BUNDLE_PATH=ainic \ - $GITHUB_WORKSPACE/.github/workflows/docker + $CWS/.github/workflows/docker end_time=$(date +%s) elapsed=$((end_time - start_time)) echo "⏱️ [build primus docker-ainic] Total elapsed time: ${elapsed} seconds" @@ -148,140 +209,100 @@ jobs: docker push docker.io/tasimage/primus:${{env.IMAGE_TAG}}-ainic # docker login -u rocmshared -p ${{ secrets.ROCM_DOCKER_HUB_TOKEN }} - echo "> Build Docker Image with tag: ${{ env.IMAGE_TAG }}-v25.09-ainic" - start_time=$(date +%s) - mkdir -p $GITHUB_WORKSPACE/.github/workflows/docker/ainic - cp /apps/tas/0_public/primus_docker_ci/ainic/ainic_bundle_1.117.5-a-56.tar.gz $GITHUB_WORKSPACE/.github/workflows/docker/ainic/ || { echo "Error: Failed to copy ainic bundle"; exit 1; } - docker build -f $GITHUB_WORKSPACE/.github/workflows/docker/Dockerfile_v25.09_ainic \ - --network=host \ - -t tasimage/primus:${{env.IMAGE_TAG}}-v25.09-ainic \ - --build-arg AINIC_BUNDLE_PATH=ainic \ - --build-arg PRIMUS_TURBO_COMMIT=${PRIMUS_TURBO_COMMIT} \ - $GITHUB_WORKSPACE/.github/workflows/docker - end_time=$(date +%s) - elapsed=$((end_time - start_time)) - echo "⏱️ [build primus docker-v25.09-ainic] Total elapsed time: ${elapsed} seconds" - - docker tag tasimage/primus:${{env.IMAGE_TAG}}-v25.09-ainic docker.io/tasimage/primus:${{env.IMAGE_TAG}}-v25.09-ainic - docker login -u tasimage -p ${{ secrets.PRIMUS_DOCKER_HUB_TOKEN }} - docker push docker.io/tasimage/primus:${{env.IMAGE_TAG}}-v25.09-ainic - # docker login -u rocmshared -p ${{ secrets.ROCM_DOCKER_HUB_TOKEN }} + # echo "> Build Docker Image with tag: ${{ env.IMAGE_TAG }}-jax" + # start_time=$(date +%s) + # docker build -f $GITHUB_WORKSPACE/.github/workflows/docker/Dockerfile \ + # --network=host \ + # -t tasimage/primus:${{env.IMAGE_TAG}}-jax \ + # --build-arg BASE_IMAGE=${MAXTEXT_BASE_IMAGE} \ + # --build-arg PRIMUS_TURBO_COMMIT=${PRIMUS_TURBO_COMMIT} \ + # --build-arg PRIMUS_TURBO_AITER_COMMIT=${PRIMUS_TURBO_AITER_COMMIT} \ + # --build-arg PRIMUS_TURBO_FRAMEWORK=JAX \ + # --build-arg UCCL_COMMIT=${UCCL_COMMIT} \ + # --build-arg ROCSHMEM_COMMIT=${ROCSHMEM_COMMIT} \ + # --build-arg TRITON_COMMIT=${TRITON_COMMIT} . + # end_time=$(date +%s) + # elapsed=$((end_time - start_time)) + # echo "⏱️ [build primus docker-jax] Total elapsed time: ${elapsed} seconds" - echo "> Build Docker Image with tag: ${{ env.IMAGE_TAG }}-jax" - start_time=$(date +%s) - docker build -f $GITHUB_WORKSPACE/.github/workflows/docker/Dockerfile \ - --network=host \ - -t tasimage/primus:${{env.IMAGE_TAG}}-jax \ - --build-arg BASE_IMAGE=${MAXTEXT_BASE_IMAGE} \ - --build-arg PRIMUS_TURBO_COMMIT=${PRIMUS_TURBO_COMMIT} \ - --build-arg PRIMUS_TURBO_AITER_COMMIT=${PRIMUS_TURBO_AITER_COMMIT} \ - --build-arg PRIMUS_TURBO_FRAMEWORK=JAX \ - --build-arg UCCL_COMMIT=${UCCL_COMMIT} \ - --build-arg ROCSHMEM_COMMIT=${ROCSHMEM_COMMIT} \ - --build-arg TRITON_COMMIT=${TRITON_COMMIT} . - end_time=$(date +%s) - elapsed=$((end_time - start_time)) - echo "⏱️ [build primus docker-jax] Total elapsed time: ${elapsed} seconds" + # echo "> Docker tag image for Docker Hub" + # docker tag tasimage/primus:${{env.IMAGE_TAG}}-jax docker.io/tasimage/primus:${{env.IMAGE_TAG}}-jax + # docker login -u tasimage -p ${{ secrets.PRIMUS_DOCKER_HUB_TOKEN }} + # docker push docker.io/tasimage/primus:${{env.IMAGE_TAG}}-jax + # # docker login -u rocmshared -p ${{ secrets.ROCM_DOCKER_HUB_TOKEN }} - echo "> Docker tag image for Docker Hub" - docker tag tasimage/primus:${{env.IMAGE_TAG}}-jax docker.io/tasimage/primus:${{env.IMAGE_TAG}}-jax - docker login -u tasimage -p ${{ secrets.PRIMUS_DOCKER_HUB_TOKEN }} - docker push docker.io/tasimage/primus:${{env.IMAGE_TAG}}-jax - # docker login -u rocmshared -p ${{ secrets.ROCM_DOCKER_HUB_TOKEN }} + # echo "> Build Docker Image with tag: ${{ env.IMAGE_TAG }}-jax-ainic" + # start_time=$(date +%s) + # mkdir -p $GITHUB_WORKSPACE/.github/workflows/docker/ainic + # cp /apps/tas/0_public/primus_docker_ci/ainic/ainic_bundle_1.117.5-a-56.tar.gz $GITHUB_WORKSPACE/.github/workflows/docker/ainic/ || { echo "Error: Failed to copy ainic bundle"; exit 1; } + # # cp /shared_nfs/tas/primus_github_runner/build-docker/ainic/ainic_bundle_1.117.5-a-56.tar.gz $GITHUB_WORKSPACE/.github/workflows/docker/ainic/ || { echo "Error: Failed to copy ainic bundle"; exit 1; } + # docker build -f $GITHUB_WORKSPACE/.github/workflows/docker/Dockerfile_jax.ainic \ + # --network=host \ + # -t tasimage/primus:${{env.IMAGE_TAG}}-jax-ainic \ + # --build-arg BASE_IMAGE=${MAXTEXT_BASE_IMAGE} \ + # --build-arg AINIC_BUNDLE_PATH=ainic \ + # $GITHUB_WORKSPACE/.github/workflows/docker + # end_time=$(date +%s) + # elapsed=$((end_time - start_time)) + # echo "⏱️ [build primus docker-jax-ainic] Total elapsed time: ${elapsed} seconds" - echo "> Build Docker Image with tag: ${{ env.IMAGE_TAG }}-jax-ainic" - start_time=$(date +%s) - mkdir -p $GITHUB_WORKSPACE/.github/workflows/docker/ainic - cp /apps/tas/0_public/primus_docker_ci/ainic/ainic_bundle_1.117.5-a-56.tar.gz $GITHUB_WORKSPACE/.github/workflows/docker/ainic/ || { echo "Error: Failed to copy ainic bundle"; exit 1; } - docker build -f $GITHUB_WORKSPACE/.github/workflows/docker/Dockerfile_jax.ainic \ - --network=host \ - -t tasimage/primus:${{env.IMAGE_TAG}}-jax-ainic \ - --build-arg BASE_IMAGE=${MAXTEXT_BASE_IMAGE} \ - --build-arg AINIC_BUNDLE_PATH=ainic \ - $GITHUB_WORKSPACE/.github/workflows/docker - end_time=$(date +%s) - elapsed=$((end_time - start_time)) - echo "⏱️ [build primus docker-jax-ainic] Total elapsed time: ${elapsed} seconds" - - docker tag tasimage/primus:${{env.IMAGE_TAG}}-jax-ainic docker.io/tasimage/primus:${{env.IMAGE_TAG}}-jax-ainic - docker login -u tasimage -p ${{ secrets.PRIMUS_DOCKER_HUB_TOKEN }} - docker push docker.io/tasimage/primus:${{env.IMAGE_TAG}}-jax-ainic - # docker login -u rocmshared -p ${{ secrets.ROCM_DOCKER_HUB_TOKEN }} + # docker tag tasimage/primus:${{env.IMAGE_TAG}}-jax-ainic docker.io/tasimage/primus:${{env.IMAGE_TAG}}-jax-ainic + # docker login -u tasimage -p ${{ secrets.PRIMUS_DOCKER_HUB_TOKEN }} + # docker push docker.io/tasimage/primus:${{env.IMAGE_TAG}}-jax-ainic + # # docker login -u rocmshared -p ${{ secrets.ROCM_DOCKER_HUB_TOKEN }} # echo "> Docker cleanup local images" # docker rmi tasimage/primus:${{env.IMAGE_TAG}} - # docker rmi tasimage/primus:${{env.IMAGE_TAG}}-v25.09-ainic - # docker rmi tasimage/primus:${{env.IMAGE_TAG}}-v25.10-ainic # docker rmi tasimage/primus:${{env.IMAGE_TAG}}-jax echo "> build-docker success" run-unittest-torch: + permissions: + contents: read + actions: read # let "Write runtime summary" list this job's own steps for auto-discovered timing env: + # PRIMUS_WORKDIR: /shared_nfs/tas/primus_github_runner/primus-lm-cicd/actions-runner PRIMUS_WORKDIR: /mnt/apps_proxy/tas/0_public/primus_ci/actions-runner-torch # PRIMUS_WORKDIR: /wekafs/primus-data/primus_safe_ci/torch PRIMUS_TURBO_ATTN_V3_ATOMIC_FP32: 1 - needs: [code-lint] + # All test steps run inside a sibling container launched from the image that + # build-docker built+pushed (docker.io/tasimage/primus:${IMAGE_TAG}). That + # image already bundles turbo / aiter / triton / rocSHMEM / uccl / origami, + # so their runtime installs are removed here. + UT_CONTAINER: primus_ut_torch + needs: [build-docker] + if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }} # runs-on: [primus-lm-cicd-torch-j8knc] runs-on: [primus-lm-cicd-v26.3-tas8n-a16-40] + # runs-on: [primus-lm-cicd-v26.3-crusoe-m2m-321] steps: - - run: echo "🎉 Begin Primus-Turbo Checkout." - - name: Clean stale Primus-Turbo checkout - run: rm -rf "${GITHUB_WORKSPACE}/Primus-Turbo" - - name: Set commit hash to env - run: echo "PRIMUS_TURBO_COMMIT=${PRIMUS_TURBO_COMMIT}" >> $GITHUB_ENV - - name: Checkout Repo Primus-Turbo - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 - with: - repository: AMD-AGI/Primus-Turbo - submodules: "recursive" - path: Primus-Turbo - ref: ${{ env.PRIMUS_TURBO_COMMIT }} - - run: echo "Begin AITER + Primus-Turbo Install." - - name: Install AITER - run: | - : > "$RUNNER_TEMP/runtime.tsv" # reset the CI runtime log for this job - echo "✅ [Uninstall old aiter] started at: $(date)" - pip3 uninstall aiter amd-aiter -y || true - rm -rf /tmp/aiter || true - cd /tmp - git clone https://github.com/ROCm/aiter.git - cd aiter - git checkout -f ${PRIMUS_TURBO_AITER_COMMIT} - git submodule sync - git submodule update --init --recursive - start_time=$(date +%s) - echo "✅ [Build aiter] started at: $(date)" - PREBUILD_KERNELS=3 pip install --no-cache-dir --use-pep517 . - end_time=$(date +%s) - elapsed=$((end_time - start_time)) - echo "✅ [Build aiter] ended at: $(date)" - echo "⏱️ [Build aiter] Total elapsed time: ${elapsed} seconds" - echo -e "Build aiter\t${elapsed}" >> "$RUNNER_TEMP/runtime.tsv" - - name: Install Primus-Turbo - run: | - rm -rf /tmp/Primus-Turbo || true - mv Primus-Turbo /tmp/ - echo "Primus-Turbo dir: /tmp/Primus-Turbo" - git config --global --add safe.directory /tmp/Primus-Turbo || true - cd /tmp/Primus-Turbo || true - start_time=$(date +%s) - echo "✅ [Pip install requirements] started at: $(date)" - mkdir -p ${PRIMUS_WORKDIR}/primus-cache - MAX_JOBS=128 pip install --cache-dir=${PRIMUS_WORKDIR}/primus-cache --no-build-isolation --no-clean -r requirements.txt - end_time=$(date +%s) - elapsed=$((end_time - start_time)) - echo "✅ [Pip install requirements] ended at: $(date)" - echo "⏱️ [Pip install requirements] Total elapsed time: ${elapsed} seconds" - echo -e "primus-turbo: pip install requirements\t${elapsed}" >> "$RUNNER_TEMP/runtime.tsv" - start_time=$(date +%s) - echo "✅ [build primus-turbo] started at: $(date)" - pip3 install --no-build-isolation -e . -v - end_time=$(date +%s) - elapsed=$((end_time - start_time)) - echo "✅ [build primus-turbo] ended at: $(date)" - echo "⏱️ [build primus-turbo] Total elapsed time: ${elapsed} seconds" - echo -e "primus-turbo: build/install\t${elapsed}" >> "$RUNNER_TEMP/runtime.tsv" - - run: echo "🎉 Begin Primus Unit Test." + - run: echo "🎉 Begin Primus Unit Test (containerized)." + - name: Pre-checkout cleanup of root-owned leftovers + # Safety net for when a PREVIOUS run never reached its Clean step (runner + # crash/reboot/OOM, or cancellation past the grace period): it can leave + # root-owned files (E2E data/, output/, pp_simulation_result/, + # .pytest_cache, ...) in the workspace, and actions/checkout then fails + # with EACCES while deleting them ("Deleting the contents of ..."). Fix + # ownership here BEFORE checkout so the current run self-heals regardless + # of how the previous one ended. Runs as root via docker because the + # runner user cannot chown root-owned files. Best-effort (never fails). + run: | + WS="${GITHUB_WORKSPACE}" + CWS="$WS"; [[ "$WS" == /apps/* ]] && CWS="/mnt/apps_proxy${WS#/apps}" + if [ ! -d "$CWS" ]; then echo "Workspace $CWS not present yet; nothing to clean."; exit 0; fi + owner="$(stat -c '%u:%g' "$CWS")" + echo "Workspace=$CWS owner=$owner root-owned-before=$(find "$CWS" -user 0 2>/dev/null | wc -l)" + FIX="find '$CWS' -user 0 -exec chown $owner {} + 2>/dev/null; true" + if [ "$(docker inspect -f '{{.State.Running}}' "${UT_CONTAINER}" 2>/dev/null)" = "true" ]; then + echo "Fixing ownership via running container ${UT_CONTAINER}." + docker exec "${UT_CONTAINER}" bash -lc "$FIX" || true + else + IMG="$(docker images --format '{{.Repository}}:{{.Tag}}' | grep -m1 -E 'tasimage/primus|rocm/primus' || true)" + [ -n "$IMG" ] || IMG="${BASE_IMAGE}" + echo "Fixing ownership via throwaway container from image: $IMG" + docker run --rm -v /mnt/apps_proxy:/mnt/apps_proxy "$IMG" bash -lc "$FIX" || true + fi + echo "root-owned-after=$(find "$CWS" -user 0 2>/dev/null | wc -l)" - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: submodules: recursive @@ -293,24 +314,73 @@ jobs: echo "GITHUB_WORKSPACE: $GITHUB_WORKSPACE" echo "Runner Temp Dir: $RUNNER_TEMP" echo "Runner Tool Cache: $RUNNER_TOOL_CACHE" + - name: Resolve container workspace path + run: | + # This runner lives under /apps, so GITHUB_WORKSPACE is an /apps path. + # On this host /apps cannot be bind-mounted into a container (the mount + # shows up empty); only its identical bind-mount /mnt/apps_proxy can. + # Export CWS = the /mnt/apps_proxy view of GITHUB_WORKSPACE and use it + # as the container-side workspace (docker -w and -e GITHUB_WORKSPACE). + # Host-side steps keep using GITHUB_WORKSPACE (/apps) since both paths + # point at the same files. + if [[ "${GITHUB_WORKSPACE}" == /apps/* ]]; then + echo "CWS=/mnt/apps_proxy${GITHUB_WORKSPACE#/apps}" >> "$GITHUB_ENV" + else + echo "CWS=${GITHUB_WORKSPACE}" >> "$GITHUB_ENV" + fi + - name: Resolve image tag + run: | + # build-docker sets IMAGE_TAG in its own job env only; re-derive it here + # with the identical logic so we can target the image it pushed. + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + echo "IMAGE_TAG=pr-${{ github.event.pull_request.number }}" >> $GITHUB_ENV + elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then + echo "IMAGE_TAG=latest" >> $GITHUB_ENV + elif [[ "${{ github.event_name }}" == "schedule" ]]; then + echo "IMAGE_TAG=weekly" >> $GITHUB_ENV + elif [[ "${{ github.event_name }}" == "release" ]]; then + TAG_NAME="${{ github.ref }}" + TAG="${TAG_NAME#refs/tags/}" + echo "IMAGE_TAG=$TAG" >> $GITHUB_ENV + else + echo "IMAGE_TAG=others" >> $GITHUB_ENV + fi + - name: Start (or reuse) unit-test container + run: | + : > "$RUNNER_TEMP/runtime.tsv" # reset the CI runtime log for this job + IMG="docker.io/tasimage/primus:${{ env.IMAGE_TAG }}" + echo "Target image: ${IMG}" + # Pull first, then compare image id (not tag name): IMAGE_TAG is + # constant ("latest") on main, so a tag-only check can't detect a + # same-tag repush and would keep reusing a stale container. + docker pull "${IMG}" + target_id="$(docker image inspect -f '{{.Id}}' "${IMG}" 2>/dev/null || true)" + cur_id="$(docker inspect -f '{{.Image}}' "${UT_CONTAINER}" 2>/dev/null || true)" + running="$(docker inspect -f '{{.State.Running}}' "${UT_CONTAINER}" 2>/dev/null || true)" + if [ "${running}" = "true" ] && [ -n "${target_id}" ] && [ "${cur_id}" = "${target_id}" ]; then + echo "Reusing container ${UT_CONTAINER} (already on ${IMG}, id=${target_id})." + else + echo "Recreating container ${UT_CONTAINER} (running=${running}, cur_id=${cur_id}, target_id=${target_id})." + docker rm -f "${UT_CONTAINER}" >/dev/null 2>&1 || true + docker run -d --name "${UT_CONTAINER}" \ + --network host --ipc host --privileged \ + --group-add video \ + --cap-add SYS_PTRACE --security-opt seccomp=unconfined \ + --shm-size 128G \ + -e PYTHONDONTWRITEBYTECODE=1 \ + --device /dev/kfd --device /dev/dri --device /dev/infiniband \ + -v /mnt/apps_proxy:/mnt/apps_proxy \ + -w "${CWS}" \ + "${IMG}" sleep infinity + fi + echo "Container ready:" + docker exec "${UT_CONTAINER}" bash -lc 'python3 --version; python3 -c "import triton; print('\''triton'\'', triton.__version__)" 2>/dev/null || true' - name: Install Primus run: | + docker exec -i -e GITHUB_WORKSPACE="${CWS}" -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' + set -e pip install -r requirements.txt - - name: Install fixed origami - run: | - # rocm/primus:v26.3 bundles origami 0.1.0, whose rank_configs() raises - # ValueError: vector::reserve during MoE grouped-gemm kernel selection and - # crashes training (turbo's _safe_rank_configs only catches RuntimeError). - # Install the fixed origami (rocm-libraries@223648a) over the bundled 0.1.0. - # TODO: drop this once a base image ships origami with the fix. - rm -rf /tmp/rocm-libraries - git clone --filter=blob:none --no-checkout https://github.com/ROCm/rocm-libraries.git /tmp/rocm-libraries - cd /tmp/rocm-libraries - git sparse-checkout init --cone - git sparse-checkout set shared/origami - git checkout 223648a26928ebed7f3dd0ccdc044c09f1dccf9b - pip uninstall -y origami || true - pip install ./shared/origami/python + IN - name: Set UT_LOG_PATH run: | ts="$(date +%Y%m%d-%H%M%S)" @@ -319,6 +389,8 @@ jobs: echo "UT_LOG_PATH=${PRIMUS_WORKDIR}/ut_out/pr-${{ github.event.pull_request.number }}-${ts}-${commit_id}" >> $GITHUB_ENV elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then echo "UT_LOG_PATH=${PRIMUS_WORKDIR}/ut_out/main-${ts}-${commit_id}" >> $GITHUB_ENV + elif [[ "${{ github.event_name }}" == "schedule" ]]; then + echo "UT_LOG_PATH=${PRIMUS_WORKDIR}/ut_out/weekly-${ts}-${commit_id}" >> $GITHUB_ENV elif [[ "${{ github.event_name }}" == "release" ]]; then TAG_NAME="${{ github.ref }}" TAG="${TAG_NAME#refs/tags/}" @@ -328,70 +400,84 @@ jobs: fi - name: Run CLI Shell Tests run: | + docker exec -i -e GITHUB_WORKSPACE="${CWS}" -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' + set -e echo "Running Primus CLI shell tests..." bash ./tests/runner/run_all_tests.sh + IN - name: Run Primus Core Tests run: | - echo "Running Primus Core tests..." # Note: The tests `test_fp8_te_linear` and `test_te_linear` are temporarily skipped due to intermittent failures. + # Note: `test_limit_layers_moe_with_dense_keeps_two` is temporarily disabled pending a fix to + # `_limit_layers_for_projection` dense-layer handling (num_layers collapses to 1 instead of 2). + # Note: `test_single_pass_te_vs_local` is temporarily disabled pending a fix to the + # diffusion TE-vs-local spec attention parity check. # Note HSA_NO_SCRATCH_RECLAIM=1 must be set to avoid RCCL perf hit (TAS-8N Node), rocm ver:70125424 - export HSA_NO_SCRATCH_RECLAIM=1 + docker exec -i \ + -e GITHUB_WORKSPACE="${CWS}" -e HSA_NO_SCRATCH_RECLAIM=1 -e PRIMUS_CI_FULL \ + -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' + set -e + echo "Running Primus Core tests..." mkdir -p "${GITHUB_WORKSPACE}/test-reports" - # Component-aware selection on PRs; full suite on push/release/dispatch. - # Fail-safe: any failure to compute the diff falls back to the full suite. - TARGETS="./tests/unit_tests/" - if [[ "${{ github.event_name }}" == "pull_request" ]]; then - base_sha="${{ github.event.pull_request.base.sha }}" - git fetch --no-tags --depth=200 origin "${{ github.base_ref }}" 2>/dev/null || true - changed="$(git diff --name-only "${base_sha}" HEAD 2>/dev/null || true)" - if [[ -n "${changed}" ]]; then - sel="$(printf '%s\n' "${changed}" | python tools/ci/select_tests.py)" - [[ -n "${sel}" ]] && TARGETS="${sel}" - fi - fi - echo "Selected unit-test targets: ${TARGETS}" - # shellcheck disable=SC2086 # intentional word-splitting of multiple paths - pytest --maxfail=1 -s ${TARGETS} \ + # Weekend full run also takes the @pytest.mark.slow release-tier shape + # gates (they self-skip off MI355X). + TIER_ARGS=() + [[ "${PRIMUS_CI_FULL:-0}" == "1" ]] && TIER_ARGS=(--run-slow) + pytest --maxfail=1 -s ./tests/unit_tests/ "${TIER_ARGS[@]}" \ --cov=primus --cov-report=term-missing:skip-covered \ --junitxml="${GITHUB_WORKSPACE}/test-reports/core-unit.xml" \ --deselect=tests/unit_tests/megatron/cco/test_tp_overlap.py::TPOverlapTestCase::test_fp8_te_linear \ --deselect=tests/unit_tests/megatron/cco/test_tp_overlap.py::TPOverlapTestCase::test_te_linear \ --deselect=tests/unit_tests/megatron/transformer/moe/test_token_dispatcher.py::TestFlexDispatcher::test_forward_backward \ - --deselect=tests/unit_tests/megatron/transformer/moe/test_token_dispatcher.py::TestFlexDispatcher::test_capacity_forward_backward + --deselect=tests/unit_tests/megatron/transformer/moe/test_token_dispatcher.py::TestFlexDispatcher::test_capacity_forward_backward \ + --deselect=tests/unit_tests/core/projection/test_performance_projection.py::test_limit_layers_moe_with_dense_keeps_two \ + --deselect=tests/unit_tests/backends/megatron/diffusion/test_te_vs_local_spec_attention.py::TestTEvsLocalSpecAttention::test_single_pass_te_vs_local + IN - name: Snapshot unit-test coverage if: always() continue-on-error: true run: | + docker exec -i -e GITHUB_WORKSPACE="${CWS}" -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' + set +e # Keep the unit data for the final unit-vs-E2E comparison table # (.coverage.unit is fed to `coverage combine` after the E2E steps). python -m coverage json -o coverage_unit.json 2>/dev/null || true - cp .coverage "$GITHUB_WORKSPACE/.coverage.unit" 2>/dev/null || true + cp .coverage "${GITHUB_WORKSPACE}/.coverage.unit" 2>/dev/null || true + IN - name: Setup E2E training coverage if: always() continue-on-error: true run: | # Record coverage of the training subprocesses (primus-cli -> torchrun - # -> python). A .pth makes every new interpreter call - # coverage.process_startup(); COVERAGE_PROCESS_START points it at the rc - # and COVERAGE_FILE keeps E2E data separate from the unit .coverage. + # -> python). A .pth (written inside the container's site-packages) makes + # every new interpreter call coverage.process_startup(); COVERAGE_PROCESS_START + # points it at the rc and COVERAGE_FILE keeps E2E data separate from unit. # parallel=true -> one data file per rank; sigterm=true captures ranks - # killed on teardown. These env vars are inherited by the E2E steps. + # killed on teardown. These env vars are passed to the E2E steps via -e. + docker exec -i -e GITHUB_WORKSPACE="${CWS}" -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' + set -e SP=$(python -c "import site; print(site.getsitepackages()[0])") echo "import coverage; coverage.process_startup()" > "$SP/primus_e2e_coverage.pth" - printf '[run]\nparallel = true\nsource = primus\nsigterm = true\n' > "$GITHUB_WORKSPACE/.coveragerc_e2e" - echo "COVERAGE_PROCESS_START=$GITHUB_WORKSPACE/.coveragerc_e2e" >> "$GITHUB_ENV" - echo "COVERAGE_FILE=$GITHUB_WORKSPACE/.coverage_e2e" >> "$GITHUB_ENV" + printf '[run]\nparallel = true\nsource = primus\nsigterm = true\n' > "${GITHUB_WORKSPACE}/.coveragerc_e2e" + IN + echo "COVERAGE_PROCESS_START=${CWS}/.coveragerc_e2e" >> "$GITHUB_ENV" + echo "COVERAGE_FILE=${CWS}/.coverage_e2e" >> "$GITHUB_ENV" - name: Decide E2E scope (torch) run: | # Fail-safe: default to running all E2E (push/release/dispatch, or any # diff failure). On PRs, narrow to the suites the changed files affect. + # Computed inside the container (git + select_tests.py), result read back + # on the runner to export RUN_*_E2E for the (host-side) skip guards. + docker exec -i -e GITHUB_WORKSPACE="${CWS}" -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' + set -e M=1; T=1 + git config --global --add safe.directory "${GITHUB_WORKSPACE}" 2>/dev/null || true if [[ "${{ github.event_name }}" == "pull_request" ]]; then base="${{ github.event.pull_request.base.sha }}" git fetch --no-tags --depth=200 origin "${{ github.base_ref }}" 2>/dev/null || true changed="$(git diff --name-only "${base}" HEAD 2>/dev/null || true)" if [[ -n "${changed}" ]]; then - e2e="$(printf '%s\n' "${changed}" | python tools/ci/select_tests.py --e2e)" + e2e="$(printf '%s\n' "${changed}" | python tools/ci/select_tests.py)" echo "Selected torch E2E scope: ${e2e:-}" if [[ "${e2e}" != "all" ]]; then echo "${e2e}" | grep -qw megatron || M=0 @@ -399,33 +485,79 @@ jobs: fi fi fi - echo "RUN_MEGATRON_E2E=${M}" >> "$GITHUB_ENV" - echo "RUN_TORCHTITAN_E2E=${T}" >> "$GITHUB_ENV" + printf '%s %s\n' "$M" "$T" > "${GITHUB_WORKSPACE}/.e2e_scope" + IN + read RM RT < "${GITHUB_WORKSPACE}/.e2e_scope" + echo "RUN_MEGATRON_E2E=${RM:-1}" >> "$GITHUB_ENV" + echo "RUN_TORCHTITAN_E2E=${RT:-1}" >> "$GITHUB_ENV" + if [[ "${PRIMUS_CI_FULL:-0}" == "1" ]]; then + echo "**Test tier:** full — every model E2E plus the slow unit tests." >> "$GITHUB_STEP_SUMMARY" + else + echo "**Test tier:** slim — one model E2E per architecture (\`-m 'not weekly'\`); the siblings run in the weekend scheduled build." >> "$GITHUB_STEP_SUMMARY" + fi - name: Run Primus Model Tests -- Megatron-LM env: HF_TOKEN: ${{secrets.HF_TOKEN}} run: | if [[ "${RUN_MEGATRON_E2E:-1}" != "1" ]]; then echo "Skipping Megatron-LM E2E: no relevant changes."; exit 0; fi - echo "Set UT_LOG_PATH: ${{ env.UT_LOG_PATH }}" - rm -rf "${{ env.UT_LOG_PATH }}" - mkdir -p "${{ env.UT_LOG_PATH }}" - # MASTER_PORT=10009 DATA_PATH=/wekafs/primus-data \ - MASTER_PORT=10009 DATA_PATH=/mnt/apps_proxy/tas/0_public/data HSA_NO_SCRATCH_RECLAIM=1 \ - GPU_ARCHS=gfx942 \ - pytest --maxfail=1 -s ./tests/trainer/test_megatron_trainer.py \ + docker exec -i \ + -e GITHUB_WORKSPACE="${CWS}" -e UT_LOG_PATH -e HF_TOKEN \ + -e COVERAGE_PROCESS_START -e COVERAGE_FILE -e PRIMUS_CI_FULL \ + -e MASTER_PORT=10009 -e DATA_PATH=/mnt/apps_proxy/tas/0_public/data \ + -e HSA_NO_SCRATCH_RECLAIM=1 \ + -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' + set -e + # Detect the real GPU arch (gfx950 on MI355X, gfx942 on MI300X, ...) so the + # aiter/turbo JIT kernels are built for the actual hardware. Hardcoding the + # wrong arch (e.g. gfx942 on an MI355X node) makes those kernels SIGSEGV. + export GPU_ARCHS="$(python3 -c 'import torch; print(torch.cuda.get_device_properties(0).gcnArchName.split(":")[0])' 2>/dev/null || echo gfx942)" + echo "Detected GPU_ARCHS=${GPU_ARCHS}" + echo "Set UT_LOG_PATH: ${UT_LOG_PATH}" + rm -rf "${UT_LOG_PATH}" + mkdir -p "${UT_LOG_PATH}" + # Temporarily deselect the Mamba / SSM tests: on gfx950 the pinned Triton + # crashes (SIGABRT) while compiling the mamba_ssm SSD backward kernel with an + # LLVM assertion (Sequence.h: `Begin <= End`) in make_amdgcn. Re-enable once + # the Triton/LLVM toolchain is fixed for this kernel. + TIER_ARGS=() + [[ "${PRIMUS_CI_FULL:-0}" == "1" ]] || TIER_ARGS=(-m "not weekly") + pytest --maxfail=1 -s ./tests/trainer/test_megatron_trainer.py "${TIER_ARGS[@]}" \ + --deselect tests/trainer/test_megatron_trainer.py::TestMegatronTrainer::test_mamba_130M_bridge_pretrain \ + --deselect tests/trainer/test_megatron_trainer.py::TestMegatronTrainer::test_mamba_370M \ + --deselect tests/trainer/test_megatron_trainer.py::TestMegatronTrainer::test_zebra_llama_1B_hybrid \ --junitxml="${GITHUB_WORKSPACE}/test-reports/megatron-e2e.xml" + IN - name: Run Primus Model Tests -- TorchTitan env: HF_TOKEN: ${{secrets.HF_TOKEN}} run: | if [[ "${RUN_TORCHTITAN_E2E:-1}" != "1" ]]; then echo "Skipping TorchTitan E2E: no relevant changes."; exit 0; fi - echo "Set UT_LOG_PATH: ${{ env.UT_LOG_PATH }}" - rm -rf "${{ env.UT_LOG_PATH }}" - mkdir -p "${{ env.UT_LOG_PATH }}" - # MASTER_PORT=10009 DATA_PATH=/wekafs/primus-data \ - MASTER_PORT=10009 DATA_PATH=/mnt/apps_proxy/tas/0_public/data HSA_NO_SCRATCH_RECLAIM=1 \ - pytest --maxfail=1 -s ./tests/trainer/test_torchtitan_trainer.py \ + docker exec -i \ + -e GITHUB_WORKSPACE="${CWS}" -e UT_LOG_PATH -e HF_TOKEN \ + -e COVERAGE_PROCESS_START -e COVERAGE_FILE -e PRIMUS_CI_FULL \ + -e MASTER_PORT=10009 -e DATA_PATH=/mnt/apps_proxy/tas/0_public/data \ + -e HSA_NO_SCRATCH_RECLAIM=1 \ + -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' + set -e + echo "Set UT_LOG_PATH: ${UT_LOG_PATH}" + rm -rf "${UT_LOG_PATH}" + mkdir -p "${UT_LOG_PATH}" + # Temporarily deselect the deepseek_v3_671b config: under activation + # checkpointing the MoE dispatch is non-deterministic on recompute, so the + # recomputed dispatch tensors get a different token count than the forward + # pass (e.g. 635136 vs 635144) and torch.utils.checkpoint aborts with a + # CheckpointError. Re-enable once the MoE dispatch is recompute-stable. + # Temporarily deselect the deepseek_v3_16b_fp8 config: the aiter flash + # attention v3 backward kernel raises "RuntimeError: invalid argument for + # fmha_v3_bwd" during loss.backward() on the fp8 path (all ranks). + # Re-enable once the primus_turbo/aiter fp8 attention backward is fixed. + TIER_ARGS=() + [[ "${PRIMUS_CI_FULL:-0}" == "1" ]] || TIER_ARGS=(-m "not weekly") + pytest --maxfail=1 -s ./tests/trainer/test_torchtitan_trainer.py "${TIER_ARGS[@]}" \ + --deselect tests/trainer/test_torchtitan_trainer.py::TestTorchTitanTrainer::test_deepseek_v3_671b \ + --deselect tests/trainer/test_torchtitan_trainer.py::TestTorchTitanTrainer::test_deepseek_v3_16b_fp8 \ --junitxml="${GITHUB_WORKSPACE}/test-reports/torchtitan-e2e.xml" + IN - name: Run Primus CLI tool smoke (benchmark / preflight, E2E coverage) if: always() continue-on-error: true @@ -433,8 +565,12 @@ jobs: # Best-effort CLI smoke so primus/tools (GEMM / attention / RCCL # benches + preflight probes) is exercised under the inherited E2E # coverage injection. Never fails CI: coverage is kept up to any exit. - export HSA_NO_SCRATCH_RECLAIM=1 - OUT="${{ env.UT_LOG_PATH }}/cli_smoke"; mkdir -p "$OUT" + docker exec -i \ + -e GITHUB_WORKSPACE="${CWS}" -e UT_LOG_PATH -e HSA_NO_SCRATCH_RECLAIM=1 \ + -e COVERAGE_PROCESS_START -e COVERAGE_FILE \ + -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' + set +e + OUT="${UT_LOG_PATH}/cli_smoke"; mkdir -p "$OUT" cli() { python -m primus.cli.main "$@" || true; } # single-GPU benches + preflight info cli benchmark gemm --M 512 --N 512 --K 512 --duration 2 --output-file "$OUT/gemm.md" @@ -446,6 +582,7 @@ jobs: NGPU=$(python -c "import torch; print(torch.cuda.device_count())" 2>/dev/null || echo 1) [ "$NGPU" -ge 2 ] && torchrun --nproc_per_node="$NGPU" -m primus.cli.main benchmark rccl --op all_reduce all_gather --min-bytes 1K --max-bytes 256K --num-sizes 2 --iters 3 --warmup 1 --output-file "$OUT/rccl.md" || true [ "$NGPU" -ge 8 ] && torchrun --nproc_per_node=8 -m primus.cli.main benchmark strided-allgather --sizes-mb 8 --iters 3 --warmup 1 || true + IN - name: Upload test reports if: always() uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 @@ -457,23 +594,30 @@ jobs: - name: Write test summary if: always() run: | - python tools/ci/junit_summary.py --title torch test-reports/*.xml >> "$GITHUB_STEP_SUMMARY" || true + docker exec -e GITHUB_WORKSPACE="${CWS}" -w "${CWS}" "${UT_CONTAINER}" \ + bash -lc 'python tools/ci/junit_summary.py --title torch test-reports/*.xml' >> "$GITHUB_STEP_SUMMARY" || true - name: Write runtime summary if: always() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - python tools/ci/runtime_summary.py --title torch "$RUNNER_TEMP/runtime.tsv" >> "$GITHUB_STEP_SUMMARY" || true + docker exec -e GITHUB_WORKSPACE="${CWS}" -e GITHUB_TOKEN -e RUNNER_TEMP -w "${CWS}" "${UT_CONTAINER}" \ + bash -lc 'python tools/ci/runtime_summary.py --title torch' >> "$GITHUB_STEP_SUMMARY" || true - name: Build torch coverage json (unit + E2E) if: always() continue-on-error: true run: | + docker exec -i -e GITHUB_WORKSPACE="${CWS}" -w "${CWS}" "${UT_CONTAINER}" bash -s <<'IN' + set +e # Don't instrument coverage's own helper processes here. unset COVERAGE_PROCESS_START # Merge per-rank E2E data, then line-merge unit + E2E into one dataset. # The JSON is consumed by the coverage-summary job (no per-job summary). python -m coverage combine 2>/dev/null || true - COVERAGE_FILE="$GITHUB_WORKSPACE/.coverage_all" python -m coverage combine --keep \ - "$GITHUB_WORKSPACE/.coverage.unit" "$GITHUB_WORKSPACE/.coverage_e2e" 2>/dev/null || true - COVERAGE_FILE="$GITHUB_WORKSPACE/.coverage_all" python -m coverage json -o coverage_combined.json 2>/dev/null || true + COVERAGE_FILE="${GITHUB_WORKSPACE}/.coverage_all" python -m coverage combine --keep \ + "${GITHUB_WORKSPACE}/.coverage.unit" "${GITHUB_WORKSPACE}/.coverage_e2e" 2>/dev/null || true + COVERAGE_FILE="${GITHUB_WORKSPACE}/.coverage_all" python -m coverage json -o coverage_combined.json 2>/dev/null || true + IN - name: Upload torch coverage json if: always() continue-on-error: true @@ -487,18 +631,43 @@ jobs: - name: Clean if: always() run: | - rm -rf ${PRIMUS_WORKDIR}/Primus-Turbo - rm -rf ${PRIMUS_WORKDIR}/Primus - # Remove E2E coverage artifacts and, importantly, the subprocess - # injection .pth so it never leaks into later jobs on this persistent runner. - rm -f "${GITHUB_WORKSPACE}/.coverage_e2e"* "${GITHUB_WORKSPACE}/.coverage.unit" "${GITHUB_WORKSPACE}/.coverage_all" "${GITHUB_WORKSPACE}/.coveragerc_e2e" coverage_unit.json coverage_combined.json || true - SP=$(python -c "import site; print(site.getsitepackages()[0])" 2>/dev/null) && rm -f "$SP/primus_e2e_coverage.pth" || true + # The container steps run as ROOT and write root-owned files into the + # mounted GITHUB_WORKSPACE (__pycache__, .pytest_cache/, .hypothesis/, + # logs/, test-reports/, ut_out/, .coverage*, plus E2E-generated dirs like + # data/, output/, pp_simulation_result/). The next run's actions/checkout + # runs as the runner user and cannot delete those, so it fails at + # "Deleting the contents of ..." (e.g. EACCES on data/huggingface). + # Remove the well-known artifacts here AS ROOT (via the container), then + # chown whatever root-owned files remain back to the runner user so the + # next checkout can clean them. Artifacts have already been uploaded by the + # preceding upload steps. The container is left running for reuse. + docker exec -i -e GITHUB_WORKSPACE="${CWS}" "${UT_CONTAINER}" bash -s <<'IN' || true + set +e + cd "${GITHUB_WORKSPACE}" || exit 0 + rm -rf logs test-reports ut_out .pytest_cache .hypothesis \ + .coverage .coverage.* .coverage_e2e* .coverage.unit .coverage_all \ + .coveragerc_e2e .e2e_scope coverage_unit.json coverage_combined.json + # Bytecode caches written during the run (root-owned; block checkout clean). + find . -type d -name __pycache__ -prune -exec rm -rf {} + 2>/dev/null + # Coverage subprocess-injection .pth in site-packages. + SP=$(python -c "import site; print(site.getsitepackages()[0])" 2>/dev/null) && rm -f "$SP/primus_e2e_coverage.pth" + # Safety net: hand any remaining root-owned files (E2E-generated data/, + # output/, pp_simulation_result/, .git internals, ...) back to the runner + # user so the next actions/checkout can delete them instead of hitting + # EACCES. Owner is taken from GITHUB_WORKSPACE itself (the runner user). + owner="$(stat -c '%u:%g' "${GITHUB_WORKSPACE}" 2>/dev/null)" + [ -n "$owner" ] && find "${GITHUB_WORKSPACE}" -user 0 -exec chown "$owner" {} + 2>/dev/null + IN run-unittest-jax: + permissions: + contents: read + actions: read # let "Write runtime summary" list this job's own steps for auto-discovered timing env: # PRIMUS_WORKDIR: /wekafs/primus-data/primus_safe_ci/jax PRIMUS_WORKDIR: /mnt/apps_proxy/tas/0_public/primus_docker_jax_ci/actions-runner needs: [code-lint] + if: ${{ github.event_name != 'pull_request' || !github.event.pull_request.draft }} runs-on: [primus-jax-tas-runner] # docker container primus_jax_github_runner on tas a16-31 steps: - run: echo "🎉 Begin Primus-Turbo Checkout." @@ -565,6 +734,8 @@ jobs: echo "UT_LOG_PATH=${PRIMUS_WORKDIR}/ut_out/pr-${{ github.event.pull_request.number }}-${ts}-${commit_id}" >> $GITHUB_ENV elif [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then echo "UT_LOG_PATH=${PRIMUS_WORKDIR}/ut_out/main-${ts}-${commit_id}" >> $GITHUB_ENV + elif [[ "${{ github.event_name }}" == "schedule" ]]; then + echo "UT_LOG_PATH=${PRIMUS_WORKDIR}/ut_out/weekly-${ts}-${commit_id}" >> $GITHUB_ENV elif [[ "${{ github.event_name }}" == "release" ]]; then TAG_NAME="${{ github.ref }}" TAG="${TAG_NAME#refs/tags/}" @@ -599,7 +770,7 @@ jobs: git fetch --no-tags --depth=200 origin "${{ github.base_ref }}" 2>/dev/null || true changed="$(git diff --name-only "${base}" HEAD 2>/dev/null || true)" if [[ -n "${changed}" ]]; then - e2e="$(printf '%s\n' "${changed}" | python tools/ci/select_tests.py --e2e)" + e2e="$(printf '%s\n' "${changed}" | python tools/ci/select_tests.py)" echo "Selected jax E2E scope: ${e2e:-}" if [[ "${e2e}" != "all" ]]; then echo "${e2e}" | grep -qw maxtext || X=0 @@ -619,6 +790,8 @@ jobs: # run_unit_tests.py shells out to pytest; PYTEST_ADDOPTS injects the # JUnit report without having to touch the wrapper script. export PYTEST_ADDOPTS="--junitxml=${GITHUB_WORKSPACE}/test-reports/maxtext-e2e.xml" + # JAX_SKIP_UT=1 in both tiers: the MaxText models it hides stay hidden + # even in the weekend full run, like the --deselect'ed cases. # MASTER_PORT=10009 DATA_PATH=/wekafs/primus-data \ MASTER_PORT=10009 DATA_PATH=/mnt/apps_proxy/tas/0_public/data \ JAX_SKIP_UT=1 python ./tests/run_unit_tests.py --jax @@ -636,8 +809,10 @@ jobs: python tools/ci/junit_summary.py --title jax test-reports/*.xml >> "$GITHUB_STEP_SUMMARY" || true - name: Write runtime summary if: always() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - python tools/ci/runtime_summary.py --title jax "$RUNNER_TEMP/runtime.tsv" >> "$GITHUB_STEP_SUMMARY" || true + python tools/ci/runtime_summary.py --title jax >> "$GITHUB_STEP_SUMMARY" || true - name: Build jax coverage json (MaxText E2E) if: always() continue-on-error: true diff --git a/.github/workflows/deploy-backend-gap-dashboard.yml b/.github/workflows/deploy-backend-gap-dashboard.yml index 2c61ea52d..958c9d655 100644 --- a/.github/workflows/deploy-backend-gap-dashboard.yml +++ b/.github/workflows/deploy-backend-gap-dashboard.yml @@ -1,15 +1,28 @@ name: Deploy Backend Gap Dashboard +# Report content lives on the orphan `dashboard-data` branch; tooling + site +# shell live on `main`. This job checks out both and overlays the data branch's +# `docs/` before building, so report runs never touch `main`. +# +# Pre-merge preview: run this workflow via "Run workflow" on a dev branch; it +# builds and deploys the full site (dashboard + that branch's sections) so new +# content can be validated before merging. Note: one Pages site per repo, so a +# preview temporarily replaces production until the next normal run. on: workflow_dispatch: + # Companion `notify-dashboard-deploy.yml` on the dashboard-data branch fires + # this after a data push, so report updates publish without a timed schedule. + repository_dispatch: + types: [dashboard-data-updated] + # Tooling/shell/workflow changes and mounted section sources (declared in + # pages-sections.json) on main. Add a new section's source path here too. push: branches: - main paths: - - "docs/backend-gap/**" - - "docs/weekly_reports/**" - - "docs/monthly_reports/**" - "tools/backend_gap_report/**" + - "tools/pip_index/**" + - "examples/deepseek-v4/projection/site/**" - ".github/workflows/deploy-backend-gap-dashboard.yml" permissions: @@ -21,12 +34,30 @@ concurrency: group: backend-gap-dashboard-pages cancel-in-progress: true +env: + DASHBOARD_DATA_BRANCH: dashboard-data + jobs: build: runs-on: ubuntu-latest steps: - - name: Check out repository + - name: Check out repository (tooling + site shell from main) + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + + - name: Check out dashboard data branch uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + ref: ${{ env.DASHBOARD_DATA_BRANCH }} + path: _dashboard_data + + - name: Overlay report data onto the checkout + run: | + if [ -d _dashboard_data/docs ]; then + cp -r _dashboard_data/docs/. docs/ + else + echo "::error::dashboard-data branch has no docs/ directory" + exit 1 + fi - name: Set up Python uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 diff --git a/.github/workflows/docker-release/Dockerfile.jax-v26.5 b/.github/workflows/docker-release/Dockerfile.jax-v26.5 new file mode 100644 index 000000000..5d6e8a334 --- /dev/null +++ b/.github/workflows/docker-release/Dockerfile.jax-v26.5 @@ -0,0 +1,281 @@ +ARG DEFAULT_BASE=ubuntu:24.04 +ARG BASE_IMAGE=${DEFAULT_BASE} +FROM ${BASE_IMAGE:-$DEFAULT_BASE} AS jax_base + +WORKDIR /workspace/ +ENV MAX_JOBS=128 +ENV PYTORCH_ROCM_ARCH="gfx942;gfx950" +ENV ROCM_AMDGPU_TARGETS="gfx942,gfx950" + +# Flag to fix profiler hang issue. +ENV ROCPROFILER_QUEUE_INTERPOSITION=0 +ENV DEBUG_HIP_DYNAMIC_QUEUES=0 + +# Install Ubuntu dependencies +ENV DEBIAN_FRONTEND=noninteractive +RUN apt update \ + && apt install -y \ + gfortran \ + git \ + git-lfs \ + ninja-build \ + g++ \ + pkg-config \ + xxd \ + patchelf \ + automake \ + libtool \ + python3-venv \ + python3-dev \ + python3-pip \ + python-is-python3 \ + libegl1-mesa-dev \ + wget \ + sudo \ + flex \ + liblzma-dev \ + ccache \ + libdw1 \ + libdrm-dev \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# Setup python env +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" +RUN pip install --upgrade pip \ + && pip uninstall -y wheel \ + && pip install \ + cmake==3.31.6 \ + ninja==1.11.1.3 \ + wheel==0.45.1 \ + packaging==25.0 \ + setuptools==69.5.1 \ + && rm -rf /root/.cache + +ENV THEROCK_TARBALL=https://repo.amd.com/rocm/tarball-multi-arch/therock-dist-linux-multiarch-7.14.0.tar.gz + +RUN mkdir -p /opt/rocm \ + && cd /opt/rocm \ + && wget ${THEROCK_TARBALL} \ + && tar -xvf *.tar.gz -C /opt/rocm \ + && rm *.tar.gz + +# Setup ROCm stack related envs +ENV PATH="/opt/rocm/lib:/opt/venv/bin:/opt/rocm/bin:$PATH" +ENV LD_LIBRARY_PATH="/opt/rocm/lib:/opt/rocm/lib/rocm_sysdeps/lib" +ENV ROCM_PATH=/opt/rocm +ENV HIP_PLATFORM=amd +ENV HIP_DEVICE_LIB_PATH=/opt/rocm/lib/llvm/amdgcn/bitcode + +# Install LLVM 18 +RUN echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main' | sudo tee /etc/apt/sources.list.d/llvm.list +RUN wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - +RUN sudo apt-get update +RUN sudo apt-get install -y clang-18 lld-18 llvm-18-dev llvm-18-tools + +# amdsmi +RUN pip install amdsmi==7.0.2 \ + && rm -rf /root/.cache + +WORKDIR /workspace + +# Install maxtext +ARG MAXTEXT_REPO=https://github.com/ROCm/maxtext.git +ARG MAXTEXT_BRANCH=release/v26.5 + +RUN git clone ${MAXTEXT_REPO} \ + && cd maxtext \ + && git checkout ${MAXTEXT_BRANCH} \ + && chmod +x src/dependencies/scripts/setup.sh \ + && ./src/dependencies/scripts/setup.sh \ + && cd .. \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* \ + && rm -rf /root/.cache + +# Fix (root cause): the prebuilt PyPI TensorFlow wheel bundles an LLVM whose symbols +# collide with ROCm 7.14's libLLVM inside Grain "spawn" workers -> SIGSEGV on +# `import tensorflow` after `import jax`. Rebuild the CPU-only TF wheel from ROCm's +# fork (correct symbol visibility) and install it in place of the stock wheel. A CPU +# build also has no bundled NCCL, so it preserves the previous XLA-RCCL fix. +# NOTE: bumps TF to 2.21.0 and adds a long (~30-60 min) bazel build to the image. +ARG TF_REPO=https://github.com/ROCm/tensorflow-upstream.git +ARG TF_BRANCH=upstream-v2.21.0 +ARG BAZELISK_VERSION=v1.25.0 +# Bazelisk auto-selects the bazel version pinned by TF's .bazelversion. +RUN apt-get update && apt-get install -y unzip zip \ + && wget -O /usr/local/bin/bazel \ + https://github.com/bazelbuild/bazelisk/releases/download/${BAZELISK_VERSION}/bazelisk-linux-amd64 \ + && chmod +x /usr/local/bin/bazel \ + && apt clean && rm -rf /var/lib/apt/lists/* +RUN git clone --depth 1 --branch ${TF_BRANCH} ${TF_REPO} tensorflow-upstream \ + && cd tensorflow-upstream \ + && bazel build //tensorflow/tools/pip_package:wheel \ + --repo_env=WHEEL_NAME=tensorflow_cpu \ + --repo_env=HERMETIC_PYTHON_VERSION=3.12 \ + && pip uninstall -y tensorflow tensorflow-cpu tensorflow_cpu \ + && pip install --no-deps \ + bazel-bin/tensorflow/tools/pip_package/wheel_house/tensorflow_cpu-2.21.0-cp312-cp312-linux_x86_64.whl \ + && cd .. \ + && rm -rf tensorflow-upstream /root/.cache/bazel /root/.cache + +# JAX +# Note: JAX and related libraries need to be installed before TE +ARG JAX_VERSION=0.10.0 +ENV JAX_VERSION=${JAX_VERSION} +# See: https://repo.amd.com/rocm/whl-multi-arch/jax-rocm7-pjrt/ +ENV JAX_PJRT_VERSION=0.10.0+rocm7.14.0 +# See: https://repo.amd.com/rocm/whl-multi-arch/jax-rocm7-plugin/ +ENV JAX_PLUGIN_VERSION=0.10.0+rocm7.14.0 + +RUN pip install jax==${JAX_VERSION} jaxlib==${JAX_VERSION} scipy==1.16 \ + && pip install \ + --index-url https://repo.amd.com/rocm/whl-multi-arch/ \ + --pre jax_rocm7_pjrt==${JAX_PJRT_VERSION} \ + --pre jax_rocm7_plugin==${JAX_PLUGIN_VERSION} \ + && rm -rf /root/.cache + +# TransformerEngine +# See: https://rocm.frameworks-nightlies.amd.com/whl-staging/device-all/transformer-engine-rocm-jax/ +ARG TE_VERSION=2.15.0.dev0+rocm7.15.0a20260707.72d01a0 + +RUN pip install \ + pybind11==3.0.4 \ + importlib-metadata==8.7.1 \ + pydantic==2.13.4 \ + flax==0.12.2 \ + && pip install \ + --index-url https://rocm.frameworks-nightlies.amd.com/whl-staging/device-all/ \ + --pre \ + --no-build-isolation \ + transformer_engine_rocm_jax==${TE_VERSION} \ + && rm -rf /root/.cache + +# Configure required envs +ENV NVTE_ALLOW_NONDETERMINISTIC_ALGO=1 +ENV NVTE_USE_HIPBLASLT=1 +ENV GPU_MAX_HW_QUEUES=2 +ENV HIP_FORCE_DEV_KERNARG=1 +ENV HSA_FORCE_FINE_GRAIN_PCIE=1 +ENV NVTE_FUSED_ATTN=1 +ENV NCCL_DEBUG=VERSION +ENV NVTE_CK_USES_BWD_V3=1 +ENV NVTE_CK_USES_FWD_V3=1 +ENV NVTE_CK_IS_V3_ATOMIC_FP32=1 +ENV NVTE_CK_HOW_V3_BF16_CVT=2 +ENV XLA_PYTHON_CLIENT_MEM_FRACTION=.9 +ENV XLA_FLAGS="--xla_gpu_memory_limit_slop_factor=95 --xla_gpu_reduce_scatter_combine_threshold_bytes=8589934592 --xla_gpu_enable_latency_hiding_scheduler=True --xla_gpu_all_gather_combine_threshold_bytes=8589934592 --xla_gpu_enable_triton_gemm=False --xla_gpu_enable_cublaslt=True --xla_gpu_autotune_level=0 --xla_gpu_enable_all_gather_combine_by_dim=FALSE --xla_gpu_enable_command_buffer=''" + +WORKDIR /workspace + +# Primus +ARG PRIMUS_REPO=https://github.com/AMD-AGI/Primus.git +ARG PRIMUS_BRANCH=main + +RUN git clone --recurse-submodules ${PRIMUS_REPO} \ + && cd Primus \ + && git checkout ${PRIMUS_BRANCH} \ + && git submodule update --init third_party/maxtext/ \ + && cd .. \ + && pip uninstall -y dataclasses dataclasses_json \ + && rm -rf /root/.cache + +####### AINIC related installations ####### +RUN apt update \ + && apt install -y \ + libibverbs-dev \ + jq \ + dpkg-dev \ + kmod \ + xz-utils \ + ibverbs-utils \ + infiniband-diags \ + rdma-core \ + ethtool \ + libevent-dev \ + libhwloc-dev \ + libmunge-dev \ + software-properties-common \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# Install AMD AINIC library +ARG AINIC_BUNDLE_VERSION="1.117.5-a-77" + +RUN add-apt-repository -y "deb https://repo.radeon.com/amdainic/pensando/ubuntu/${AINIC_BUNDLE_VERSION} noble main" \ + && apt update --allow-insecure-repositories \ + && apt install -y --allow-unauthenticated libionic-dev \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# Allow insecure repositories by default +RUN echo 'Acquire::AllowInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99allow-insecure-repositories + +# Install UCX +ARG UCX_VERSION="1.18.0" + +RUN wget https://github.com/openucx/ucx/releases/download/v${UCX_VERSION}/ucx-${UCX_VERSION}.tar.gz \ + && mkdir -p ucx-${UCX_VERSION} \ + && tar -zxf ucx-${UCX_VERSION}.tar.gz -C ucx-${UCX_VERSION} --strip-components=1 \ + && cd ucx-${UCX_VERSION} \ + && mkdir build \ + && cd build \ + && ../configure --prefix=/workspace/ucx-${UCX_VERSION}/install --with-rocm=${ROCM_PATH} \ + && make -j 16 \ + && make install \ + && cd ../.. \ + && rm ucx-${UCX_VERSION}.tar.gz + +ENV UCX_INSTALL_DIR=/workspace/ucx-${UCX_VERSION}/install + +# Install OpenMPI +ARG MPI_VERSION="4.1.6" + +RUN wget https://download.open-mpi.org/release/open-mpi/v$(echo "${MPI_VERSION}" | cut -d. -f1-2)/openmpi-${MPI_VERSION}.tar.gz \ + && mkdir -p ompi-${MPI_VERSION} \ + && tar -zxf openmpi-${MPI_VERSION}.tar.gz -C ompi-${MPI_VERSION} --strip-components=1 \ + && cd ompi-${MPI_VERSION} \ + && mkdir build \ + && cd build \ + && ../configure --prefix=${WORKDIR}/ompi-${MPI_VERSION}/install --with-ucx=${UCX_INSTALL_DIR} --disable-oshmem --disable-mpi-fortran \ + && make -j 16 \ + && make install \ + && cd ../.. \ + && rm openmpi-${MPI_VERSION}.tar.gz \ + && rm -rf ompi-${MPI_VERSION} + +# Build RCCL with https://github.com/ROCm/rocm-systems/pull/8484 +# This commit `9e5e408` is on `develop` branch, commited on 2026-07-13. +RUN rm -rf rocm-systems \ + && git clone https://github.com/ROCm/rocm-systems.git \ + && cd rocm-systems \ + && git checkout 9e5e4084a4b8e1e86551b0eb054725c62354a926 \ + && cd projects/rccl \ + && ./install.sh -l \ + --prefix build/ \ + --amdgpu_targets="gfx942;gfx950" \ + && cp -r build/release/librccl* /opt/rocm/lib/ \ + && cd /workspace \ + && rm -rf rocm-systems + +####### End of AINIC related installations ####### + +# Secret scan will complain those files to be secrets. Explicitly remove them. +RUN rm -f /workspace/Primus/.git/packed-refs /workspace/Primus/.git/modules/third_party/**/packed-refs + +# Clean cache +RUN rm -rf /root/.cache + +# Training docker manifest +ARG GIT_COMMIT_TAG=DEV +ARG DOCKERFILE_PATH=Dockerfile + +# Copy patch files for training docker versioning +RUN mkdir -p /workspace/.manifest \ + && env > /workspace/.manifest/env.txt \ + && pip list > /workspace/.manifest/requirements.txt \ + && dpkg -l > /workspace/.manifest/dpkg-list.txt \ + && echo "${GIT_COMMIT_TAG}" > /workspace/.manifest/training_docker_version + +COPY ${DOCKERFILE_PATH} /workspace/.manifest/Dockerfile diff --git a/.github/workflows/docker-release/Dockerfile.primus-v26.5 b/.github/workflows/docker-release/Dockerfile.primus-v26.5 new file mode 100644 index 000000000..02529d57a --- /dev/null +++ b/.github/workflows/docker-release/Dockerfile.primus-v26.5 @@ -0,0 +1,502 @@ +# Primus Training Dockerfile for ROCm on TheRock +ARG DEFAULT_BASE=ubuntu:24.04 +ARG BASE_IMAGE=${DEFAULT_BASE} +FROM ${BASE_IMAGE:-$DEFAULT_BASE} AS pytorch_base + +WORKDIR /workspace/ +ENV MAX_JOBS=128 +ENV PYTORCH_ROCM_ARCH="gfx942;gfx950" +ENV ROCM_AMDGPU_TARGETS="gfx942,gfx950" + +# Install Ubuntu dependencies +ENV DEBIAN_FRONTEND=noninteractive +RUN apt update \ + && apt install -y \ + gfortran \ + git \ + git-lfs \ + ninja-build \ + g++ \ + pkg-config \ + xxd \ + patchelf \ + automake \ + libtool \ + python3-venv \ + python3-dev \ + python3-pip \ + python-is-python3 \ + libegl1-mesa-dev \ + wget \ + sudo \ + flex \ + liblzma-dev \ + ccache \ + libdw1 \ + libdrm-dev \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# Setup python env +RUN python -m venv /opt/venv +ENV PATH="/opt/venv/bin:$PATH" +RUN pip install --upgrade pip \ + && pip install \ + pybind11 \ + typeguard \ + wheel==0.45.1 \ + cmake==3.31.6 \ + ninja==1.11.1.3 \ + packaging==25.0 \ + setuptools==75.1.0 \ + && rm -rf /root/.cache + +RUN apt update \ + && apt install -y ninja-build autoconf libtool flex rdma-core ffmpeg \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# Workaround for the HSA_STATUS_ERROR_OUT_OF_RESOURCES issue +ENV HSA_ENABLE_SCRATCH_ASYNC_RECLAIM=0 +ENV HSA_NO_SCRATCH_RECLAIM=1 + +# Pytorch + rocm +# See: https://rocm.nightlies.amd.com/whl-multi-arch/torch/ +ARG PYTORCH_VERSION=2.12.0+rocm7.15.0a20260720 + +RUN pip install \ + cxxfilt==0.3.0 \ + tqdm==4.67.3 \ + pyyaml==6.0.3 \ + pytest==9.0.3 \ + matplotlib==3.10.9 \ + pandas==2.3.3 \ + py-cpuinfo==9.0.0 \ + build==1.5.0 \ + && rm -rf /root/.cache + +RUN python -m pip uninstall -y torch && \ + python -m pip install \ + --index-url https://rocm.nightlies.amd.com/whl-multi-arch \ + --pre \ + torch==${PYTORCH_VERSION} \ + amd-torch-device-gfx942==${PYTORCH_VERSION} \ + amd-torch-device-gfx950==${PYTORCH_VERSION} \ + rocm-sdk-devel \ + rocm-sdk-device-gfx942 \ + rocm-sdk-device-gfx950 \ + torchaudio \ + torchvision==0.27 \ + amd-torchvision-device-gfx942==0.27 \ + amd-torchvision-device-gfx950==0.27 \ + apex \ + && rm -rf /root/.cache + +RUN rocm-sdk init +ENV ROCM_PATH=/opt/venv/lib/python3.12/site-packages/_rocm_sdk_devel +# Note: Primus uses `ROCM_HOME` instead of `ROCM_PATH`. Set both to avoid potential issues. +ENV ROCM_HOME=/opt/venv/lib/python3.12/site-packages/_rocm_sdk_devel +ENV HIP_PLATFORM=amd +ENV HIP_PATH=$ROCM_PATH +ENV HIP_CLANG_PATH=$ROCM_PATH/llvm/bin +ENV HIP_INCLUDE_PATH=$ROCM_PATH/include +ENV HIP_LIB_PATH=$ROCM_PATH/lib +ENV HIP_DEVICE_LIB_PATH=$ROCM_PATH/lib/llvm/amdgcn/bitcode +ENV PATH="$ROCM_PATH/bin:$HIP_CLANG_PATH:$PATH" +ENV LD_LIBRARY_PATH="$HIP_LIB_PATH:$ROCM_PATH/lib:$ROCM_PATH/lib64:$ROCM_PATH/llvm/lib:$ROCM_PATH/lib/host-math/lib:$ROCM_PATH/lib/rocm_sysdeps/lib" +ENV LIBRARY_PATH="$HIP_LIB_PATH:$ROCM_PATH/lib:$ROCM_PATH/lib64" +ENV CPATH=$HIP_INCLUDE_PATH +ENV PKG_CONFIG_PATH="$ROCM_PATH/lib/pkgconfig" + +# FA +ARG FA_REPO=https://github.com/ROCm/flash-attention.git +ARG FA_BRANCH=6387433156558135a998d5568a9d74c1778666d8 +ENV GPU_ARCHS="${PYTORCH_ROCM_ARCH}" + +RUN git clone --recursive ${FA_REPO} \ + && cd flash-attention \ + && git checkout ${FA_BRANCH} \ + && python setup.py install \ + && cd .. \ + && rm -rf flash-attention \ + && rm -rf /root/.cache + +# TransformerEngine +# See: https://rocm.frameworks-nightlies.amd.com/whl-staging/device-all/transformer-engine-rocm-torch/ +ARG TE_VERSION=2.15.0.dev0+rocm7.15.0a20260716.a07e607 + +# Those envs are required for getting the best performance +ENV NVTE_USE_CAST_TRANSPOSE_TRITON=1 +ENV NVTE_CK_IS_V3_ATOMIC_FP32=0 +ENV NVTE_CK_USES_BWD_V3=1 +ENV NVTE_CK_USES_FWD_V3=1 +ENV CK_TILE_FLOAT_TO_BFLOAT16_DEFAULT=2 +ENV NVTE_CK_HOW_V3_BF16_CVT=2 + +RUN pip install \ + pybind11==3.0.4 \ + importlib-metadata==8.7.1 \ + onnxscript==0.7.0 \ + pydantic==2.13.4 \ + nvdlfw_inspect==0.2.2 \ + && rm -rf /root/.cache +RUN pip install \ + --index-url https://rocm.frameworks-nightlies.amd.com/whl-staging/device-all/ \ + --pre \ + --no-build-isolation \ + transformer_engine_rocm_torch==${TE_VERSION} \ + && sed -i 's| mv -n "$_TMP_SO" "$OUTPUT"$| mv -n "$_TMP_SO" "$OUTPUT" 2>/dev/null \|\| true|' \ + /opt/venv/lib/python3.12/site-packages/transformer_engine/lib/ck_jit/ck_jit_compile.sh \ + && rm -rf /root/.cache + +# Flux +ARG FLUX_REPO=https://github.com/ROCm/AMDiffusionBenchmark.git +ARG FLUX_BRANCH=26.4_RC_Patch_torchcodec + +RUN git clone ${FLUX_REPO} \ + && cd AMDiffusionBenchmark \ + && git checkout ${FLUX_BRANCH} \ + && pip install -r requirements.txt \ + && cd .. \ + && rm -rf /root/.cache + +# DLRM +ARG DLRM_REPO=https://github.com/AMD-AGI/DLRMBenchmark.git +ARG DLRM_BRANCH=main + +RUN git clone ${DLRM_REPO} \ + && cd DLRMBenchmark \ + && git checkout ${DLRM_BRANCH} \ + && cd .. \ + && rm -rf /root/.cache + +# torchtune +ARG TORCHTUNE_REPO=https://github.com/pytorch/torchtune.git +ARG TORCHTUNE_BRANCH=b4c98ac2a37f0397d64c22579aed415ce7264db6 + +RUN git clone ${TORCHTUNE_REPO} \ + && if [ "${TORCHTUNE_BRANCH}" != "" ]; then cd torchtune && git checkout ${TORCHTUNE_BRANCH} && cd ..; fi \ + && cd torchtune \ + && git checkout ${TORCHTUNE_BRANCH} \ + && find torchtune/modules/moe/utils.py -type f -print0 | xargs -0 sed -i 's/use_grouped_mm = True/use_grouped_mm = False/g' \ + && pip install . \ + && cd .. \ + && rm -rf torchtune \ + && rm -rf /root/.cache + +# Note: we need to change `pad_inner_dim` default explicitly to address fp8 in torchtune issue +# torchao +ARG TORCHAO_REPO=https://github.com/pytorch/ao.git +ARG TORCHAO_BRANCH=e9c7bead90b840b280f97374308255957108ce47 + +RUN git clone ${TORCHAO_REPO} \ + && cd ao && git checkout ${TORCHAO_BRANCH} \ + && find torchao/float8/config.py -type f -print0 | xargs -0 sed -i 's/pad_inner_dim: bool = False/pad_inner_dim: bool = True/g' \ + && find torchao/csrc/rocm/swizzle/swizzle.cpp -type f -print0 | xargs -0 sed -i 's/if defined(HIPBLASLT_VEC_EXT)/if false/g' \ + && pip install --no-build-isolation . \ + && cd .. \ + && rm -rf ao \ + && rm -rf /root/.cache + +RUN pip install \ + datasets==3.6.0 \ + av==16.0.1 \ + transformers==4.55.0 \ + optree==0.18.0 \ + sympy \ + accelerate==1.9.0 \ + trl==0.21.0 \ + tensorboard==2.20.0 \ + peft \ + scipy \ + einops \ + flask-restful \ + nltk \ + pytest \ + pytest-cov \ + pytest_mock \ + pytest-csv \ + pytest-random-order \ + sentencepiece \ + wrapt \ + zarr==2.18.7 \ + numcodecs==0.12.1 \ + xarray \ + wandb \ + tensorstore==0.1.45 \ + pytest_mock \ + pybind11 \ + tiktoken \ + pynvml \ + huggingface_hub[cli] \ + && python3 -m nltk.downloader punkt_tab \ + && rm -rf /root/.cache + +# Groupped GEMM +ARG GROUPED_GEMM_REPO=https://github.com/caaatch22/grouped_gemm.git +ARG GROUPED_GEMM_BRANCH=rocm + +RUN git clone ${GROUPED_GEMM_REPO} \ + && cd grouped_gemm \ + && git checkout ${GROUPED_GEMM_BRANCH} \ + && git submodule update --init --recursive \ + && pip install --no-build-isolation . \ + && cd .. \ + && rm -rf grouped_gemm \ + && rm -rf /root/.cache + +# Install Causal-Conv1d and its dependencies +ARG CAUSAL_CONV1D_REPO=https://github.com/Dao-AILab/causal-conv1d +ARG CAUSAL_CONV1D_BRANCH=e940ead2fd962c56854455017541384909ca669f + +ENV CAUSAL_CONV1D_FORCE_BUILD=TRUE +ENV MAMBA_FORCE_BUILD=TRUE +ENV HIP_ARCHITECTURES=gfx942,gfx950 + +RUN git clone ${CAUSAL_CONV1D_REPO} causal-conv1d \ + && cd causal-conv1d \ + && git checkout ${CAUSAL_CONV1D_BRANCH} \ + && git show --oneline -s \ + && pip install --no-build-isolation . \ + && cd .. \ + && rm -fr causal-conv1d \ + && rm -rf /root/.cache + +# Mamba +ARG MAMBA_REPO=https://github.com/AndreasKaratzas/mamba.git +ARG MAMBA_BRANCH=enable-primus-hybrid-models + +RUN pip install apache-tvm-ffi==0.1.11 \ + && git clone --branch ${MAMBA_BRANCH} ${MAMBA_REPO} \ + && cd mamba \ + && python setup.py install \ + && cd .. \ + && rm -rf /root/.cache + +# Primus +ARG PRIMUS_REPO=https://github.com/AMD-AGI/Primus.git +# Latest commit on `release/v26.5` branch. Commited on 2026-07-22. +ARG PRIMUS_BRANCH=b511d1b66b0068715308ea9bfe8ba147ea1a3860 +# Note: those envs are required to resolve ther issue around the primus update +# post v26.2 +ENV NVTE_FLASH_ATTN=0 +ENV NVTE_FUSED_ATTN=1 + +RUN git clone --recurse-submodules ${PRIMUS_REPO} \ + && cd Primus \ + && git checkout ${PRIMUS_BRANCH} \ + && git submodule update --init --recursive \ + && pip install -r requirements.txt \ + && cd .. \ + && rm -rf /root/.cache + +# Aiter +ARG AITER_REPO=https://github.com/ROCm/aiter.git +ARG AITER_COMMIT=0f3c58e6edb6754940bcf9fd5f09ccb6f389f52e + +RUN pip uninstall aiter amd-aiter -y \ + && rm -rf aiter \ + && git clone --recursive ${AITER_REPO} \ + && cd aiter \ + && git checkout ${AITER_COMMIT} \ + && git submodule update --init --recursive \ + && PREBUILD_KERNELS=3 pip install --no-cache-dir --use-pep517 . \ + && cd .. \ + && rm -rf aiter + +# Primus-Turbo +ARG TURBO_REPO=https://github.com/AMD-AGI/Primus-Turbo.git +# Latest commit on `main` branch. Commited on 2026-07-20. +ARG TURBO_COMMIT=edc8d2ccb0be4888e80ee7c6e765fd3956026a32 +ENV MAX_JOBS=128 +ENV HCC_AMDGPU_TARGET="gfx942,gfx950" + +RUN git clone ${TURBO_REPO} --recursive \ + && cd Primus-Turbo \ + && git checkout ${TURBO_COMMIT} \ + && git submodule update --init --recursive \ + && pip3 install -r requirements.txt \ + && pip3 install --no-build-isolation . -v \ + && cd .. \ + && rm -rf /root/.cache + +RUN pip install \ + boto3==1.35.42 \ + botocore==1.35.99 \ + && rm -rf /root/.cache + +WORKDIR /workspace/ + +# Install torchrec +RUN pip install --no-deps torchrec \ + && pip install \ + tensordict \ + iopath \ + torchmetrics==1.0.3 \ + git+https://github.com/mlperf/logging.git \ + --extra-index-url https://rocm.nightlies.amd.com/whl-multi-arch \ + && rm -rf /root/.cache + +# Install FBGEMM +ARG FBGEMM_REPO=https://github.com/pytorch/FBGEMM.git +# Latest known working commit on the `main` branch. Committed on 2026-06-17. +ARG FBGEMM_COMMIT=80bd3c077dc41b55cd16ed4dcad15cf7c1c1d76a +ENV BUILD_ROCM_VERSION='7.14' + +RUN apt update \ + && apt install -y libtbb-dev \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +RUN git clone ${FBGEMM_REPO} \ + && cd FBGEMM \ + && git checkout ${FBGEMM_COMMIT} \ + && cd fbgemm_gpu \ + && git clean -dfx \ + && git submodule sync \ + && git submodule update --init --recursive \ + && pip install -r requirements.txt \ + && pip install setuptools==75.1.0 \ + && python setup.py install \ + --build-variant=rocm \ + --build-target=default \ + -DAMDGPU_TARGETS=$PYTORCH_ROCM_ARCH \ + -DHIP_ROOT_DIR=$ROCM_PATH \ + -DCMAKE_C_FLAGS="-DTORCH_USE_HIP_DSA" \ + -DCMAKE_CXX_FLAGS="-DTORCH_USE_HIP_DSA" \ + && cd ../.. \ + && rm -rf /root/.cache + +# Install numactl +RUN apt update \ + && apt install -y numactl \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# This is improtant to unset and reset the arc so aiter can +# pick up the correct architecture +ENV GPU_ARCHS=native + +RUN apt update \ + && apt install -y \ + libz3-dev \ + pciutils \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# Uninstall tilelang to avoid dependency issues +# tilelang is installed as part of mamba-ssm package: https://github.com/state-spaces/mamba/blob/0048fbf2e7b2f214dcbe703ea3dec2b9647595e1/pyproject.toml#L20 +RUN pip uninstall -y tilelang + +####### AINIC related installations ####### +RUN apt update \ + && apt install -y \ + libibverbs-dev \ + jq \ + dpkg-dev \ + kmod \ + xz-utils \ + ibverbs-utils \ + infiniband-diags \ + rdma-core \ + ethtool \ + libevent-dev \ + libhwloc-dev \ + libmunge-dev \ + software-properties-common \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# Install AMD AINIC library +ARG AINIC_BUNDLE_VERSION="1.117.5-a-77" + +RUN add-apt-repository -y "deb https://repo.radeon.com/amdainic/pensando/ubuntu/${AINIC_BUNDLE_VERSION} noble main" \ + && apt update --allow-insecure-repositories \ + && apt install -y --allow-unauthenticated libionic-dev \ + && apt clean \ + && rm -rf /var/lib/apt/lists/* + +# Allow insecure repositories by default +RUN echo 'Acquire::AllowInsecureRepositories "true";' >> /etc/apt/apt.conf.d/99allow-insecure-repositories + +# Install UCX +ARG UCX_VERSION="1.18.0" + +RUN wget https://github.com/openucx/ucx/releases/download/v${UCX_VERSION}/ucx-${UCX_VERSION}.tar.gz \ + && mkdir -p ucx-${UCX_VERSION} \ + && tar -zxf ucx-${UCX_VERSION}.tar.gz -C ucx-${UCX_VERSION} --strip-components=1 \ + && cd ucx-${UCX_VERSION} \ + && mkdir build \ + && cd build \ + && ../configure --prefix=/workspace/ucx-${UCX_VERSION}/install --with-rocm=${ROCM_PATH} \ + && make -j 16 \ + && make install \ + && cd ../.. \ + && rm ucx-${UCX_VERSION}.tar.gz + +ENV UCX_INSTALL_DIR=/workspace/ucx-${UCX_VERSION}/install + +# Install OpenMPI +ARG MPI_VERSION="4.1.6" + +RUN wget https://download.open-mpi.org/release/open-mpi/v$(echo "${MPI_VERSION}" | cut -d. -f1-2)/openmpi-${MPI_VERSION}.tar.gz \ + && mkdir -p ompi-${MPI_VERSION} \ + && tar -zxf openmpi-${MPI_VERSION}.tar.gz -C ompi-${MPI_VERSION} --strip-components=1 \ + && cd ompi-${MPI_VERSION} \ + && mkdir build \ + && cd build \ + && ../configure --prefix=/opt/openmpi --with-ucx=${UCX_INSTALL_DIR} --disable-oshmem --disable-mpi-fortran \ + && make -j 16 \ + && make install \ + && cd ../.. \ + && rm openmpi-${MPI_VERSION}.tar.gz \ + && rm -rf ompi-${MPI_VERSION} + +ENV PATH="/opt/openmpi/bin:${PATH}" +ENV LD_LIBRARY_PATH="/opt/openmpi/lib:${LD_LIBRARY_PATH}" + +####### End of AINIC related installations ####### + +# MLPerf related changes +ARG TRAINING_RESULTS_REPO=https://github.com/mlcommons/training_results_v6.0.git +# Latest commit on `main` branch. Commited on 2026-06-15. +ARG TRAINING_RESULTS_COMMIT=eabf23a07b2a0c60a289ff871dc3a46fff0d0421 + +RUN git clone ${TRAINING_RESULTS_REPO} --recursive \ + && cd training_results_v6.0 \ + && git checkout ${TRAINING_RESULTS_COMMIT} \ + && pip install AMD/benchmarks/llama31_8b/implementations/MI355X_EPYC_9575F_primus/primus_mllog-0.1.21-py3-none-any.whl \ + && cd .. \ + && rm -rf training_results_v6.0 \ + && rm -rf /root/.cache + +# Some secrets could leak from these files. Explicitly clean them out. +RUN rm -f /workspace/Megatron-LM/.gitlab/stages/00.pre.yml \ + /workspace/Primus/third_party/Megatron-LM/.gitlab/stages/00.pre.yml + +# Secret scan will complain those files to be secrets. Explicitly remove them. +RUN rm -f /workspace/Primus/.git/packed-refs /workspace/Primus/.git/modules/third_party/**/packed-refs + +# Clean cache +RUN rm -rf /root/.cache + +# Clean build files +RUN rm -rf /*.whl \ + && rm -rf /root/.triton \ + && rm -rf /workspace/aiter/aiter/jit/build \ + && rm -rf /workspace/TransformerEngine + +# Training docker manifest +ARG GIT_COMMIT_TAG=DEV +ARG DOCKERFILE_PATH=Dockerfile + +# Copy patch files for training docker versioning +RUN mkdir -p /workspace/.manifest \ + && env > /workspace/.manifest/env.txt \ + && pip list > /workspace/.manifest/requirements.txt \ + && dpkg -l > /workspace/.manifest/dpkg-list.txt \ + && echo "${GIT_COMMIT_TAG}" > /workspace/.manifest/training_docker_version + +COPY ${DOCKERFILE_PATH} /workspace/.manifest/Dockerfile + +CMD ["/usr/bin/bash"] diff --git a/.github/workflows/docker/Dockerfile b/.github/workflows/docker/Dockerfile index 84f7c5f39..89c25d508 100644 --- a/.github/workflows/docker/Dockerfile +++ b/.github/workflows/docker/Dockerfile @@ -1,10 +1,10 @@ -ARG BASE_IMAGE=docker.io/rocm/primus:v26.3 +ARG BASE_IMAGE=docker.io/rocm/primus:v26.4 FROM ${BASE_IMAGE} ARG PRIMUS_TURBO_COMMIT ARG PRIMUS_TURBO_AITER_COMMIT ARG PRIMUS_TURBO_FRAMEWORK -ARG ROCSHMEM_COMMIT +# ARG ROCSHMEM_COMMIT # only needed if the rocSHMEM rebuild below is restored ARG UCCL_COMMIT ARG TRITON_COMMIT # Non-interactive APT @@ -21,34 +21,25 @@ RUN rm -rf /var/lib/apt/lists/* # --------------------------------------------------------------------------- # Enviroment variables # --------------------------------------------------------------------------- -ENV ROCSHMEM_HOME=/opt/rocshmem -ENV UCX_HOME=/opt/ucx -# ENV MPI_HOME=/opt/ompi -# Use the system OpenMPI prefix from the v26.3 base image. -ENV MPI_HOME=/usr/lib/x86_64-linux-gnu/openmpi -ENV ROCM_HOME=/opt/rocm ENV PRIMUS_TURBO_FRAMEWORK=${PRIMUS_TURBO_FRAMEWORK} -# ENV PATH="/opt/ompi/bin:/opt/ompi/sbin:${PATH}" -# ENV LD_LIBRARY_PATH="/opt/ompi/lib:${LD_LIBRARY_PATH}" ENV GPU_ARCHS="gfx942;gfx950" ENV PYTORCH_ROCM_ARCH="gfx942;gfx950" ENV HCC_AMDGPU_TARGET="gfx942,gfx950" -# --------------------------------------------------------------------------- -# Install rocSHMEM -# --------------------------------------------------------------------------- -RUN mkdir -p /opt && cd /opt && \ - git clone https://github.com/ROCm/rocSHMEM.git && \ - cd rocSHMEM && \ - git checkout ${ROCSHMEM_COMMIT} && \ - mkdir build && \ - cd build && \ - MPI_ROOT=${MPI_HOME} UCX_ROOT=${UCX_HOME} INSTALL_PREFIX=${ROCSHMEM_HOME} ../scripts/build_configs/gda \ - -DGDA_IONIC=ON \ - -DGDA_MLX5=ON \ - -DGDA_BNXT=ON \ - -DUSE_IPC=ON -RUN rm -rf /opt/rocSHMEM + +# rocSHMEM: used by Primus ODC via Primus-Turbo's odc_rocshmem_* extensions. +# This base image already ships a prebuilt install (commit 17ff985c, version +# 3.2.0) at $ROCSHMEM_HOME=/opt/rocshmem, so building it again is redundant. +# +# If a future base image drops it, restore by uncommenting below (and the +# `ARG ROCSHMEM_COMMIT` above). Note: cmake reads the ROCm version from +# -DROCM_PATH=, not $ROCM_PATH; omitting it falls back to the nonexistent +# /opt/rocm and fails. +# RUN mkdir -p /opt && cd /opt && git clone https://github.com/ROCm/rocSHMEM.git && \ +# cd rocSHMEM && git checkout ${ROCSHMEM_COMMIT} && mkdir build && cd build && \ +# MPI_ROOT=${MPI_HOME} UCX_ROOT=${UCX_HOME} INSTALL_PREFIX=${ROCSHMEM_HOME} \ +# ../scripts/build_configs/gda -DROCM_PATH=${ROCM_PATH} \ +# -DGDA_IONIC=ON -DGDA_MLX5=ON -DGDA_BNXT=ON -DUSE_IPC=ON # --------------------------------------------------------------------------- # Install Primus-Turbo @@ -73,18 +64,41 @@ RUN cd /opt && \ RUN rm -rf /opt/Primus-Turbo +# Primus-Turbo links librocshmem.a with -l: (not --whole-archive) under +# -fgpu-rdc/--hip-link, so rocSHMEM's team.cpp.o (which defines the plain, +# non-namespaced rocshmem::ROCSHMEM_TEAM_WORLD symbol) never gets pulled into +# libprimus_turbo_kernels.so; since it's a shared object the missing symbol +# only surfaces as an ImportError at dlopen/import time, not at build time. +# Work around it by re-exporting that symbol from a tiny shim .so, placed on +# libprimus_turbo_kernels.so's existing rpath and wired in via patchelf. +RUN PT_LIB=$(python3 -c "import primus_turbo, os; print(os.path.join(os.path.dirname(primus_turbo.__file__), 'lib', 'libprimus_turbo_kernels.so'))") && \ + mkdir -p /tmp/rocshmem_team_shim && cd /tmp/rocshmem_team_shim && \ + ar x ${ROCSHMEM_HOME}/lib/librocshmem.a team.cpp.o && \ + g++ -shared -fPIC -o ${ROCSHMEM_HOME}/lib/librocshmem_team_shim.so team.cpp.o \ + -L${ROCSHMEM_HOME}/lib -l:librocshmem.a -L${MPI_HOME}/lib -l:libmpi.so \ + -Wl,-rpath,${ROCSHMEM_HOME}/lib -Wl,-rpath,${MPI_HOME}/lib && \ + patchelf --add-needed librocshmem_team_shim.so "${PT_LIB}" && \ + cd / && rm -rf /tmp/rocshmem_team_shim + # --------------------------------------------------------------------------- # Install Triton # --------------------------------------------------------------------------- RUN cd /opt && \ - git clone -b release/3.7.x https://github.com/triton-lang/triton.git && \ + git clone https://github.com/triton-lang/triton.git && \ cd triton && \ git checkout ${TRITON_COMMIT} && \ - pip3 install ninja cmake && \ - pip3 install --no-build-isolation -v . + pip3 install -r python/requirements.txt && \ + MAX_JOBS=96 pip3 install --no-build-isolation --force-reinstall --no-deps . RUN rm -rf /opt/triton +# torch's dist-info still pins the base image's stock triton build; without this, +# any later "pip install" (e.g. CI unit tests) sees torch's triton requirement as +# unsatisfiable and silently upgrades torch from PyPI, dragging in a CUDA stack. +RUN TORCH_META=$(python3 -c "import importlib.metadata as m; print(m.distribution('torch')._path / 'METADATA')") && \ + TRITON_VER=$(python3 -c "import importlib.metadata as m; print(m.version('triton'))") && \ + sed -i -E "s/^Requires-Dist: triton==.*/Requires-Dist: triton==${TRITON_VER}/" "${TORCH_META}" + # --------------------------------------------------------------------------- # Install UCCL-EP (skip for JAX framework) # --------------------------------------------------------------------------- @@ -100,12 +114,14 @@ RUN if [ "$PRIMUS_TURBO_FRAMEWORK" != "JAX" ]; then \ rm -rf /opt/uccl; \ fi -# --------------------------------------------------------------------------- -# Install fixed origami (rocm-libraries@223648a) over the base image's bundled -# 0.1.0. The bundled origami's rank_configs() raises -# `ValueError: vector::reserve` during MoE grouped-gemm kernel selection and -# crashes training (turbo's _safe_rank_configs only catches RuntimeError). -# Skipped for JAX. TODO: drop once a base image ships origami with the fix. +# Install origami (rocm-libraries@223648a). The v26.4 base image ships no +# origami at all. primus_turbo's MoE grouped-gemm kernel selection tolerates +# its absence (falls back to a heuristic, no measurable perf loss on gfx942), +# but Primus's performance-projection tool (primus projection performance) +# hard-depends on origami as its only supported GEMM simulation backend and +# has no fallback (see primus/core/projection/simulation_backends/factory.py) +# — dropping this install broke TestProjectionSimulate::* outright. Skipped +# for JAX, where the projection tool isn't used. RUN if [ "$PRIMUS_TURBO_FRAMEWORK" != "JAX" ]; then \ rm -rf /tmp/rocm-libraries && \ git clone --filter=blob:none --no-checkout https://github.com/ROCm/rocm-libraries.git /tmp/rocm-libraries && \ diff --git a/.github/workflows/docker/Dockerfile.ainic b/.github/workflows/docker/Dockerfile.ainic index ca103df2d..5308ae2b9 100644 --- a/.github/workflows/docker/Dockerfile.ainic +++ b/.github/workflows/docker/Dockerfile.ainic @@ -7,12 +7,16 @@ ARG AINIC_BUNDLE_PATH # Non-interactive APT ENV DEBIAN_FRONTEND=noninteractive +# `cmd | tee log` below reports only tee's exit code by default, silently +# swallowing real failures; pipefail makes the RUN fail with `cmd` instead. +SHELL ["/bin/bash", "-o", "pipefail", "-c"] # --------------------------------------------------------------------------- # Install build dependencies # --------------------------------------------------------------------------- +# initramfs-tools: required by the AINIC install.sh below. RUN apt-get update && \ - apt-get install jq dpkg-dev kmod xz-utils \ + apt-get install jq dpkg-dev kmod xz-utils initramfs-tools \ libfmt-dev libboost-all-dev \ libibverbs-dev ibverbs-utils infiniband-diags -y @@ -20,8 +24,18 @@ RUN apt-get update && \ # Enviroment variables # --------------------------------------------------------------------------- ENV WORKDIR=/workspace -ENV ROCM_PATH=/opt/rocm -ENV MPI_PATH=/usr/lib/x86_64-linux-gnu/openmpi +# ROCM_PATH/MPI_HOME already come from the base image; mirror MPI_HOME here +# instead of hardcoding the old v26.3 OpenMPI path. +ENV MPI_PATH=${MPI_HOME} + +# v26.4 moved ROCm to a pip-installed rocm-sdk-devel, so /opt/rocm no longer +# exists, and rocm-sdk-devel/bin/amdclang++ can't find its sibling clang++ +# (now under llvm/bin/). rccl/amd-anp still hardcode both, so symlink them in +# rather than patching those repos. +RUN ln -sfn "${ROCM_PATH}" /opt/rocm && \ + for f in "${ROCM_PATH}"/llvm/bin/clang*; do \ + ln -sf "$f" "${ROCM_PATH}/bin/$(basename "$f")"; \ + done # =============================== Build AINIC Driver =============================== # WARNING: Please ensure the following environment variables are correctly set: diff --git a/.github/workflows/docker/Dockerfile_v25.09_ainic b/.github/workflows/docker/Dockerfile_v25.09_ainic deleted file mode 100644 index d8c5934a7..000000000 --- a/.github/workflows/docker/Dockerfile_v25.09_ainic +++ /dev/null @@ -1,106 +0,0 @@ -# Base image -FROM docker.io/rocm/megatron-lm:v25.9_gfx950 - -# Specify the commit of Primus-Turbo when building: docker build --build-arg PRIMUS_TURBO_COMMIT=xxx .) -ARG PRIMUS_TURBO_COMMIT -ARG AINIC_BUNDLE_PATH - -# Install basic dependencies -RUN apt-get update - -# Clone and install the Primus-Turbo -WORKDIR /opt -RUN mkdir -p /opt && cd /opt && \ - git clone https://github.com/AMD-AGI/Primus-Turbo.git && \ - cd Primus-Turbo && \ - git checkout ${PRIMUS_TURBO_COMMIT} && \ - git submodule update --init --recursive && \ - pip3 install -r requirements.txt && \ - GPU_ARCHS="gfx942;gfx950" pip3 install --no-build-isolation . - -RUN apt-get install --reinstall binutils -y && apt-get install numactl -y - -WORKDIR /opt -ENV WORKDIR=/opt -ENV ROCM_PATH=/opt/rocm - -RUN apt-get update && \ - apt-get install jq dpkg-dev kmod xz-utils \ - libfmt-dev libboost-all-dev \ - libibverbs-dev ibverbs-utils infiniband-diags -y - -# =============================== Build AINIC Driver =============================== -# WARNING: Please ensure the following environment variables are correctly set: -# WARNING: 1. PATH: /usr/sbin must be included. -# WARNING: 2. LD_LIBRARY_PATH: /usr/lib must be included. -# WARNING: If these paths are missing, tools and libraries may not function correctly. -# INFO: Installation completed successfully - -COPY ${AINIC_BUNDLE_PATH}/ainic_bundle_1.117.5-a-56.tar.gz ${WORKDIR} -RUN cd ${WORKDIR} && \ - echo "Building ainic bundle... current directory: ${WORKDIR}" && \ - tar zxf ainic_bundle_1.117.5-a-56.tar.gz && \ - cd ainic_bundle_1.117.5-a-56 && \ - tar zxf host_sw_pkg.tar.gz && \ - cd host_sw_pkg && \ - ./install.sh --domain=user -y 2>&1 | tee log_install.txt && \ - cd ${WORKDIR} && \ - apt-get install -y ./amd/ainic/deb-repo/libionic*.deb - -# =============================== Test AINIC Driver =============================== -# ibv_devices -# rdma link -# ethtool -i enp9s0 -# ibv_devinfo -vv | grep GID - -# =============================== Build UCX =============================== -RUN cd ${WORKDIR} && wget https://github.com/openucx/ucx/releases/download/v1.18.0/ucx-1.18.0.tar.gz && \ - mkdir -p ucx-1.18.0 && \ - tar -zxf ucx-1.18.0.tar.gz -C ucx-1.18.0 --strip-components=1 && \ - cd ucx-1.18.0 && mkdir build && cd build && \ - ../configure --prefix=${WORKDIR}/ucx-1.18.0/install --with-rocm=${ROCM_PATH} 2>&1 | tee log_ucx_configure.txt && \ - make -j 16 2>&1 | tee log_ucx_build.txt && \ - make install && \ - cd ${WORKDIR} - -ENV UCX_INSTALL_DIR=${WORKDIR}/ucx-1.18.0/install - -# =============================== Build MPI =============================== -RUN cd ${WORKDIR} && \ - wget https://download.open-mpi.org/release/open-mpi/v4.1/openmpi-4.1.6.tar.gz && \ - mkdir -p ompi-4.1.6 && \ - tar -zxf openmpi-4.1.6.tar.gz -C ompi-4.1.6 --strip-components=1 && \ - cd ompi-4.1.6 && mkdir build && cd build && \ - ../configure --prefix=${WORKDIR}/ompi-4.1.6/install --with-ucx=${UCX_INSTALL_DIR} \ - --disable-oshmem --disable-mpi-fortran 2>&1 | tee log_mpi_configure.txt && \ - make -j 16 2>&1 | tee log_mpi_build.txt && \ - make install && \ - cd ${WORKDIR} - -ENV MPI_PATH=${WORKDIR}/ompi-4.1.6/install - -# =============================== Build RCCL =============================== -RUN cd ${WORKDIR} && \ - git clone https://github.com/ROCm/rccl.git && \ - cd rccl && git checkout drop/2025-08 && \ - ./install.sh -l --prefix build/ --disable-mscclpp \ - --disable-msccl-kernel --amdgpu_targets="gfx950" 2>&1 | tee log_rccl_install.txt && \ - cd ${WORKDIR} - -ENV RCCL_HOME=${WORKDIR}/rccl - -# =============================== Build AMD ANP =============================== - -RUN cd ${WORKDIR} && git clone https://github.com/rocm/amd-anp.git && \ -cd amd-anp && git checkout tags/v1.1.0-5 && \ -sed -i '5a CFLAGS += --offload-arch=gfx950' ./Makefile && head -10 ./Makefile && \ -make -j 16 RCCL_BUILD=${RCCL_HOME}/build/release \ - MPI_INCLUDE=${MPI_PATH}/include/ \ - MPI_LIB_PATH=${MPI_PATH}/lib/ \ - ROCM_PATH=${ROCM_PATH} 2>&1 | tee log_amd_anp_build.txt - -# Set the default working directory -WORKDIR /opt - -# check the installed Primus-Turbo package -RUN python3 -m pip show primus-turbo || true diff --git a/.github/workflows/release-build-wheel.yml b/.github/workflows/release-build-wheel.yml index 71ae789a2..9727d7c1f 100644 --- a/.github/workflows/release-build-wheel.yml +++ b/.github/workflows/release-build-wheel.yml @@ -149,6 +149,12 @@ jobs: format: spdx-json artifact-name: primus-sbom.spdx.json output-file: dist/primus-sbom.spdx.json + # The "Attach wheel to release" step below is the single place that + # uploads release assets (with clobber semantics it controls). Let + # this action only produce the SBOM as a workflow artifact, so it + # doesn't race that step by also auto-uploading to the release on + # `release` events -- that always fails with "asset already exists". + upload-release-assets: false - name: Attest build provenance uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 diff --git a/.gitignore b/.gitignore index 97c0687f4..48d575a0a 100644 --- a/.gitignore +++ b/.gitignore @@ -6,10 +6,34 @@ dist/ *.egg-info *~ logs +.pip_cache .vscode local/ .gitmodules output experiment -data +/data/* +!primus/backends/diffusion/data/ +!primus/backends/diffusion/data/** +primus/backends/diffusion/data/**/__pycache__/ +primus/backends/diffusion/data/**/*.pyc pp_simulation_result + +# Allow the projection site's breakdown JSON (consumed by the static site / +# GitHub Pages) despite the generic `data` ignore above. +!examples/deepseek-v4/projection/site/data/ +!examples/deepseek-v4/projection/site/data/*.json + +*.log +*.nohup +*.zip + +# Local run artifacts dropped at the repo root. These capture node FQDNs and +# absolute paths from whichever cluster the run happened on, so keep them out +# of the history. +/log +/log.* +/nohup.* +/core.*.gpu +.triton_cache_shared/ +.cursor/ diff --git a/.gitmodules b/.gitmodules index 1d234e782..226dbc1ce 100644 --- a/.gitmodules +++ b/.gitmodules @@ -8,7 +8,7 @@ [submodule "third_party/maxtext"] path = third_party/maxtext url = https://github.com/ROCm/maxtext.git - branch = release/v26.3 + branch = release/v26.5 [submodule "third_party/Emerging-Optimizers"] path = third_party/Emerging-Optimizers url = https://github.com/NVIDIA-NeMo/Emerging-Optimizers.git @@ -16,6 +16,14 @@ [submodule "third_party/Megatron-Bridge"] path = third_party/Megatron-Bridge url = https://github.com/NVIDIA-NeMo/Megatron-Bridge.git +[submodule "third_party/mamba"] + path = third_party/mamba + url = https://github.com/AndreasKaratzas/mamba.git + branch = enable-primus-hybrid-models [submodule "third_party/HummingbirdXT"] path = third_party/HummingbirdXT url = https://github.com/AMD-AGI/HummingbirdXT.git +[submodule "third_party/Automodel"] + path = third_party/Automodel + url = https://github.com/NVIDIA-NeMo/Automodel.git + branch = main diff --git a/LICENSE b/LICENSE index 297acbff2..96a4fb715 100644 --- a/LICENSE +++ b/LICENSE @@ -41,6 +41,10 @@ Primus uses or references the following third-party projects: - License: MIT License (Sea AI Lab, NVIDIA) - Repository: https://github.com/sail-sg/zero-bubble-pipeline-parallelism +5. odc (On-Demand Communication) + - License: MIT License (Sea AI Lab) + - Repository: https://github.com/sail-sg/odc + User must comply with the respective licenses of these third-party projects when using or distributing Primus. -------------------------------------------------------------------------------- diff --git a/README.md b/README.md index 481149035..698034e0f 100644 --- a/README.md +++ b/README.md @@ -12,30 +12,48 @@ ## ✨ Key Features -- **🔄 Multi-Backend Support**: Seamlessly switch between Megatron-LM, TorchTitan, and other training frameworks -- **🚀 Unified CLI**: One command interface for local development, containers, and Slurm clusters ([Docs](./docs/README.md)) -- **⚡ ROCm Optimized**: Deep integration with AMD ROCm stack and optimized kernels from Primus-Turbo -- **📦 Production Ready**: Battle-tested on large-scale training with hundreds of GPUs -- **🔌 Extensible Architecture**: Plugin-based design for easy integration of custom models and workflows -- **🛡️ Enterprise Features**: Built-in fault tolerance, checkpoint management, and monitoring +- **🔄 Multi-Backend Support**: Seamlessly switch between Megatron-LM, TorchTitan, JAX MaxText, Megatron-Bridge, and the diffusion backend under one configuration system +- **🎯 Full Training Lifecycle**: Pretraining, SFT / LoRA post-training ([Docs](./docs/02-user-guide/posttraining.md)), and image / video diffusion training ([Docs](./docs/04-technical-guides/diffusion-models/README.md)) +- **🚀 Unified CLI**: One command interface for local development, containers, and Slurm clusters ([Docs](./docs/02-user-guide/cli-reference.md)) +- **⚡ ROCm Optimized**: Deep integration with AMD ROCm stack, Primus-Turbo kernels, DeepEP, fused [MegaMoE](./docs/04-technical-guides/mega-moe.md), and FP8 / MXFP8 / MXFP4 recipes +- **📐 Plan Before You Train**: [Projection](./docs/02-user-guide/projection.md) estimates parallelism and memory fit before you allocate a cluster, and the [tuning agent](./docs/02-user-guide/tuning-agent.md) searches configs automatically +- **📦 Production Ready**: Battle-tested on large-scale training with hundreds of GPUs; shipped as ROCm Docker images and a pip wheel +- **🔌 Extensible Architecture**: Patch-based backend integration for custom models and workflows ([Docs](./docs/06-developer-guide/extending-backends.md)) +- **🛡️ Enterprise Features**: Built-in fault tolerance, checkpoint management, and monitoring with MLflow / TraceLens --- ## ✅ Supported Models (high level) -- **Megatron-LM**: LLaMA2 / LLaMA3 / LLaMA4 families, DeepSeek-V2/V3, Mixtral-style MoE, and other GPT-style models -- **TorchTitan**: LLaMA3 / LLaMA4, DeepSeek-V3, and related decoder-only architectures -- **MaxText (JAX)**: LLaMA3.x and other MaxText-supported transformer models (subset; see MaxText docs for details) +- **Megatron-LM**: LLaMA2 / LLaMA3.x / LLaMA4 families, DeepSeek-V2 / V3 / V4, Qwen2.5 and Qwen3 (dense and MoE), Mixtral, Grok, GPT-OSS 20B/120B, GLM, Kimi K2, MiniMax, LFM2, plus hybrid and linear-attention stacks (Mamba, Zebra-LLaMA with GDN / KDA) +- **TorchTitan**: LLaMA3.x / LLaMA4, DeepSeek-V3 (16B to 671B), and Qwen3 0.6B to 32B +- **MaxText (JAX)**: LLaMA2 / LLaMA3.x, DeepSeek-V2 16B, Mixtral-8x7B, Grok1, and Qwen3 14B / 30B-A3B (subset; see MaxText docs for details) +- **Megatron-Bridge**: SFT and LoRA post-training for Qwen3 8B/32B, LLaMA3.1 70B, Zebra-LLaMA, and Mamba +- **Diffusion**: Flux.1 (schnell / dev) text-to-image and Wan 2.1 / 2.2 text- and image-to-video -For the full and up-to-date model matrix, see [Supported Models](./docs/backends/overview.md#supported-models). +For the full and up-to-date model matrix, see [Supported Models](./docs/06-developer-guide/model-support-matrix.md). --- ## 🆕 What's New +- **[2026/07/29]** ⚡ **MegaMoE** - FlyDSL-based fused MoE layer that folds expert all-to-all into the grouped GEMMs, plus FP4 grouped GEMM support ([MegaMoE guide](./docs/04-technical-guides/mega-moe.md)) +- **[2026/07/29]** Hybrid linear-attention models: Gated Delta Net (GDN) and Kimi Delta Attention (KDA) on Megatron-LM ([Hybrid models](./docs/04-technical-guides/hybrid-models/README.md)) +- **[2026/07/22]** Backend upgrades: TorchTitan v0.2.2 (PyTorch 2.12) with GPT-OSS, and MaxText v26.5 +- **[2026/07/17]** 🚀 **DeepSeek-V4 training support** - model definition, fused attention/MoE kernels, Muon optimizer, FP8/FP4 recipes, and a projection toolkit ([examples](./examples/deepseek-v4)) +- **[2026/07/16]** 🎨 **Diffusion backend** - Flux.1 image and Wan video training with FP8/MXFP4, FSDP2, and Energon data pipelines ([Diffusion docs](./docs/04-technical-guides/diffusion-models/README.md)) +- **[2026/07/14]** MLPerf Training 6.0 examples on MI355X: Llama2-70B LoRA, Llama3.1-8B, and GPT-OSS-20B ([examples](./examples/mlperf)) +- **[2026/06/15]** [Tuning agent](./docs/02-user-guide/tuning-agent.md) with memory-aware benchmarking for automatic config search +- **[2026/06/08]** Primus is published as a pip wheel with a bundled `primus-cli` ([install](#install-as-a-python-package-pip)) +- **[2026/01/22]** Post-training via Megatron-Bridge - SFT and LoRA workflows ([Post-training](./docs/02-user-guide/posttraining.md)) +- **[2026/01/06]** MXFP4 low-precision training in the Megatron-LM backend, with MXFP8 recipes following in June - **[2025/12/17]** MoE Training Best Practices on AMD GPUs - [MoE Package Blog](https://rocm.blogs.amd.com/software-tools-optimization/primus-moe-package/README.html) - **[2025/11/14]** 🎉 **Primus CLI 1.0 Released** - Unified command-line interface with comprehensive documentation - **[2025/08/22]** Primus introduction [blog](https://rocm.blogs.amd.com/software-tools-optimization/primus/README.html) + +
+Earlier updates + - **[2025/06/18]** Added TorchTitan backend support - **[2025/05/16]** Added benchmark suite for performance evaluation - **[2025/04/18]** Added [Preflight](./primus/tools/preflight/README.md) cluster sanity checker @@ -43,6 +61,8 @@ For the full and up-to-date model matrix, see [Supported Models](./docs/backends - **[2025/04/09]** Extended support for LLaMA2, LLaMA3, DeepSeek-V2/V3 models - **[2025/03/04]** Released Megatron trainer module +
+ --- ## 🚀 Setup & Deployment @@ -91,9 +111,9 @@ primus-cli deps sync --dir ~/.cache/Primus/third_party ```bash # For Megatron-LM and TorchTitan backends - docker pull rocm/primus:v26.3 + docker pull rocm/primus:v26.4 # For MaxText backend - docker pull rocm/jax-training:v26.3 + docker pull rocm/jax-training:maxtext-v26.4-jax0.9.1-te2.12.0 ``` 2. **Clone the repository** @@ -102,7 +122,7 @@ primus-cli deps sync --dir ~/.cache/Primus/third_party git clone --recurse-submodules https://github.com/AMD-AGI/Primus.git cd Primus # checkout the branch for the specific release - git checkout release/v26.3 + git checkout release/v26.4 git submodule update --init --recursive ``` @@ -113,12 +133,12 @@ primus-cli deps sync --dir ~/.cache/Primus/third_party # NOTE: If your config downloads weights/tokenizer from Hugging Face Hub, # you typically need to pass HF_TOKEN into the container. # Run in the Primus repository root directory - ./primus-cli container --image rocm/primus:v26.3 \ + ./primus-cli container --image rocm/primus:v26.4 \ --env HF_TOKEN="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ -- train pretrain --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml ``` -For more detailed usage instructions, see the [CLI User Guide](./docs/cli/PRIMUS-CLI-GUIDE.md). +For more detailed usage instructions, see the [CLI User Guide](./docs/02-user-guide/cli-reference.md). #### Option 2: wheel installation of Primus and run training in container @@ -131,18 +151,18 @@ For more detailed usage instructions, see the [CLI User Guide](./docs/cli/PRIMUS python -m venv primus-env source primus-env/bin/activate # Install Primus - pip install "primus==26.3.1" --no-deps --extra-index-url https://amd-agi.github.io/Primus/simple/ + pip install "primus==26.4.0" --no-deps --extra-index-url https://amd-agi.github.io/Primus/simple/ ``` >**Note**: this will only install the Primus CLI in your virtual environment under the `site-packages` directory, without other dependencies. The third party submodules will be downloaded on the first run. The complete dependencies and training software stack is provided in the AMD published training Docker images. You can use `primus-cli` to launch the training in container from any directory. - >**Note**: If you don't want to use docker container to run training, and want to install the complete dependencies and training software stack on your host machine, please refer to the instruction: [Install training environment on your host machine](docs/install-on-host.md). The automated installation script is under development and will be released soon. + >**Note**: If you don't want to use docker container to run training, and want to install the complete dependencies and training software stack on your host machine, please refer to the instruction: [Install training environment on your host machine](docs/01-getting-started/installation.md#bare-metal-host-setup). The automated installation script is under development and will be released soon. 2. **Run training in container using pip-installed Primus** ```bash - primus-cli container --image rocm/primus:v26.3 \ + primus-cli container --image rocm/primus:v26.4 \ --env HF_TOKEN="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ --volume /path/to/your/data:/data -- --log_file /data/run.log \ -- train pretrain --config /data/your/config.yaml @@ -162,10 +182,10 @@ For more detailed usage instructions, see the [CLI User Guide](./docs/cli/PRIMUS Comprehensive documentation is available in the [`docs/`](./docs/) directory: -- **[Quick Start Guide](./docs/quickstart.md)** - Get started in 5 minutes -- **[Primus CLI User Guide](./docs/cli/PRIMUS-CLI-GUIDE.md)** - Complete CLI reference and usage -- **[CLI Architecture](./docs/cli/CLI-ARCHITECTURE.md)** - Technical design and architecture -- **[Backend Patch Notes](./docs/backends/overview.md)** - Primus-specific backend arguments +- **[Quick Start Guide](./docs/01-getting-started/quickstart.md)** - Get started in 5 minutes +- **[Primus CLI User Guide](./docs/02-user-guide/cli-reference.md)** - Complete CLI reference and usage +- **[CLI Architecture](./docs/06-developer-guide/cli-architecture.md)** - Technical design and architecture +- **[Backend Patch Notes](./docs/06-developer-guide/backend-patch-notes.md)** - Primus-specific backend arguments - **[Full Documentation Index](./docs/README.md)** - Browse all available documentation --- diff --git a/benchmark/kernel/rccl/run_benchmark_b200.sh b/benchmark/kernel/rccl/run_benchmark_b200.sh index f3a97d36b..996d2a549 100644 --- a/benchmark/kernel/rccl/run_benchmark_b200.sh +++ b/benchmark/kernel/rccl/run_benchmark_b200.sh @@ -27,8 +27,8 @@ set -euo pipefail GPUS_PER_NODE=8 # Pre-imported sqsh avoids enroot aufs whiteout errors on ext4 /data filesystem. -# The sqsh is pre-imported on: GPUA806, GPUA817, GPUA818, GPUA863, GPUA7DD, GPUA81E -# If running on other nodes, root must run: enroot import --output /scratch/enroot/data/nvcr+nvidia+nemo+25.11.01.sqsh docker://nvcr.io#nvidia/nemo:25.11.01 +# It must exist on every node in the allocation; where it does not, root must run: +# enroot import --output /scratch/enroot/data/nvcr+nvidia+nemo+25.11.01.sqsh docker://nvcr.io#nvidia/nemo:25.11.01 DOCKER_IMAGE="/scratch/enroot/data/nvcr+nvidia+nemo+25.11.01.sqsh" MASTER_PORT=29500 SCRIPT_DIR="${SLURM_SUBMIT_DIR}" diff --git a/benchmark/kernel/rccl/run_slurm.sh b/benchmark/kernel/rccl/run_slurm.sh index 2a6710147..2e6b52078 100644 --- a/benchmark/kernel/rccl/run_slurm.sh +++ b/benchmark/kernel/rccl/run_slurm.sh @@ -10,7 +10,7 @@ # Usage: # DOCKER_IMAGE= sbatch run_slurm.sh # DOCKER_IMAGE= NNODES=2 PARTITION=my-gpu sbatch run_slurm.sh -# DOCKER_IMAGE=rocm/primus:v26.3 NNODES=2 sbatch -N2 -w smci355-ccs-aus-n04-[25,29] -p Compute-DCPT ./run_slurm.sh +# DOCKER_IMAGE=rocm/primus:v26.4 NNODES=2 sbatch -N2 -w node[01-02] -p my-gpu ./run_slurm.sh # # Environment variables (all optional except DOCKER_IMAGE): # DOCKER_IMAGE Docker image to use (required) @@ -89,7 +89,7 @@ NODE_RANK="${SLURM_NODEID}" # ---- Build short NODE_TAG like: n05-29_n05-33 ---- short_node() { - # input: smci355-ccs-aus-n05-29 -> output: n05-29 + # keep the last two dash-separated fields, e.g. -n05-29 -> n05-29 local h="$1" local n_part id_part n_part="$(echo "$h" | awk -F"-" "{print \$(NF-1)}")" # n05 @@ -179,7 +179,7 @@ docker run --rm \ MEGATRON_PATH=\"\${PRIMUS_ROOT_PATH}/third_party/Megatron-LM\" export PYTHONPATH=\"\${MEGATRON_PATH}:\${PYTHONPATH:-}\" - # Final CSV names you want (no smci prefix, include both nodes) + # Final CSV names (short node tag, no cluster prefix, includes every node) FINAL_ALLREDUCE=\"${OUTPUT_DIR}/allreduce_\${NODE_TAG}.csv\" FINAL_ALLGATHER=\"${OUTPUT_DIR}/allgather_\${NODE_TAG}.csv\" FINAL_REDUCESCATTER=\"${OUTPUT_DIR}/reducescatter_\${NODE_TAG}.csv\" diff --git a/benchmark/kernel/rccl/submit_b200.sh b/benchmark/kernel/rccl/submit_b200.sh deleted file mode 100644 index db2b67097..000000000 --- a/benchmark/kernel/rccl/submit_b200.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -sbatch -w GPUA7DD,GPUA817 run_benchmark_gb200.sh diff --git a/benchmark/kernel/rccl/submit_gb200.sh b/benchmark/kernel/rccl/submit_gb200.sh deleted file mode 100644 index 52e7bf92a..000000000 --- a/benchmark/kernel/rccl/submit_gb200.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -# Slurm -w accepts hostlist patterns; quote so shellcheck does not treat brackets as globs. -sbatch -w 'slurm-compute-node-[0-1]' run_bench_gb200.sh -sbatch -w 'slurm-compute-node-[2-3]' run_bench_gb200.sh -sbatch -w 'slurm-compute-node-[4-5]' run_bench_gb200.sh -sbatch -w 'slurm-compute-node-[6-7]' run_bench_gb200.sh -sbatch -w 'slurm-compute-node-[8-9]' run_bench_gb200.sh -sbatch -w 'slurm-compute-node-[10-11]' run_bench_gb200.sh -sbatch -w 'slurm-compute-node-[12-13]' run_bench_gb200.sh -sbatch -w 'slurm-compute-node-[14-15]' run_bench_gb200.sh -sbatch -w 'slurm-compute-node-[16-17]' run_bench_gb200.sh diff --git a/benchmark/kernel/rccl/submit_pairs.sh b/benchmark/kernel/rccl/submit_pairs.sh deleted file mode 100755 index ba64c93a1..000000000 --- a/benchmark/kernel/rccl/submit_pairs.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -DOCKER_IMAGE="rocm/primus:v26.3" -#DOCKER_IMAGE="docker.gpuperf:5000/aai_2026_training/rocm/primus_megatron:v25.11_gpt_oss_sink" -#DOCKER_IMAGE="docker.gpuperf:5000/gpuperf/primus:v26.1_sinkfa" -NNODES=2 -PARTITION="Compute-DCPT" -SCRIPT="./run_slurm.sh" - -NODELISTS=( - "smci355-ccs-aus-n01-21,smci355-ccs-aus-n01-25" - "smci355-ccs-aus-n01-33,smci355-ccs-aus-n02-21" - "smci355-ccs-aus-n02-25,smci355-ccs-aus-n02-29" - "smci355-ccs-aus-n03-25,smci355-ccs-aus-n03-33" - "smci355-ccs-aus-n04-21,smci355-ccs-aus-n04-25" - "smci355-ccs-aus-n04-29,smci355-ccs-aus-n04-33" - "smci355-ccs-aus-n05-21,smci355-ccs-aus-n05-29" - "smci355-ccs-aus-n05-33,smci355-ccs-aus-n06-25" - "smci355-ccs-aus-n06-33,smci355-ccs-aus-n10-29" -) - -echo "Submitting ${#NODELISTS[@]} jobs..." -export DOCKER_IMAGE NNODES -for nodelist in "${NODELISTS[@]}"; do - echo ">> $nodelist" - sbatch -N "${NNODES}" -w "$nodelist" -p "$PARTITION" "$SCRIPT" -done - -echo "Done." diff --git a/benchmark/megatron/model/benchmark_report.py b/benchmark/megatron/model/benchmark_report.py index c918541a1..64cb7d7d0 100644 --- a/benchmark/megatron/model/benchmark_report.py +++ b/benchmark/megatron/model/benchmark_report.py @@ -13,11 +13,22 @@ ANSI_ESCAPE_PATTERN = re.compile(r"\x1B[@-_][0-?]*[ -/]*[@-~]") ARG_PATTERN = re.compile(r"\]:\s+([a-zA-Z0-9_]+)\s+\.{3,}\s+(.*)$") +# Field order matches the current Megatron training-log line, where the memory +# segment is appended last (after tokens) rather than sitting between elapsed and +# throughput as in the old format. Groups: elapsed inst/avg, tflops inst/avg, +# tokens inst/avg, memory. ITERATION_PATTERN = re.compile( r"iteration\s+\d+/.*?elapsed time per iteration \(ms\): ([\d.]+)/([\d.]+).*?" - r"mem usages: ([\d.]+).*?" - r"throughput per GPU \(TFLOP/s/GPU\): ([\d.]+)/([\d.]+).*?" - r"tokens per GPU \(tokens/s/GPU\): ([\d.]+)/([\d.]+)", + # Compute (TFLOP/s/GPU) accepts both the current "compute per GPU (...): X (avg Y)" + # label and the legacy "throughput per GPU (...): X/Y" label. The space before + # "(avg" is optional (some log sinks drop it). + r"(?:compute|throughput) per GPU \(TFLOP/s/GPU\): ([\d.]+)(?:/|\s*\(avg\s*)([\d.]+)\)?.*?" + # Tokens accepts both the current "tokens/s/GPU inst/harmonic mean: X/Y" label + # and the legacy "tokens per GPU (tokens/s/GPU): X/Y" label. + r"(?:tokens per GPU \(tokens/s/GPU\)|tokens/s/GPU inst/harmonic mean): ([\d.]+)/([\d.]+).*?" + # Memory accepts both the current "hip mem usage/free/total/usage_ratio: X GB/..." + # segment and the legacy "mem usages: X" segment. Only the used value is captured. + r"(?:hip mem usage/free/total/usage_ratio:\s*|mem usages:\s*)([\d.]+)(?:\s*G(?:i)?B)?", re.DOTALL, ) @@ -49,9 +60,9 @@ def parse_last_metrics_from_log(file_path: str) -> Dict[str, float]: last = matches[-1] step_time_s = (float(last[0]) + float(last[1])) / 2000 - mem_usage = float(last[2]) - tflops = max(float(last[3]), float(last[4])) - tokens_per_gpu = (float(last[5]) + float(last[6])) / 2 + tflops = max(float(last[2]), float(last[3])) + tokens_per_gpu = (float(last[4]) + float(last[5])) / 2 + mem_usage = float(last[6]) return { "TFLOP/s/GPU": round(tflops, 2), diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 000000000..c6cf22b0f --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,2 @@ +_build +sphinx/_toc.yml diff --git a/docs/.readthedocs.yaml b/docs/.readthedocs.yaml new file mode 100644 index 000000000..75a0bb1c4 --- /dev/null +++ b/docs/.readthedocs.yaml @@ -0,0 +1,16 @@ +# Read the Docs configuration file +# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details + +version: 2 + +build: + os: ubuntu-24.04 + tools: + python: "3.12" + +sphinx: + configuration: docs/conf.py + +python: + install: + - requirements: docs/sphinx/requirements.txt diff --git a/docs/01-getting-started/README.md b/docs/01-getting-started/README.md new file mode 100644 index 000000000..559b9b52f --- /dev/null +++ b/docs/01-getting-started/README.md @@ -0,0 +1,12 @@ +# Getting started + +Start here if you are new to Primus. + +- [Project overview](overview.md): what Primus does, who it is for, key capabilities +- [Installation guide](installation.md): prerequisites, Docker/bare-metal/Slurm setup +- [Quickstart](quickstart.md): first training run in 5 minutes +- [Glossary](glossary.md): terms, acronyms, and domain concepts + +--- + +[← Documentation home](../README.md) diff --git a/docs/01-getting-started/bare-metal-installation-jax.md b/docs/01-getting-started/bare-metal-installation-jax.md new file mode 100644 index 000000000..c6cf91529 --- /dev/null +++ b/docs/01-getting-started/bare-metal-installation-jax.md @@ -0,0 +1,901 @@ +# Bare-metal installation (JAX / MaxText): build the Primus JAX training stack from source (no Docker) + +This guide explains how to build the **Primus JAX / MaxText training software +stack directly on a host machine**, without using the AMD published JAX training +Docker image. It is intended for users who, for policy or operational reasons, +cannot run containers and need to reproduce the same environment on bare metal. + +It is derived from the official JAX training `Dockerfile` and +installs the same components and versions. Wherever possible, everything is +installed **inside a Python virtual environment and without `sudo`**. The only +steps that require root are a small set of OS-level system libraries (installed +with `apt`), and a couple of optional networking packages used for multi-node +training. + +> **Looking for the PyTorch/Megatron/TorchTitan stack instead?** See +> [bare-metal-installation.md](bare-metal-installation.md). This document is the +> JAX **MaxText** counterpart. It is leaner than the PyTorch stack — no Flash +> Attention / aiter / Primus-Turbo / FBGEMM / rocSHMEM builds — but v26.5 still +> compiles **TensorFlow (CPU) and RCCL from source** (and TransformerEngine from +> source on hosts with glibc < 2.38), so it is not build-free. + +> **Python 3.12+ required.** MaxText requires Python ≥ 3.12 (the reference image +> is built on Ubuntu 24.04). Unlike the PyTorch recipe, Python 3.10 is **not** +> sufficient here. + +> **⚠️ Host OS: Ubuntu 24.04 / glibc ≥ 2.38 strongly recommended.** The prebuilt +> `transformer_engine_rocm_jax` wheel (and other JAX training wheels) are built +> against the Dockerfile's `ubuntu:24.04` base — they need **`glibc ≥ 2.38`** and +> **`libstdc++` with `GLIBCXX_3.4.32`** (GCC 13/14). On an older host such as +> **Ubuntu 22.04 (glibc 2.35)** the TransformerEngine shared library fails to +> load with `version 'GLIBC_2.38' not found` (and this failure is *silently* +> swallowed by the launcher — training just exits right after JAX initializes +> the GPUs). `libstdc++` can be side-loaded via `LD_LIBRARY_PATH`, but **glibc +> cannot**. For a **manual** install on Ubuntu 22.04 you must either (a) run on a +> 24.04 host, or (b) build TransformerEngine **from source** against the host +> toolchain — see +> [Section 3.7](#37-install-transformerengine-jax-from-the-prebuilt-rocm-wheel). +> Check your host with `ldd --version`. +> +> **The automated `setup.sh` does (b) for you:** its `te` stage detects the host +> glibc and, when it is < 2.38, transparently builds TransformerEngine from +> source instead of installing the prebuilt wheel — so `bash setup.sh` works on +> both Ubuntu 22.04 and 24.04. Only the prebuilt TE wheel needs glibc ≥ 2.38; the +> ROCm JAX/PJRT wheels load fine on glibc 2.35. + +--- + +## Quick path: automated install scripts + +If you just want the environment built for you, use the helper scripts in +[tools/installation-jax/](https://github.com/AMD-AGI/Primus/tree/main/tools/installation-jax). +They automate everything in Section 3 (Python venv) of this guide — venv +creation, the ROCm release tarball, MaxText and its dependencies, TensorFlow +(built from source), JAX + ROCm PJRT/plugin, TransformerEngine, RCCL (built from +source), and Primus itself — and provide a single `env.sh` to activate the +environment before each job. Read the rest of this document if you want to +understand or customize what they do, or if you need the multi-node networking +stack (Section 4), which the scripts do not build. + +There are two files: + +- **env.sh** — defines the install location and exports every environment + variable the build and runtime need (ROCm paths, `NVTE_*` flags, `XLA_FLAGS`, + `MAXTEXT_PATH`, cache locations, etc.). Source it both during the build and + every time you use the environment. +- **setup.sh** — runs the install in re-runnable **stages**. It sources `env.sh` + automatically. + +### Choose where it installs (important) + +Everything lives under `PRIMUS_JAX_BASE` (venv, kept checkouts), with transient +build sources on `SRC_DIR` (defaults to local `/tmp` for fast I/O). +**`PRIMUS_JAX_BASE` is required — there is no default**, so set it to a directory +you can write to that has tens of GB free (`env.sh` errors out if it is unset): + +```bash +export PRIMUS_JAX_BASE=/path/to/big/disk/primus-jax-env # venv + checkouts (persistent) +export SRC_DIR=/tmp/primus-jax-build # transient sources (optional override) +``` + +The scripts **auto-detect your GPU architecture** (`env.sh` reads `rocminfo`, or +falls back to the kernel KFD sysfs `gfx_target_version` when no ROCm is +installed yet), and install the matching device wheels — `gfx942` (MI300X/MI325X), +`gfx950` (MI350X/MI355X), or both. To force a target, export it before running: + +```bash +export PYTORCH_ROCM_ARCH="gfx942;gfx950" +``` + +> `PYTORCH_ROCM_ARCH` is the variable name the ROCm SDK and TransformerEngine +> read to select gfx targets — it applies to the JAX build too, despite the name. + +**If your default `python3` is older than 3.12** (e.g. Ubuntu 22.04 ships 3.10), +you do **not** need `sudo` or a PPA. The scripts use [`uv`](https://docs.astral.sh/uv/) +to provide Python 3.12: + +- If `uv` is already installed, `env.sh` auto-detects a uv-managed + `>= 3.12` interpreter, and `setup.sh` runs `uv python install 3.12` + automatically when none is present yet. Just run `bash setup.sh`. +- If `uv` is not installed, install it once (no root) and re-run: + + ```bash + python3 -m pip install --user uv # or: curl -LsSf https://astral.sh/uv/install.sh | sh + bash setup.sh # setup.sh will fetch Python 3.12 via uv + ``` + +To force a specific interpreter instead, export it before running: + +```bash +export PRIMUS_PYTHON=/path/to/python3.12 +``` + +### Build the environment + +```bash +cd tools/installation-jax + +bash setup.sh # run all default stages, in order +bash setup.sh --list # list available stages +bash setup.sh te # re-run a single stage (e.g. reinstall TransformerEngine) +bash setup.sh venv rocm jax # run a subset of stages +``` + +Stages are idempotent and re-runnable, so if a step fails you can fix the cause +and re-run just that stage. On failure the script stops immediately and prints +which stage failed. + +Default stages (v26.5): + +``` +venv → rocm → maxtext → tf_source → jax → te → primus → jaxreqs → rccl → manifest +``` + +This mirrors the v26.5 image, which **builds TensorFlow (2.21 CPU) and RCCL from +source** (`tf_source`, `rccl`). Those are heavy: the TF bazel build alone is +~30–60 min. Lighter/alternative stages: + +- `tf_cpu_fix` — pip `tensorflow-cpu` instead of the `tf_source` bazel build. +- `te_source` — force the from-source TransformerEngine build regardless of + glibc. You normally don't need to pass this: the default `te` stage + **auto-falls-back** to a from-source build on glibc < 2.38 hosts (e.g. Ubuntu + 22.04), where the prebuilt wheel won't load (see the host-OS note above and + Section 3.7). Heavy (~30–60 min). + +```bash +# Ubuntu 22.04 (glibc < 2.38): `bash setup.sh` already builds TE from source +# automatically. If you also want to skip the heavy tf_source bazel build, swap +# in the lighter tf_cpu_fix: +bash setup.sh venv rocm maxtext tf_cpu_fix jax te primus jaxreqs rccl manifest +``` + +### Use the environment for a training job + +```bash +# Use the SAME PRIMUS_JAX_BASE you built with +export PRIMUS_JAX_BASE=/path/to/big/disk/primus-jax-env +source tools/installation-jax/env.sh # activates the venv + sets ROCm/NVTE/XLA vars + +python -c "import jax; print('devices:', jax.devices())" + +# Primus is checked out under $WORKSPACE_DIR +cd "$WORKSPACE_DIR/Primus" +./primus-cli direct -- train pretrain \ + --config examples/maxtext/configs/MI300X/llama2_7B-pretrain.yaml +``` + +> Pick the config directory that matches your GPU: `examples/maxtext/configs/MI300X/` +> for **gfx942** (MI300X/MI325X) and `examples/maxtext/configs/MI355X/` for +> **gfx950** (MI350X/MI355X), e.g. +> `--config examples/maxtext/configs/MI355X/llama2_7B-pretrain.yaml`. + +### What the scripts do NOT do + +- **System (`apt`) packages** (Section 2): skipped — they need root. A C++ + compiler (`g++`/`make`), `git`, and the build basics must already be present, + along with the small extra set MaxText expects (`numactl`, `curl`, `lsb-release`, …). +- **Multi-node networking** (Section 4: UCX, OpenMPI, AINIC): not built. + Single-node training works without them; follow Section 4 manually if you need + distributed-over-RDMA. +- **gcsfuse**: only needed to mount Google Cloud Storage buckets for data. Not + required for synthetic-data or local-data runs. + +The scripts also adapt a few host-specific details beyond the Dockerfile (the +Python part of MaxText's `setup.sh` is run directly to skip its `apt`/interactive +steps, and the ROCm/MaxText/Primus checkouts are collapsed onto a single +`MAXTEXT_PATH`). See +[tools/installation-jax/README.md](https://github.com/AMD-AGI/Primus/blob/main/tools/installation-jax/README.md) +for the full rationale. + +--- + +## 0. The key idea: ROCm comes from a tarball, not from a system install + +This build **does not require a system-wide ROCm installation**. In v26.5 ROCm +is delivered as a **release tarball** (AMD "TheRock" multi-arch dist) that is +extracted into a user-writable directory (`$ROCM_DIR`, default +`$PRIMUS_JAX_BASE/rocm`) — no `/opt/rocm`, no root: + +- The tarball (`repo.amd.com/rocm/tarball-multi-arch/therock-dist-linux-multiarch-7.14.0.tar.gz`) + provides the full ROCm toolchain (HIP, hipBLASLt, compilers, headers, + libraries). `ROCM_PATH` points at the extraction dir. +- `jax` + `jaxlib` (upstream) plus the ROCm `jax_rocm7_pjrt` and + `jax_rocm7_plugin` wheels (from `repo.amd.com/rocm/whl-multi-arch/`) provide + GPU-accelerated JAX built against that ROCm. + +> The pinned **release tarball** is stable and avoids any pip version-skew that +> could break hipBLASLt GEMMs. RCCL is then rebuilt from source (see Section 3.11) +> and dropped into this ROCm tree. + +This means almost the entire stack can be installed **without root** into a +venv. The only host-level requirements from the system administrator are: + +- The **AMD GPU kernel driver (amdgpu / ROCm KMD)** must already be installed and + loaded (`/dev/kfd` and `/dev/dri` must exist, and the user must be in the + `video` and `render` groups). +- A small set of **build/runtime system libraries** (see Section 2). + +--- + +## 1. Required software stack for JAX MaxText training + +The complete environment is composed of the following layers: + + +| Layer | Component | Source | Needs root? | +| ----------------------- | ----------------------------------------------------------------- | ------------------------- | ------------------------- | +| Kernel / hardware | AMD GPU driver (amdgpu KMD), GPU device access | OS / admin | Yes (one-time, by admin) | +| OS libraries | Build toolchain + runtime libs (`g++`, `git`, `numactl`, RDMA, …) | `apt` | Yes (one-time) | +| ROCm user-space | TheRock ROCm dist (HIP, hipBLASLt, compilers, libs) | **release tarball** ($ROCM_DIR) | No (user dir) | +| Deep learning framework | JAX (`jax`, `jaxlib`) + ROCm `jax_rocm7_pjrt` / `jax_rocm7_plugin`| pip (upstream + repo.amd.com) | No (venv) | +| Accelerated kernels | TransformerEngine (JAX) — prebuilt ROCm wheel (or from source) | pip (staging index) / build | No (venv) | +| Training framework | MaxText (ROCm fork) + its Python deps | git + pip (`uv`) | No (venv) | +| Collectives fix | `tensorflow-cpu` 2.21 (built from source; no bundled NCCL/LLVM) | build from source (bazel) | No (venv) | +| Collectives lib | RCCL (rebuilt from source into the ROCm tree) | build from source | No (user dir) | +| Multi-node comms | UCX, OpenMPI, AMD AINIC (libionic) | build from source / apt | Mostly no (AINIC needs root) | +| Primus | Primus + `third_party/maxtext` submodule | git + pip | No (venv) | + + +### 1.1 Version requirements (pins and host prerequisites) + +These are the exact versions the v26.5 reference `Dockerfile` pins. The install +scripts use the same pins; change one and you may have to change the others. + +| Component | Pinned version / source | Notes | +| --------------------------------- | ----------------------------------------------------------------------- | ----- | +| ROCm (TheRock dist tarball) | `therock-dist-linux-multiarch-7.14.0.tar.gz` | Multi-arch (gfx942 + gfx950). Extracted into `$ROCM_DIR`. | +| JAX / jaxlib | `0.10.0` | Upstream PyPI. | +| ROCm PJRT / plugin | `jax_rocm7_pjrt` / `jax_rocm7_plugin` `0.10.0+rocm7.14.0` | From `repo.amd.com/rocm/whl-multi-arch/`. | +| TransformerEngine (JAX) | `transformer_engine_rocm_jax 2.15.0.dev0+rocm7.15.0a20260707.72d01a0` | Prebuilt wheel **needs glibc ≥ 2.38**; else build from source (`te_source`). | +| TensorFlow (CPU, from source) | ROCm `tensorflow-upstream` branch `upstream-v2.21.0` | Built with bazelisk `v1.25.0`. Needs host `clang-18`/`lld-18`. | +| RCCL (from source) | `rocm-systems` @ `9e5e4084a4b8e1e86551b0eb054725c62354a926` | Installed into `$ROCM_PATH/lib`. Needs host `clang-18`/`lld-18`. | +| MaxText (ROCm fork) | `release/v26.5` | 2-value `initialize()`/`run()` API; Primus `main` supports it (fix #912). | +| Primus | `main` | Includes the MaxText `initialize()`/`run()` compatibility shim. | +| scipy | `1.16` | | +| amdsmi | `7.0.2` | pip, after the ROCm tarball. | +| Build front-end | `cmake 3.31.6`, `ninja 1.11.1.3`, `wheel 0.45.1`, `packaging 25.0`, `setuptools 69.5.1` | Plus `uv` (used by MaxText's dep install). | +| TE deps | `pybind11 3.0.4`, `importlib-metadata 8.7.1`, `pydantic 2.13.4`, `flax 0.12.2` | | + +**Host prerequisites** (independent of the pins above): + +| Requirement | Minimum / recommended | Why | +| ---------------------- | ---------------------------------------------- | --- | +| **Python** | **≥ 3.12** — **3.12 recommended/pinned** | MaxText requires ≥ 3.12; the prebuilt TE/JAX wheels are `cp312`, so on a 3.13 venv you must build TE from source. `uv` provides 3.12 with no root. | +| **glibc** | **≥ 2.38** for the prebuilt TE wheel | Ubuntu 24.04 = glibc 2.38+. On **Ubuntu 22.04 (glibc 2.35)** the prebuilt TE wheel won't load — the `te` stage auto-falls-back to a from-source build. Check with `ldd --version`. `glibc` cannot be side-loaded via `LD_LIBRARY_PATH`. | +| **libstdc++ (GCC)** | `GLIBCXX_3.4.32` (GCC 13/14) for prebuilt TE | Can be side-loaded via `LD_LIBRARY_PATH` if glibc itself is new enough. | +| **C/C++ toolchain** | `g++` with C++17; `clang-18`/`lld-18` for the TF/RCCL source builds | Source builds (`tf_source`, `rccl`, `te_source`) need LLVM 18. | +| **GPU arch** | AMD Instinct **gfx942** (MI300/MI325) or **gfx950** (MI350/MI355) | The pinned ROCm tarball + JAX wheels target these. Other archs (e.g. gfx90a/MI250) need matching wheels not pinned here. | +| **GPU driver (KMD)** | amdgpu / ROCm kernel driver loaded | `/dev/kfd` + `/dev/dri` present; user in `video`/`render` groups. | + +> **Validated:** the scripts' default flow (with `te_source` substituted for the +> prebuilt `te`) has been run end-to-end for single-node MaxText pretraining on +> **gfx942 / Ubuntu 22.04 (glibc 2.35) / Python 3.12**. The prebuilt-`te` flow is +> the path for Ubuntu 24.04 hosts. + +For a **distributed (multi-node) JAX MaxText job** specifically, beyond JAX and +ROCm you additionally need: + +- **RCCL** (AMD's collective library) — rebuilt from source into the ROCm tree + (see Section 3.11). This is what MaxText's collectives run over. +- **AMD AINIC / RDMA stack** (`libibverbs`, `rdma-core`, `libionic`) — for + high-performance networking on AMD Pensando NICs. +- Correct GPU/NIC device permissions and (often) hugepages / `ulimit -l unlimited`. +- **UCX + OpenMPI** (Section 4) — **optional for MaxText**; carried over from the + reference image for MPI-launched / other JAX workloads. MaxText itself does not + use them (see the note in Section 4). + +> Unlike the PyTorch stack, the JAX MaxText image does **not** build rocSHMEM, and +> MaxText does not launch via `mpirun`. JAX forms its process group through the +> **JAX distributed coordinator** (`JAX_COORDINATOR_IP`, which Primus sets from +> `MASTER_ADDR`) and runs collectives over **RCCL**. + +--- + +## 2. System packages (require `sudo` / administrator, one-time) + +These are OS-level libraries needed to *build* the rest of the stack and to run +MaxText / RDMA networking. They must be installed by someone with root, but this +is a **one-time** action; everything afterward is done unprivileged in a venv. + +> If you genuinely cannot get root at all, these packages must already be present +> on the host. The remainder of the guide (Sections 3+) then runs entirely +> without root. + +### 2.1 Build toolchain and core libraries + +```bash +sudo apt update +sudo apt install -y \ + gfortran git git-lfs ninja-build g++ pkg-config xxd patchelf \ + automake libtool flex ccache \ + python3-venv python3-dev python3-pip python-is-python3 \ + libegl1-mesa-dev liblzma-dev libdw1 libdrm-dev \ + wget unzip zip +``` + +> **Source builds (v26.5): LLVM 18 toolchain.** The `tf_source` (TensorFlow 2.21 +> bazel) and `rccl` source builds want a host `clang-18`/`lld-18`. The reference +> image adds the LLVM apt repo and installs them: +> +> ```bash +> echo 'deb http://apt.llvm.org/jammy/ llvm-toolchain-jammy-18 main' | sudo tee /etc/apt/sources.list.d/llvm.list +> wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - +> sudo apt update && sudo apt install -y clang-18 lld-18 llvm-18-dev llvm-18-tools +> ``` +> +> (Use `jammy` for Ubuntu 22.04, `noble` for 24.04.) You can skip this if you use +> the lighter `tf_cpu_fix` stage instead of `tf_source`. + +> **Python 3.12+**: MaxText needs Python ≥ 3.12. Ubuntu 24.04 ships 3.12 by +> default. On Ubuntu 22.04 (which ships 3.10) `apt install python3.12` fails +> because jammy has no such package — do **not** rely on it. Two options: +> +> - **Recommended, no sudo — `uv`** (this is what the automated scripts use): +> ```bash +> python3 -m pip install --user uv # or: curl -LsSf https://astral.sh/uv/install.sh | sh +> uv python install 3.12 # downloads a standalone CPython 3.12 (no root) +> export PRIMUS_PYTHON="$(uv python find '>=3.12')" +> ``` +> The manual venv step below then uses `"$PRIMUS_PYTHON" -m venv ...`. +> - **Alternative — deadsnakes PPA** (needs sudo, and the PPA must be reachable +> from your network): +> ```bash +> sudo add-apt-repository ppa:deadsnakes/ppa +> sudo apt update +> sudo apt install -y python3.12 python3.12-venv python3.12-dev +> export PRIMUS_PYTHON=python3.12 +> ``` + +### 2.2 Extra packages MaxText's setup expects + +MaxText's own `setup.sh` installs these via `apt`; on bare metal, pre-install +them once: + +```bash +sudo apt install -y \ + numactl lsb-release gnupg curl net-tools iproute2 procps lsof ethtool +``` + +Optional — only if you read training data from Google Cloud Storage: + +```bash +# gcsfuse (mount GCS buckets) +export GCSFUSE_REPO=gcsfuse-$(lsb_release -c -s) +echo "deb https://packages.cloud.google.com/apt $GCSFUSE_REPO main" | \ + sudo tee /etc/apt/sources.list.d/gcsfuse.list +curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add - +sudo apt update && sudo apt install -y gcsfuse +``` + +### 2.3 RDMA / networking libraries (needed for multi-node training) + +```bash +sudo apt install -y \ + rdma-core libibverbs-dev ibverbs-utils infiniband-diags \ + ethtool kmod dpkg-dev jq xz-utils \ + libevent-dev libhwloc-dev libmunge-dev \ + software-properties-common +``` + +### 2.4 AMD AINIC library (optional, for AMD Pensando NICs) + +This pulls a vendor `.deb` from the AMD radeon repository. Skip it if you are +not using AMD AINIC networking. + +```bash +# Pin to the version used by the reference image +AINIC_BUNDLE_VERSION="1.117.5-a-77" + +sudo add-apt-repository -y \ + "deb https://repo.radeon.com/amdainic/pensando/ubuntu/${AINIC_BUNDLE_VERSION} noble main" +sudo apt update --allow-insecure-repositories +sudo apt install -y --allow-unauthenticated libionic-dev +``` + +--- + +## 3. Build the Python environment (no `sudo` from here on) + +Everything below runs as a regular user inside a virtual environment. + +### 3.1 Create and activate the virtual environment + +```bash +# Pick a stable location, e.g. ~/primus-jax-env. MaxText needs Python >= 3.12. +# On Ubuntu 22.04, get a 3.12 interpreter via uv first (see the Python 3.12+ +# note in Section 2.1): export PRIMUS_PYTHON="$(uv python find '>=3.12')" +"${PRIMUS_PYTHON:-python3.12}" -m venv ~/primus-jax-env +source ~/primus-jax-env/bin/activate + +# Build/runtime knobs (match the Dockerfile) +export MAX_JOBS=128 # lower this if you have fewer CPU cores / less RAM +export PYTORCH_ROCM_ARCH="gfx942;gfx950" # MI300/MI325 = gfx942, MI350/MI355 = gfx950 +export ROCM_AMDGPU_TARGETS="gfx942,gfx950" +``` + +### 3.2 Bootstrap build tooling + +```bash +pip install --upgrade pip +pip uninstall -y wheel +pip install \ + cmake==3.31.6 \ + ninja==1.11.1.3 \ + wheel==0.45.1 \ + packaging==25.0 \ + setuptools==69.5.1 \ + uv +``` + +### 3.3 Workaround environment variables + +```bash +# Avoids HSA_STATUS_ERROR_OUT_OF_RESOURCES on some configurations +export HSA_ENABLE_SCRATCH_ASYNC_RECLAIM=0 +export HSA_NO_SCRATCH_RECLAIM=1 + +# Fix the ROCm profiler hang issue +export ROCPROFILER_QUEUE_INTERPOSITION=0 +export DEBUG_HIP_DYNAMIC_QUEUES=0 +``` + +### 3.4 Install ROCm from the TheRock release tarball + +This step replaces a system ROCm install. v26.5 uses a **release tarball** +(not pip wheels) extracted into a user-writable dir — no `/opt/rocm`, no root. + +```bash +# Extract into a user-writable location (the automated env.sh uses +# $PRIMUS_JAX_BASE/rocm). ROCM_PATH will point here. +export ROCM_DIR=~/primus-jax-env/rocm +mkdir -p "$ROCM_DIR" +wget -O /tmp/therock-dist.tar.gz \ + https://repo.amd.com/rocm/tarball-multi-arch/therock-dist-linux-multiarch-7.14.0.tar.gz +tar -xzf /tmp/therock-dist.tar.gz -C "$ROCM_DIR" + +# amdsmi (installed via pip in the reference image) +pip install amdsmi==7.0.2 +``` + +> The tarball is multi-arch (gfx942 + gfx950), so there is no per-arch package to +> pick, and there is **no pip version-skew to worry about** — a pinned tarball +> cannot drift out of sync and break hipBLASLt GEMMs. + +### 3.5 Export ROCm paths + +Point the rest of the build/runtime at the extracted ROCm. **These must be set +every time you use the environment** — Section 5 shows how to make them +persistent (the automated `env.sh` does all of this for you). + +```bash +export ROCM_PATH=$ROCM_DIR +export ROCM_HOME=$ROCM_PATH +export HIP_PLATFORM=amd +export HIP_PATH=$ROCM_PATH +export HIP_CLANG_PATH=$ROCM_PATH/llvm/bin +export HIP_INCLUDE_PATH=$ROCM_PATH/include +export HIP_LIB_PATH=$ROCM_PATH/lib +export HIP_DEVICE_LIB_PATH=$ROCM_PATH/lib/llvm/amdgcn/bitcode +# The reference Dockerfile puts $ROCM_PATH/lib on PATH too; mirror that. +export PATH="$ROCM_PATH/lib:$ROCM_PATH/bin:$HIP_CLANG_PATH:$PATH" +export LD_LIBRARY_PATH="$ROCM_PATH/lib:$ROCM_PATH/lib/rocm_sysdeps/lib:$ROCM_PATH/lib64:$ROCM_PATH/llvm/lib" +export LIBRARY_PATH="$ROCM_PATH/lib:$ROCM_PATH/lib64" +export CPATH=$HIP_INCLUDE_PATH +export PKG_CONFIG_PATH="$ROCM_PATH/lib/pkgconfig" +``` + +Quick check before continuing: + +```bash +hipcc --version +``` + +### 3.6 Install MaxText, then JAX + the ROCm PJRT/plugin + +> **Order matters (v26.5).** The subsections below are numbered for reference, +> but the correct install order is: **MaxText deps (§3.8) → TensorFlow from +> source (§3.9) → ROCm JAX/PJRT/plugin (§3.6, right here) → TransformerEngine +> (§3.7) → Primus (§3.10) → RCCL from source (§3.11)**. MaxText's `setup.sh` +> pulls in a stock `jax`/`tensorflow`, so the ROCm JAX must be installed *after* +> MaxText (to override it) and *before* TE (or a later step overwrites +> `jaxlib`). The automated `setup.sh` enforces this ordering for you. + +MaxText install is in Section 3.8 (deps) below; the JAX packages are: + +```bash +JAX_VERSION=0.10.0 +JAX_PJRT_VERSION=0.10.0+rocm7.14.0 # https://repo.amd.com/rocm/whl-multi-arch/jax-rocm7-pjrt/ +JAX_PLUGIN_VERSION=0.10.0+rocm7.14.0 # https://repo.amd.com/rocm/whl-multi-arch/jax-rocm7-plugin/ + +pip install jax==${JAX_VERSION} jaxlib==${JAX_VERSION} scipy==1.16 +pip install \ + --index-url https://repo.amd.com/rocm/whl-multi-arch/ \ + --pre jax_rocm7_pjrt==${JAX_PJRT_VERSION} \ + --pre jax_rocm7_plugin==${JAX_PLUGIN_VERSION} +``` + +### 3.7 Install TransformerEngine (JAX) from the prebuilt ROCm wheel + +TransformerEngine is installed as a prebuilt ROCm JAX wheel. Check +[the staging index](https://rocm.frameworks-nightlies.amd.com/whl-staging/device-all/transformer-engine-rocm-jax/) +for the current pin. + +```bash +TE_VERSION=2.15.0.dev0+rocm7.15.0a20260707.72d01a0 + +pip install \ + pybind11==3.0.4 \ + importlib-metadata==8.7.1 \ + pydantic==2.13.4 \ + flax==0.12.2 + +pip install \ + --index-url https://rocm.frameworks-nightlies.amd.com/whl-staging/device-all/ \ + --pre \ + --no-build-isolation \ + transformer_engine_rocm_jax==${TE_VERSION} +``` + +> **The prebuilt wheel needs `glibc ≥ 2.38` (Ubuntu 24.04).** Verify it actually +> loads before continuing: +> +> ```bash +> python -c "import transformer_engine.jax; print('TE JAX OK')" +> ``` +> +> If you see `OSError: ... version 'GLIBC_2.38' not found` (typical on Ubuntu +> 22.04, glibc 2.35), the wheel is incompatible with your host. **Build +> TransformerEngine from source instead** so it links against your host's glibc. +> This is exactly what the automated `te_source` stage does +> (`bash setup.sh ... te_source ...`); the manual equivalent is: +> +> ```bash +> pip uninstall -y transformer_engine transformer_engine_rocm_jax +> export USE_ROCM=1 NVTE_FRAMEWORK=jax NVTE_USE_ROCM=1 +> export NVTE_ROCM_ARCH="${PYTORCH_ROCM_ARCH}" CMAKE_BUILD_PARALLEL_LEVEL=${MAX_JOBS} +> git clone --recursive https://github.com/ROCm/TransformerEngine.git +> cd TransformerEngine +> git checkout 635d7c085c39a6d9bfe4881c7d3efab7a46d7129 # last known-good ROCm JAX TE source commit +> git submodule update --init --recursive +> python3 setup.py bdist_wheel && pip install dist/*.whl +> cd .. +> ``` +> +> If you only hit a `GLIBCXX_3.4.32` (libstdc++) error but glibc is new enough, +> you can instead side-load a newer `libstdc++.so.6` (e.g. extracted from a +> newer distro's `libstdc++6` package) via `LD_LIBRARY_PATH` — no rebuild needed. + +### 3.8 Install MaxText and its dependencies + +Clone the ROCm MaxText fork and install its Python dependencies. The reference +image runs MaxText's `src/dependencies/scripts/setup.sh`; on bare metal we run +the **Python portion** of that script directly (the `apt`/`gcsfuse` steps are the +one-time root action from Section 2, and the venv already exists). + +> **MaxText `release/v26.5` and the Primus API.** MaxText v26.5 uses a +> **2-value** `initialize()`/`run()` API (`config, recorder`). Primus `main` +> handles it: `MaxTextPretrainTrainer` forwards `initialize()`'s tuple verbatim +> to `run()` (fix #912), so v26.5 trains out of the box. Override `MAXTEXT_BRANCH` +> only if you deliberately need to pin a different MaxText release. + +```bash +cd ~/primus-jax-env # or your $WORKSPACE_DIR +git clone https://github.com/ROCm/maxtext.git +cd maxtext +git checkout release/v26.5 # matches the v26.5 image; Primus main supports its 2-value API + +# MaxText installs its deps with uv. The default (tpu) requirements set contains +# the framework-agnostic Python deps WITHOUT any CUDA packages, which is what the +# ROCm image uses. +pip install -U setuptools wheel uv +python -m uv pip install --resolution=lowest \ + -r src/dependencies/requirements/generated_requirements/tpu-requirements.txt +python -m src.dependencies.scripts.install_pre_train_extra_deps +python -m uv pip install --no-deps -e . +cd .. +``` + +> This pulls in the full `tensorflow` package (via `tensorflow-text`); the next +> step swaps it for the CPU build. It may also nudge `jax`/`jaxlib`/`scipy` +> within their allowed ranges — the ROCm PJRT/plugin installed in 3.6 remain in +> place. + +### 3.9 Build TensorFlow (CPU) from source + +v26.5 rebuilds TensorFlow 2.21 (CPU) from ROCm's fork. The stock PyPI TF wheel +bundles an LLVM whose symbols collide with ROCm's `libLLVM` in Grain "spawn" +workers → **SIGSEGV on `import tensorflow` after `import jax`**. A CPU build has +correct symbol visibility and no bundled NCCL (so it also preserves the XLA→RCCL +collective fix). This is a **heavy bazel build (~30–60 min)** and needs a host +`clang`/`lld` (LLVM 18) plus `unzip`/`zip` (Section 2). + +```bash +# bazelisk (to a user-writable location) auto-picks the bazel version TF pins. +wget -O ~/primus-jax-env/bin/bazel \ + https://github.com/bazelbuild/bazelisk/releases/download/v1.25.0/bazelisk-linux-amd64 +chmod +x ~/primus-jax-env/bin/bazel +export PATH="$HOME/primus-jax-env/bin:$PATH" + +git clone --depth 1 --branch upstream-v2.21.0 https://github.com/ROCm/tensorflow-upstream.git +cd tensorflow-upstream +bazel --output_user_root=/tmp/primus-jax-build/bazel build //tensorflow/tools/pip_package:wheel \ + --repo_env=WHEEL_NAME=tensorflow_cpu \ + --repo_env=HERMETIC_PYTHON_VERSION=3.12 +pip uninstall -y tensorflow tensorflow-cpu tensorflow_cpu +pip install --no-deps bazel-bin/tensorflow/tools/pip_package/wheel_house/tensorflow_cpu-2.21.0-cp312-cp312-linux_x86_64.whl +cd .. +``` + +> **Lighter alternative:** if you don't want the bazel build, `pip install +> --no-deps tensorflow-cpu==$(pip show tensorflow | awk '/^Version:/{print $2}')` +> installs a CPU wheel that avoids the bundled-NCCL clash. It may still hit the +> LLVM-symbol SIGSEGV on some ROCm 7.14 configs — the from-source build is the +> robust fix. The automated recipe exposes this as the `tf_cpu_fix` stage. + +### 3.10 Install Primus + +```bash +cd ~/primus-jax-env # or your $WORKSPACE_DIR +git clone --recurse-submodules https://github.com/AMD-AGI/Primus.git +cd Primus +git checkout main +git submodule update --init third_party/maxtext/ + +# The JAX path does not install Primus' torch-oriented requirements.txt. +# Remove stale dataclasses backports that conflict on modern Python: +pip uninstall -y dataclasses dataclasses_json + +# Primus' JAX runtime deps (also installed by the MaxText pre-train hook at +# launch time): +pip install -r requirements-jax.txt +``` + +If you already have a local Primus checkout (e.g. this repository), you can skip +the clone and just run the `git submodule update`, `pip uninstall`, and +`pip install -r requirements-jax.txt` steps from its root. + +> **Which MaxText does Primus run?** At launch, Primus resolves the MaxText +> backend from the `MAXTEXT_PATH` environment variable, falling back to its own +> `third_party/maxtext` submodule. Set `MAXTEXT_PATH` to the checkout you +> installed dependencies into (Section 3.8) so the code and the installed deps +> match — the automated `env.sh` does this for you. + +### 3.11 Build RCCL from source (into the ROCm tree) + +v26.5 rebuilds RCCL from `rocm-systems` and drops the libraries into the ROCm +tree so JAX/XLA collectives use it. Requires the ROCm toolchain (`hipcc`) from +Section 3.4. + +```bash +git clone https://github.com/ROCm/rocm-systems.git +cd rocm-systems +git checkout 9e5e4084a4b8e1e86551b0eb054725c62354a926 +cd projects/rccl +./install.sh -l --prefix build/ --amdgpu_targets="${PYTORCH_ROCM_ARCH}" +cp -r build/release/librccl* "$ROCM_PATH/lib/" +cd ../../.. +``` + +--- + +## 4. Multi-node communication stack (UCX, OpenMPI) + +These are only needed for **multi-node distributed** training. They build from +source and install into user-writable prefixes (no root needed, except the AINIC +`.deb` already handled in Section 2.4). + +> **Does JAX MaxText actually need UCX/OpenMPI?** For MaxText itself, **no** — JAX +> forms its process group through the **JAX distributed coordinator** +> (`JAX_COORDINATOR_IP`/`JAX_COORDINATOR_PORT`, which Primus sets from +> `MASTER_ADDR`/`MASTER_PORT`) and runs collectives over **RCCL**; there is no +> `mpirun` launch and no rocSHMEM. UCX/OpenMPI are carried over from the shared +> reference image (used by MPI-launched / other JAX-based workloads) and are +> installed here only for parity. **You can skip Section 4 entirely for +> single- and multi-node MaxText pretraining.** + +> **Multi-node: make all local GPUs visible to each process.** On each node, +> every rank/process must see all local GPUs, otherwise JAX enumerates only a +> single device per node. Export `CUDA_VISIBLE_DEVICES` covering every local GPU +> (the ROCm PJRT plugin honors the CUDA-named variable) before launching: +> +> ```bash +> export CUDA_VISIBLE_DEVICES=$(seq -s, 0 $((GPUS_PER_NODE - 1))) # e.g. 0,1,2,3,4,5,6,7 +> ``` +> +> Add it to your activation script (Section 5) or your job launcher. It is +> intentionally **not** hard-coded in `env.sh`, since the right value depends on +> how ranks are pinned to GPUs on your host/scheduler. + +### 4.1 UCX + +```bash +cd ~/primus-jax-env +UCX_VERSION="1.18.0" +wget https://github.com/openucx/ucx/releases/download/v${UCX_VERSION}/ucx-${UCX_VERSION}.tar.gz +mkdir -p ucx-${UCX_VERSION} +tar -zxf ucx-${UCX_VERSION}.tar.gz -C ucx-${UCX_VERSION} --strip-components=1 +cd ucx-${UCX_VERSION} +mkdir build && cd build +../configure --prefix=$HOME/primus-jax-env/ucx-${UCX_VERSION}/install --with-rocm=${ROCM_PATH} +make -j 16 && make install +cd ../.. + +export UCX_INSTALL_DIR=$HOME/primus-jax-env/ucx-${UCX_VERSION}/install +``` + +### 4.2 OpenMPI + +```bash +MPI_VERSION="4.1.6" +wget https://download.open-mpi.org/release/open-mpi/v$(echo "${MPI_VERSION}" | cut -d. -f1-2)/openmpi-${MPI_VERSION}.tar.gz +mkdir -p ompi-${MPI_VERSION} +tar -zxf openmpi-${MPI_VERSION}.tar.gz -C ompi-${MPI_VERSION} --strip-components=1 +cd ompi-${MPI_VERSION} +mkdir build && cd build +# Install to a user-writable prefix instead of /opt to avoid sudo +../configure --prefix=$HOME/primus-jax-env/openmpi --with-ucx=${UCX_INSTALL_DIR} \ + --disable-oshmem --disable-mpi-fortran +make -j 16 && make install +cd ../.. + +export PATH="$HOME/primus-jax-env/openmpi/bin:${PATH}" +export LD_LIBRARY_PATH="$HOME/primus-jax-env/openmpi/lib:${LD_LIBRARY_PATH}" +``` + +> The Dockerfile installs OpenMPI under `/workspace`. The `$HOME/...` prefixes +> above keep it unprivileged. + +--- + +## 5. Make the environment reproducible (activation script) + +Many of the variables above (especially the ROCm paths and the `NVTE_*` / `XLA_*` +runtime flags) must be present in **every** shell that runs training. Append them +to the venv's activation script so they're set whenever you +`source ~/primus-jax-env/bin/activate`: + +```bash +cat >> ~/primus-jax-env/bin/activate <<'EOF' + +# ---- Primus JAX host environment ---- +export PYTORCH_ROCM_ARCH="gfx942;gfx950" +export ROCM_AMDGPU_TARGETS="gfx942,gfx950" +export HSA_ENABLE_SCRATCH_ASYNC_RECLAIM=0 +export HSA_NO_SCRATCH_RECLAIM=1 +export ROCPROFILER_QUEUE_INTERPOSITION=0 +export DEBUG_HIP_DYNAMIC_QUEUES=0 + +# v26.5: ROCm lives in the extracted tarball dir (Section 3.4), NOT a pip wheel. +export ROCM_PATH=$HOME/primus-jax-env/rocm +export ROCM_HOME=$ROCM_PATH +export HIP_PLATFORM=amd +export HIP_PATH=$ROCM_PATH +export HIP_CLANG_PATH=$ROCM_PATH/llvm/bin +export HIP_INCLUDE_PATH=$ROCM_PATH/include +export HIP_LIB_PATH=$ROCM_PATH/lib +export HIP_DEVICE_LIB_PATH=$ROCM_PATH/lib/llvm/amdgcn/bitcode +export PATH="$ROCM_PATH/lib:$ROCM_PATH/bin:$HIP_CLANG_PATH:$HOME/primus-jax-env/openmpi/bin:$PATH" +export LD_LIBRARY_PATH="$ROCM_PATH/lib:$ROCM_PATH/lib/rocm_sysdeps/lib:$ROCM_PATH/lib64:$ROCM_PATH/llvm/lib:$HOME/primus-jax-env/openmpi/lib" +export LIBRARY_PATH="$HIP_LIB_PATH:$ROCM_PATH/lib64" +export CPATH=$HIP_INCLUDE_PATH +export PKG_CONFIG_PATH="$ROCM_PATH/lib/pkgconfig" + +# Point Primus at the MaxText checkout whose deps we installed +export MAXTEXT_PATH=$HOME/primus-jax-env/maxtext + +# TransformerEngine (ROCm) runtime flags for JAX +export NVTE_ROCM_ARCH="$PYTORCH_ROCM_ARCH" +export NVTE_USE_ROCM=1 +export NVTE_USE_HIPBLASLT=1 +export NVTE_ALLOW_NONDETERMINISTIC_ALGO=1 +export NVTE_FUSED_ATTN=1 +export NVTE_CK_USES_BWD_V3=1 +export NVTE_CK_USES_FWD_V3=1 +export NVTE_CK_IS_V3_ATOMIC_FP32=1 +export NVTE_CK_HOW_V3_BF16_CVT=2 + +# AMD GPU runtime knobs +export GPU_MAX_HW_QUEUES=2 +export HIP_FORCE_DEV_KERNARG=1 +export HSA_FORCE_FINE_GRAIN_PCIE=1 +export NCCL_DEBUG=VERSION +# Bare-metal only: force RCCL to use its built-in ROCm IB/RoCE transport. On a +# host, /usr/local/lib/librccl-net.so is on the default loader path and is +# ABI-incompatible with the from-source RCCL (undefined symbol +# ncclNetPlugin_v11/_v10 -> falls back to v9 -> segfault at clique init). The +# v26.5 image ships no librccl-net.so, so this matches it. +export NCCL_NET_PLUGIN=none + +# XLA / JAX runtime settings (v26.5 uses .9) +export XLA_PYTHON_CLIENT_MEM_FRACTION=.9 +export XLA_FLAGS="--xla_gpu_memory_limit_slop_factor=95 --xla_gpu_reduce_scatter_combine_threshold_bytes=8589934592 --xla_gpu_enable_latency_hiding_scheduler=True --xla_gpu_all_gather_combine_threshold_bytes=8589934592 --xla_gpu_enable_triton_gemm=False --xla_gpu_enable_cublaslt=True --xla_gpu_autotune_level=0 --xla_gpu_enable_all_gather_combine_by_dim=FALSE --xla_gpu_enable_command_buffer=''" +# ---- end Primus JAX host environment ---- +EOF +``` + +> The automated `tools/installation-jax/env.sh` sets all of the above (and +> auto-detects the GPU arch); prefer sourcing it over hand-editing `activate`. + +--- + +## 6. Verify the installation + +```bash +source ~/primus-jax-env/bin/activate # or: source tools/installation-jax/env.sh + +# GPUs visible to ROCm? +rocm-smi || ls -l /dev/kfd /dev/dri + +# JAX sees the GPUs? +python -c "import jax; print('jax', jax.__version__); \ +print('backend:', jax.default_backend()); \ +print('devices:', jax.devices())" + +# Key libraries import cleanly? (import transformer_engine.jax — this actually +# loads TE's shared lib, which is what fails on glibc < 2.38 with the prebuilt wheel) +python -c "import jax, jaxlib, flax, transformer_engine.jax; print('JAX/flax/TE OK')" + +# Run a Primus MaxText training directly (no container). Use the config dir that +# matches your GPU: MI300X/ for gfx942 (MI300X/MI325X), MI355X/ for gfx950 (MI350X/MI355X). +cd ~/primus-jax-env/Primus # or your Primus checkout +./primus-cli direct -- train pretrain \ + --config examples/maxtext/configs/MI300X/llama2_7B-pretrain.yaml + # gfx950: --config examples/maxtext/configs/MI355X/llama2_7B-pretrain.yaml +``` + +`jax.default_backend()` should report `gpu` (ROCm), and `jax.devices()` should +list your AMD GPUs. Use `primus-cli direct` (not `container`) since you are +running on bare metal with everything installed in your environment. + +--- + +## 7. Other important considerations + +- **Python version**: MaxText requires Python ≥ 3.12. If your venv is older, the + build will fail; get a 3.12 interpreter with `uv` (no sudo — see Section 2.1) + and recreate it. The automated `setup.sh` does this for you (it provisions + Python 3.12 via `uv` and recreates a too-old venv automatically). +- **GPU device access without root**: the user running training must be able to + read/write `/dev/kfd` and `/dev/dri/*` — usually via membership in the `video` + and `render` groups (`sudo usermod -aG video,render $USER`, then re-login). +- **Hugging Face access**: for gated models/tokenizers, export your token + (`export HF_TOKEN=hf_xxx` and/or `huggingface-cli login`). +- **Install order is load-bearing (v26.5)**: MaxText → TensorFlow (from source) + → ROCm JAX/PJRT/plugin → TransformerEngine → RCCL (from source). MaxText's + `setup.sh` pulls in a stock `jax`/`tensorflow`, so the ROCm JAX must be + installed *after* MaxText (to override it) and *before* TE (or `jaxlib` gets + clobbered). The automated `setup.sh` stage order enforces this. +- **RDMA / multi-node limits**: high-performance networking typically requires + `ulimit -l unlimited` and possibly hugepages, configured in + `/etc/security/limits.conf` (admin help). Verify NICs with `ibv_devinfo` / + `ibstat`. JAX uses the distributed coordinator (`JAX_COORDINATOR_IP` / + `JAX_COORDINATOR_PORT`), which Primus sets from `MASTER_ADDR` / `MASTER_PORT`. +- **Version drift**: the ROCm release tarball, the JAX/PJRT/plugin versions, the + TransformerEngine wheel, the TensorFlow/RCCL source revisions, and the MaxText + branch are all pinned to one release (see the table in Section 1.1). If you + change one, you may need to update the others. The Docker image is the + authoritative, tested combination — match its `Dockerfile` ARGs when in doubt. +- **Automated scripts**: the manual steps in Section 3 are automated by + [tools/installation-jax/](https://github.com/AMD-AGI/Primus/tree/main/tools/installation-jax) + (see *Quick path* above). The multi-node networking stack in Section 4 is still + manual. + +--- + +## 8. Quick reference: minimal vs. full install + +If you only need **single-node MaxText pretraining**, you can skip the +multi-node components: + + +| Component | Needed for | +| ------------------------------------------------------ | ----------------------------------- | +| ROCm (tarball), JAX + PJRT/plugin, TransformerEngine (JAX) | Core MaxText training (install these) | +| MaxText + its deps, TensorFlow (from source), RCCL (from source) | Core MaxText training (install these) | +| Primus + `third_party/maxtext` | Running MaxText via Primus | +| gcsfuse | Reading data from GCS buckets only | +| UCX, OpenMPI, AINIC | Multi-node distributed training | + + +Install the core rows first, validate with Section 6, then add the optional +components as your workload requires. diff --git a/docs/01-getting-started/bare-metal-installation.md b/docs/01-getting-started/bare-metal-installation.md new file mode 100644 index 000000000..725160c28 --- /dev/null +++ b/docs/01-getting-started/bare-metal-installation.md @@ -0,0 +1,620 @@ +# Bare-metal installation: build the Primus training stack from source (no Docker) + +This guide explains how to build the **full Primus training software stack directly on a host machine**, without using the AMD published training Docker image. It is intended for users who, for policy or operational reasons, cannot run containers and need to reproduce the same environment on bare metal. + +It is derived from the official training Dockerfile — currently +[`Dockerfile.primus-v26.5`](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/docker-release/Dockerfile.primus-v26.5) — and installs the same components and versions. Wherever possible, everything is installed **inside a Python virtual environment and without `sudo`**. The only steps that require root are a small set of OS-level system libraries (installed with `apt`), and a couple of optional networking packages used for multi-node training. + +> **Important**: This is a long, build-heavy process. A full from-source build (Flash Attention, aiter, Primus-Turbo, and on older hosts TransformerEngine) can take **several hours** and needs a machine with many CPU cores, plenty of RAM, and tens of GB of free disk. The Docker image remains the recommended and best-supported path. Use this guide only when containers are not an option. + +> **Python 3.12 is required.** The pinned torch nightly publishes a **cp312 Linux wheel and nothing else**, so a Python 3.10 environment (the default on Ubuntu 22.04) cannot install it. The automated scripts provision a private CPython 3.12 for you without root; if you build manually you must supply one yourself. See [Section 4.1](#41-create-the-virtual-environment-python-312). + +> **Using the JAX MaxText backend instead?** This guide builds the PyTorch stack (Megatron-LM / TorchTitan). For the JAX / MaxText backend, follow the leaner [JAX bare-metal installation guide](bare-metal-installation-jax.md) instead. + +--- + +## 0. The key idea: ROCm comes from pip, not from a system install + +The most important thing to understand is that this stack **does not require a system-wide ROCm installation**. ROCm is delivered as Python wheels (AMD "TheRock" multi-arch nightly wheels): + +- `rocm-sdk-devel`, `rocm-sdk-device-gfx942`, `rocm-sdk-device-gfx950` provide the ROCm toolchain (HIP, hipBLASLt, compilers, headers, libraries) **inside the virtual environment**. +- `torch`, `torchvision`, `torchaudio` and the `amd-*-device-gfx*` packages provide a ROCm-enabled PyTorch built against those wheels. + +This means almost the entire stack can be installed **without root** into a venv. The only host-level requirements from the system administrator are: + +- The **AMD GPU kernel driver (amdgpu / ROCm KMD)** must already be installed and loaded on the host (`/dev/kfd` and `/dev/dri` must exist, and the user must have permission to access them — typically by being in the `video` and `render` groups). +- A small set of **build/runtime system libraries** (see [Section 3](#3-system-packages-need-root-one-time)). + +You do **not** need to install the full ROCm user-space stack system-wide. + +--- + +## 1. Recommended path: the automated scripts + +The helper scripts in [tools/installation/](https://github.com/AMD-AGI/Primus/tree/main/tools/installation) build the entire single-node environment for you: the Python 3.12 interpreter, the venv, the ROCm/PyTorch wheels, every source-built kernel library, and Primus itself. They are the maintained path and are kept in sync with the reference Dockerfile — prefer them over the manual reference in [Section 4](#4-manual-build-reference). + +There are two files: + +- **`env.sh`** — defines the install location and exports every environment variable the build and runtime need (ROCm paths, `NVTE_*` flags, cache locations). Source it both during the build and every time you use the environment. +- **`setup.sh`** — runs the install in re-runnable **stages**. It sources `env.sh` automatically. + +### 1.1 Before you start + +- System packages from [Section 3](#3-system-packages-need-root-one-time) must already be present (a C++ compiler, `git`, `make` and the build basics). These need root, so the scripts do not install them. +- The GPU driver must be loaded and your user must be able to access `/dev/kfd` and `/dev/dri/*`. +- Python 3.12 is handled for you: if no suitable interpreter is found, `setup.sh` fetches a standalone CPython 3.12 with [`uv`](https://docs.astral.sh/uv/). No root, no `apt`. +- **`uv` itself is optional to pre-install.** If it is missing, `setup.sh` downloads it into `$PRIMUS_BASE/bin` from `astral.sh`. Install it yourself if that download is blocked, or if you prefer not to pipe a script into a shell — see [Installing `uv`](#installing-uv). + +### 1.2 Choose where it installs (required) + +`PRIMUS_BASE` **has no default and must be exported.** The right location is site-specific, so the scripts refuse to guess and stop with an error if it is unset. Point it at a directory you can write to with tens of GB free; it holds the venv, the provisioned interpreter, and the kept checkouts. + +```bash +export PRIMUS_BASE="$HOME/envs/primus-env" # venv + interpreter + checkouts (persistent) +export SRC_DIR=/tmp/primus-build # transient build sources (optional override) +``` + +The scripts detect your GPU architecture automatically and build only for it — `gfx942` (MI300X/MI325X) or `gfx950` (MI350X/MI355X) — which keeps the build as short as possible. Override it with `export PYTORCH_ROCM_ARCH="gfx942;gfx950"` only if you need a different target, such as one environment shared across both. + +### 1.3 Build the environment + +```bash +cd tools/installation + +bash setup.sh # run all default stages, in order +bash setup.sh --list # list available stages (works without PRIMUS_BASE) +bash setup.sh te # re-run a single stage (e.g. reinstall TransformerEngine) +bash setup.sh venv torch # run a subset of stages +``` + +Expect hours rather than minutes. Running it detached avoids losing the build to a dropped connection: + +```bash +nohup bash setup.sh > ~/primus-setup.log 2>&1 & +tail -f ~/primus-setup.log +``` + +Stages are idempotent, so if a build fails you can fix the cause and re-run just that stage — exporting the same `PRIMUS_BASE` again, since that is how it finds the venv. On failure the script stops immediately and prints which stage failed. + +Default stages (single-node training path): + +``` +venv → torch → flash_attn → te → torchtune → torchao → pydeps + → grouped_gemm → causal_conv1d → mamba → primus → aiter → turbo + → boto → cleanup → manifest +``` + +Optional: `torchrec` (DLRM / recommendation stack). + +Order matters if you cherry-pick: `te` needs `torch` and `flash_attn` to have run first, because the TransformerEngine package index serves only TE packages and cannot supply anything else. + +### 1.4 Use the environment for a training job + +```bash +# Use the SAME PRIMUS_BASE you built with +export PRIMUS_BASE="$HOME/envs/primus-env" +source tools/installation/env.sh # activates the venv + sets all ROCm/NVTE vars + +python -c "import torch; print('gpu:', torch.cuda.is_available())" + +# Primus is checked out under $WORKSPACE_DIR +cd "$WORKSPACE_DIR/Primus" +./primus-cli direct -- train pretrain \ + --config examples/megatron/configs/MI300X/llama3.1_8B-BF16-pretrain.yaml +``` + +Use `primus-cli direct` (not `container`), since you are running on bare metal with everything installed in your environment. + +### 1.5 What the scripts do NOT do + +- **System (`apt`) packages** ([Section 3](#3-system-packages-need-root-one-time)): skipped — they need root. +- **Multi-node networking** ([Section 5](#5-multi-node-communication-stack-ucx-and-openmpi)): UCX, OpenMPI and AMD AINIC are not built. Single-node training works without them. +- **FBGEMM / Flux / DLRM benchmarks**: not built (`torchrec` is an optional stage; FBGEMM additionally needs apt `libtbb-dev`). +- **MLPerf `primus_mllog`**: v26.5 installs it from `training_results_v6.0`; it is not part of the core training path and is not installed. + +### 1.6 Where the scripts deliberately differ from the Dockerfile + +A handful of places diverge on purpose, because copying the Dockerfile exactly produces an environment that does not work outside the container — several of its floating (unpinned) dependencies have since drifted to versions that break. The short list: + +- **TransformerEngine** is installed from the wheel index only where glibc is new enough, otherwise built from the equivalent source commit ([Section 4.6](#46-transformerengine)). +- **Companion wheels are pinned** (`torchaudio`, `torchvision`, `apex`) to the same nightly as `torch`, because the Dockerfile's floating versions no longer resolve. +- **`nvidia-cutlass-dsl` is pinned to 4.5.3** and **`flydsl` to 0.2.4**, both to versions that the Dockerfile itself resolved to when it was built. Newer releases silently break `import mamba_ssm` and aiter's CK/HIP kernels respectively. +- **`NVTE_CK_IS_V3_ATOMIC_FP32=1` on gfx942** instead of the Dockerfile's `0`: without fp32 atomics the CK v3 backward attention kernel produces `Inf` gradients on MI300X/MI325X at the first training step. This matches the MI300X/MI325X block in [training recipes](../02-user-guide/training-recipes.md); gfx950 keeps the Dockerfile value. +- **pip is constrained** so no later install can replace the ROCm `torch`/`triton` with upstream CUDA builds. + +Each one is documented with the exact failure it avoids in +[tools/installation/README.md](https://github.com/AMD-AGI/Primus/blob/main/tools/installation/README.md). Read that before "correcting" any of them back. + +--- + +## 2. Required software stack for distributed LLM training + +The complete environment is composed of the following layers: + +| Layer | Component | Source | Needs root? | +| ----------------------- | ------------------------------------------------------------------------------------------- | ----------------------- | -------------------------------- | +| Kernel / hardware | AMD GPU driver (amdgpu KMD), GPU device access | OS / admin | Yes (one-time, by admin) | +| OS libraries | Build toolchain + runtime libs (`g++`, `git`, RDMA, hwloc, etc.) | `apt` | Yes (one-time) | +| Python | CPython 3.12 (required by the pinned wheels) | `uv` / standalone build | No | +| ROCm user-space | `rocm-sdk-devel` + device packages | pip (TheRock wheels) | No (venv) | +| Deep learning framework | PyTorch ROCm (`torch`, `torchvision`, `torchaudio`, `apex`) | pip (TheRock wheels) | No (venv) | +| Accelerated kernels | Flash Attention, TransformerEngine, aiter, grouped_gemm, Primus-Turbo, causal-conv1d, mamba | pip / build from source | No (venv) | +| Training frameworks | torchtune, torchao, torchrec, FBGEMM | build from source / pip | No (venv) | +| Multi-node comms | UCX, OpenMPI, AMD AINIC (libionic) | build from source / apt | Mostly no (AINIC libs need root) | +| Primus | Primus + submodules (Megatron-LM, TorchTitan, etc.) | git + pip | No (venv) | +| Python deps | datasets, transformers, accelerate, trl, wandb, etc. | pip | No (venv) | + +For a **distributed (multi-node) training job** specifically, beyond PyTorch and ROCm you additionally need: + +- **RCCL** (AMD's collective library) — provided by the ROCm SDK wheels. +- **UCX + OpenMPI** — point-to-point transport and the MPI launcher. +- **AMD AINIC / RDMA stack** (`libibverbs`, `rdma-core`, `libionic`) — for high-performance networking on AMD Pensando NICs. +- Correct GPU/NIC device permissions and (often) hugepages / `ulimit -l unlimited` for RDMA. + +--- + +## 3. System packages (need root, one-time) + +These are OS-level libraries needed to *build* the rest of the stack and to run RDMA networking. They must be installed by someone with root, but this is a **one-time** action; everything afterward is done unprivileged in a venv. On a shared/managed host, ask your administrator to install them once. + +> If you genuinely cannot get root at all, these packages must already be present on the host. There is no supported way to install system `.deb` packages without root. The remainder of the guide then runs entirely without root. + +### 3.1 Build toolchain and core libraries + +```bash +sudo apt update +sudo apt install -y \ + gfortran git git-lfs ninja-build g++ pkg-config xxd patchelf \ + automake libtool autoconf flex ccache \ + python3-venv python3-dev python3-pip python-is-python3 \ + libegl1-mesa-dev liblzma-dev libdw1 libdrm-dev libz3-dev \ + wget xz-utils ffmpeg numactl pciutils +``` + +`libtbb-dev` is additionally required if you intend to build FBGEMM. + +### 3.2 RDMA / networking libraries (needed for multi-node training) + +```bash +sudo apt install -y \ + rdma-core libibverbs-dev ibverbs-utils infiniband-diags \ + ethtool kmod dpkg-dev jq \ + libevent-dev libhwloc-dev libmunge-dev \ + software-properties-common +``` + +### 3.3 AMD AINIC library (optional, for AMD Pensando NICs) + +This pulls a vendor `.deb` from the AMD radeon repository. Skip it if you are not using AMD AINIC networking. + +```bash +# Pin to the version used by the reference image +AINIC_BUNDLE_VERSION="1.117.5-a-77" + +sudo add-apt-repository -y \ + "deb https://repo.radeon.com/amdainic/pensando/ubuntu/${AINIC_BUNDLE_VERSION} noble main" +sudo apt update --allow-insecure-repositories +sudo apt install -y --allow-unauthenticated libionic-dev +``` + +--- + +## 4. Manual build reference + +This section is **reference material**, not a maintained walkthrough: it records the exact versions and the non-obvious build steps so you can audit or adapt the process. For an actual install, use the scripts in [Section 1](#1-recommended-path-the-automated-scripts) — they encode everything below plus the workarounds listed in [Section 1.6](#16-where-the-scripts-deliberately-differ-from-the-dockerfile). + +The authoritative source of versions is always the reference Dockerfile. + +### 4.1 Create the virtual environment (Python 3.12) + +The pinned torch nightly ships a cp312 Linux wheel only, so the interpreter must be 3.12. Ubuntu 22.04 has 3.10, and installing 3.12 with `apt` needs root — the no-root option is a standalone build, which is what `uv` provides. + +#### Installing `uv` + +Either method works and neither needs root. `setup.sh` performs the second one automatically when `uv` is missing, so you only need to do this by hand for a manual build, or if the `astral.sh` download is blocked on your network. + +```bash +# A. via pip, using whatever Python you already have. +# --user puts it in ~/.local/bin, one of the locations setup.sh searches. +python3 -m pip install --user uv + +# B. via the standalone installer (what setup.sh uses) +curl -LsSf https://astral.sh/uv/install.sh | sh +``` + +Make sure the result is on your `PATH` (`uv --version` should work). If you cannot install `uv` at all, supply your own 3.12 interpreter instead and skip it entirely — the scripts accept `PRIMUS_PYTHON=/path/to/python3.12`, and for a manual build just point `PY312` at it below. + +#### Create the venv + +```bash +uv python install 3.12 +PY312="$(uv python find 3.12)" + +"$PY312" -m venv ~/primus-env +source ~/primus-env/bin/activate +python --version # must report 3.12.x + +# Build/runtime knobs (match the Dockerfile) +export MAX_JOBS=128 # lower if you have fewer cores / less RAM +export PYTORCH_ROCM_ARCH="gfx942;gfx950" # MI300/MI325 = gfx942, MI350/MI355 = gfx950 +export ROCM_AMDGPU_TARGETS="gfx942,gfx950" +export HSA_ENABLE_SCRATCH_ASYNC_RECLAIM=0 # avoids HSA_STATUS_ERROR_OUT_OF_RESOURCES +export HSA_NO_SCRATCH_RECLAIM=1 +``` + +> Set `PYTORCH_ROCM_ARCH` to only the architecture(s) you actually have to speed up source builds (e.g. `"gfx942"` for MI300X-only). + +### 4.2 Bootstrap build tooling + +```bash +pip install --upgrade pip +pip install \ + pybind11 typeguard \ + wheel==0.45.1 cmake==3.31.6 ninja==1.11.1.3 \ + packaging==25.0 setuptools==75.1.0 +``` + +### 4.3 Install ROCm + PyTorch from the TheRock multi-arch wheels + +This replaces a system ROCm install. Install the base deps first — `apex` declares `cxxfilt`, `pytest` and `ninja` requirements that the nightly index does not serve, so they must be present before the torch resolve. + +```bash +pip install \ + cxxfilt==0.3.0 tqdm==4.67.3 pyyaml==6.0.3 pytest==9.0.3 \ + matplotlib==3.10.9 pandas==2.3.3 py-cpuinfo==9.0.0 build==1.5.0 + +# One coherent nightly. The Dockerfile pins only `torch` and lets the companions +# float, which no longer resolves; pin them all to the same date. +NIGHTLY="rocm7.15.0a20260720" + +python -m pip uninstall -y torch +python -m pip install \ + --index-url https://rocm.nightlies.amd.com/whl-multi-arch --pre \ + torch==2.12.0+${NIGHTLY} \ + amd-torch-device-gfx942==2.12.0+${NIGHTLY} \ + amd-torch-device-gfx950==2.12.0+${NIGHTLY} \ + rocm-sdk-devel==7.15.0a20260720 \ + rocm-sdk-device-gfx942==7.15.0a20260720 \ + rocm-sdk-device-gfx950==7.15.0a20260720 \ + torchaudio==2.11.0+${NIGHTLY} \ + torchvision==0.27.0+${NIGHTLY} \ + amd-torchvision-device-gfx942==0.27.0+${NIGHTLY} \ + amd-torchvision-device-gfx950==0.27.0+${NIGHTLY} \ + apex==1.12.0+${NIGHTLY} +``` + +> Install only the `*-gfx942` **or** `*-gfx950` device packages matching your hardware for a smaller install. +> +> Nightly indexes are pruned. If this date has disappeared, pick a newer one that publishes a *complete* cp312 set — `torch`, `amd-torch-device-*`, `rocm-sdk-*`, `torchaudio`, `torchvision`, `amd-torchvision-device-*` and `apex` must all come from the same date. + +### 4.4 Initialize the ROCm SDK and export ROCm paths + +`rocm-sdk init` materializes the ROCm toolchain inside the venv. The variables below point the rest of the build at that in-venv ROCm and **must be set every time you use the environment** — see [Section 6](#6-make-the-environment-reproducible). + +```bash +rocm-sdk init + +export ROCM_PATH=$(python -c 'import _rocm_sdk_devel, os; print(os.path.dirname(_rocm_sdk_devel.__file__))') +export ROCM_HOME=$ROCM_PATH # Primus uses ROCM_HOME; set both +export HIP_PLATFORM=amd +export HIP_PATH=$ROCM_PATH +export HIP_CLANG_PATH=$ROCM_PATH/llvm/bin +export HIP_INCLUDE_PATH=$ROCM_PATH/include +export HIP_LIB_PATH=$ROCM_PATH/lib +export HIP_DEVICE_LIB_PATH=$ROCM_PATH/lib/llvm/amdgcn/bitcode +export PATH="$ROCM_PATH/bin:$HIP_CLANG_PATH:$PATH" +export LD_LIBRARY_PATH="$HIP_LIB_PATH:$ROCM_PATH/lib:$ROCM_PATH/lib64:$ROCM_PATH/llvm/lib:$ROCM_PATH/lib/host-math/lib:$ROCM_PATH/lib/rocm_sysdeps/lib" +export LIBRARY_PATH="$HIP_LIB_PATH:$ROCM_PATH/lib:$ROCM_PATH/lib64" +export CPATH=$HIP_INCLUDE_PATH +export PKG_CONFIG_PATH="$ROCM_PATH/lib/pkgconfig" +``` + +> The Dockerfile hardcodes `ROCM_PATH=/opt/venv/lib/python3.12/site-packages/_rocm_sdk_devel`. The `python -c ...` form derives it from the venv instead, so it works wherever you put the environment. + +Quick check before continuing: + +```bash +hipcc --version +python -c "import torch; print(torch.__version__, torch.cuda.is_available(), torch.cuda.get_device_name(0))" +``` + +### 4.5 Source-built components and their pins + +Build these against your in-venv ROCm and selected `PYTORCH_ROCM_ARCH`, in a scratch directory: + +```bash +export GPU_ARCHS="${PYTORCH_ROCM_ARCH}" # cross-compile during builds; see Section 6 for runtime +mkdir -p ~/primus-build && cd ~/primus-build +``` + +| Component | Repository | Pin | Install command | Notes | +|---|---|---|---|---| +| Flash Attention | `ROCm/flash-attention` | `6387433156558135a998d5568a9d74c1778666d8` | `python setup.py install` | clone `--recursive` | +| grouped_gemm | `caaatch22/grouped_gemm` | branch `rocm` | `pip install --no-build-isolation .` | MoE models | +| causal-conv1d | `Dao-AILab/causal-conv1d` | `e940ead2fd962c56854455017541384909ca669f` | `pip install --no-build-isolation .` | needs `CAUSAL_CONV1D_FORCE_BUILD=TRUE`, `HIP_ARCHITECTURES=gfx942,gfx950` | +| mamba | `AndreasKaratzas/mamba` | branch `enable-primus-hybrid-models` | `pip install --no-build-isolation .` | see note below | +| aiter | `ROCm/aiter` | `0f3c58e6edb6754940bcf9fd5f09ccb6f389f52e` | `PREBUILD_KERNELS=3 pip install --no-cache-dir --use-pep517 .` | clone `--recursive`; `pip uninstall aiter amd-aiter` first | +| Primus-Turbo | `AMD-AGI/Primus-Turbo` | `edc8d2ccb0be4888e80ee7c6e765fd3956026a32` | `pip install -r requirements.txt` then `pip install --no-build-isolation . -v` | needs `HCC_AMDGPU_TARGET="gfx942,gfx950"`; see note below | +| torchtune | `pytorch/torchtune` | `b4c98ac2a37f0397d64c22579aed415ce7264db6` | `pip install .` | patch first: `sed -i 's/use_grouped_mm = True/use_grouped_mm = False/g' torchtune/modules/moe/utils.py` | +| torchao | `pytorch/ao` | `e9c7bead90b840b280f97374308255957108ce47` | `pip install --no-build-isolation .` | two patches: `pad_inner_dim` → `True` in `torchao/float8/config.py`, and `if defined(HIPBLASLT_VEC_EXT)` → `if false` in `torchao/csrc/rocm/swizzle/swizzle.cpp` | + +**mamba.** Install with `pip`, **not** `python setup.py install`: the legacy `easy_install` path ignores pip-installed packages and re-fetches the latest of every unpinned dependency as `.egg`s, which clobbers the pins. Pin `apache-tvm-ffi==0.1.11` beforehand, and `nvidia-cutlass-dsl==4.5.3` — newer CUTLASS DSL releases removed a symbol that `mamba_ssm`'s `quack-kernels` dependency imports, breaking `import mamba_ssm`. `mamba_ssm` pulls in `tilelang`, which v26.5 uninstalls again once everything is built. + +**Primus-Turbo.** Its `setup.py` hard-pins upstream `triton==3.7.0`, which conflicts with the ROCm `triton` that `torch` requires; installing it replaces the ROCm build and then segfaults on import, because ROCm's HIP runtime already loads its own LLVM. Install with `--no-deps` and supply `scipy` and `flydsl==0.2.4` yourself. Primus-Turbo also probes for rocSHMEM and mistakes the pip ROCm SDK for one; set `ROCSHMEM_HOME` to a non-existent path to make the probe fail cleanly and disable internode DeepEP. + +### 4.6 TransformerEngine + +v26.5 changed this: TE is no longer built from source but installed from the ROCm staging index. + +```bash +# Runtime tuning flags (see Section 6 for the full set) +export NVTE_USE_CAST_TRANSPOSE_TRITON=1 + +# TE's own dependencies must be present first: the staging index serves only the +# transformer-engine packages, so pip cannot fetch anything else while it is selected. +pip install pybind11==3.0.4 importlib-metadata==8.7.1 onnxscript==0.7.0 \ + pydantic==2.13.4 nvdlfw_inspect==0.2.2 einops onnx + +pip install \ + --index-url https://rocm.frameworks-nightlies.amd.com/whl-staging/device-all/ \ + --pre --no-build-isolation \ + transformer_engine_rocm_torch==2.15.0.dev0+rocm7.15.0a20260716.a07e607 +``` + +> **Those wheels need glibc ≥ 2.38.** They are built on Ubuntu 24.04, and `libtransformer_engine.so` requires `GLIBC_2.38` plus `GLIBCXX_3.4.32`. Ubuntu 22.04 has glibc 2.35, and glibc cannot be side-loaded via `LD_LIBRARY_PATH`, so on 22.04 the wheels install fine but fail at import with `version 'GLIBC_2.38' not found`. +> +> On such hosts, build the equivalent source commit instead — the same commit the version label refers to: +> +> ```bash +> git clone --recursive https://github.com/ROCm/TransformerEngine.git +> cd TransformerEngine +> git checkout a07e607f14a5330807ffdafeeb6224f2d7dffacc +> git submodule update --init --recursive +> pip install psutil +> MAX_JOBS=${MAX_JOBS} NVTE_FRAMEWORK=pytorch NVTE_USE_ROCM=1 \ +> NVTE_USE_HIPBLASLT=1 NVTE_ROCM_ARCH=${PYTORCH_ROCM_ARCH} \ +> pip install --no-build-isolation . +> ``` +> +> `setup.sh` picks between the two automatically from the host's glibc; override with `PRIMUS_TE_MODE=wheel|source`. + +Either way, apply the same fix v26.5 applies inside the image: concurrent CK JIT compiles race to publish the same `.so`, and without this the loser aborts the run. + +```bash +CK_JIT=$(python -c 'import os, sysconfig; print(os.path.join(sysconfig.get_paths()["purelib"], "transformer_engine/lib/ck_jit/ck_jit_compile.sh"))') +sed -i 's| mv -n "$_TMP_SO" "$OUTPUT"$| mv -n "$_TMP_SO" "$OUTPUT" 2>/dev/null \|\| true|' "$CK_JIT" +``` + +### 4.7 Training-framework Python dependencies + +```bash +pip install \ + datasets==3.6.0 av==16.0.1 transformers==4.55.0 optree==0.18.0 sympy \ + accelerate==1.9.0 trl==0.21.0 tensorboard==2.20.0 peft scipy einops \ + flask-restful nltk pytest pytest-cov pytest_mock pytest-csv \ + pytest-random-order sentencepiece wrapt \ + zarr==2.18.7 numcodecs==0.12.1 xarray wandb tensorstore==0.1.45 \ + pybind11 tiktoken pynvml "huggingface_hub[cli]" + +python3 -m nltk.downloader punkt_tab + +# AWS SDK (used by some data pipelines) +pip install boto3==1.35.42 botocore==1.35.99 +``` + +### 4.8 Install Primus and its submodules + +```bash +cd ~/primus-build +# Required to resolve a post-v26.2 attention backend issue +export NVTE_FLASH_ATTN=0 +export NVTE_FUSED_ATTN=1 + +git clone --recurse-submodules https://github.com/AMD-AGI/Primus.git +cd Primus +git checkout b511d1b66b0068715308ea9bfe8ba147ea1a3860 # release/v26.5 +git submodule update --init --recursive +pip install -r requirements.txt + +# Megatron's dataset indexing needs a compiled pybind11 extension, or training +# fails with "MockGPTDataset failed to build as a mock data generator". +# Pass LIBEXT explicitly: the Makefile reads it from `python3-config`, which a +# venv does not provide, so it otherwise emits a filename 3.12 will never import. +EXT=$(python -c 'import sysconfig; print(sysconfig.get_config_var("EXT_SUFFIX"))') +make -C third_party/Megatron-LM/megatron/core/datasets LIBEXT="$EXT" +``` + +If you already have a local Primus checkout, run `pip install -r requirements.txt` from its root and skip the clone — but still build `helpers_cpp` in *that* checkout, since it is the one you will launch training from. + +### 4.9 Optional: torchrec + FBGEMM (DLRM / recommendation workloads) + +```bash +pip install --no-deps torchrec +pip install tensordict iopath torchmetrics==1.0.3 \ + git+https://github.com/mlperf/logging.git \ + --extra-index-url https://rocm.nightlies.amd.com/whl-multi-arch + +# FBGEMM (GPU) — needs apt libtbb-dev +export BUILD_ROCM_VERSION='7.14' + +git clone https://github.com/pytorch/FBGEMM.git +cd FBGEMM +git checkout 80bd3c077dc41b55cd16ed4dcad15cf7c1c1d76a +cd fbgemm_gpu +git clean -dfx && git submodule sync && git submodule update --init --recursive +pip install -r requirements.txt +pip install setuptools==75.1.0 +python setup.py install \ + --build-variant=rocm \ + --build-target=default \ + -DAMDGPU_TARGETS=$PYTORCH_ROCM_ARCH \ + -DHIP_ROOT_DIR=$ROCM_PATH \ + -DCMAKE_C_FLAGS="-DTORCH_USE_HIP_DSA" \ + -DCMAKE_CXX_FLAGS="-DTORCH_USE_HIP_DSA" +cd ../.. +``` + +> The Flux (`AMDiffusionBenchmark`) and DLRM (`DLRMBenchmark`) repos in the Dockerfile are benchmark workloads, not core training dependencies. Clone them only if you need those specific benchmarks. + +--- + +## 5. Multi-node communication stack (UCX and OpenMPI) + +These are only needed for **multi-node distributed** training. They build from source into user-writable prefixes (no root needed, except the AINIC `.deb` already handled in [Section 3.3](#33-amd-ainic-library-optional-for-amd-pensando-nics)). + +### 5.1 UCX + +```bash +cd ~/primus-build +UCX_VERSION="1.18.0" +wget https://github.com/openucx/ucx/releases/download/v${UCX_VERSION}/ucx-${UCX_VERSION}.tar.gz +mkdir -p ucx-${UCX_VERSION} +tar -zxf ucx-${UCX_VERSION}.tar.gz -C ucx-${UCX_VERSION} --strip-components=1 +cd ucx-${UCX_VERSION} +mkdir build && cd build +../configure --prefix=$HOME/primus-build/ucx-${UCX_VERSION}/install --with-rocm=${ROCM_PATH} +make -j 16 && make install +cd ../.. + +export UCX_INSTALL_DIR=$HOME/primus-build/ucx-${UCX_VERSION}/install +``` + +### 5.2 OpenMPI + +```bash +MPI_VERSION="4.1.6" +wget https://download.open-mpi.org/release/open-mpi/v$(echo "${MPI_VERSION}" | cut -d. -f1-2)/openmpi-${MPI_VERSION}.tar.gz +mkdir -p ompi-${MPI_VERSION} +tar -zxf openmpi-${MPI_VERSION}.tar.gz -C ompi-${MPI_VERSION} --strip-components=1 +cd ompi-${MPI_VERSION} +mkdir build && cd build +# Install to a user-writable prefix instead of /opt to avoid sudo +../configure --prefix=$HOME/primus-build/openmpi --with-ucx=${UCX_INSTALL_DIR} \ + --disable-oshmem --disable-mpi-fortran +make -j 16 && make install +cd ../.. + +export PATH="$HOME/primus-build/openmpi/bin:${PATH}" +export LD_LIBRARY_PATH="$HOME/primus-build/openmpi/lib:${LD_LIBRARY_PATH}" +``` + +> The Dockerfile installs OpenMPI to `/opt/openmpi` (needs root). The `$HOME/primus-build/openmpi` prefix above keeps it unprivileged. Use `/opt/openmpi` only if you have root and want to match the image exactly. + +--- + +## 6. Make the environment reproducible + +The ROCm paths and `NVTE_*` flags must be present in **every** shell that runs training. + +If you used the scripts, this is already handled — `source tools/installation/env.sh` sets everything (and refuses to run if `PRIMUS_BASE` is unset). For a manual install, append the equivalent to the venv's activation script: + +```bash +cat >> ~/primus-env/bin/activate <<'EOF' + +# ---- Primus host environment ---- +export PYTORCH_ROCM_ARCH="gfx942;gfx950" +export ROCM_AMDGPU_TARGETS="gfx942,gfx950" +export HSA_ENABLE_SCRATCH_ASYNC_RECLAIM=0 +export HSA_NO_SCRATCH_RECLAIM=1 + +export ROCM_PATH=$(python -c 'import _rocm_sdk_devel, os; print(os.path.dirname(_rocm_sdk_devel.__file__))') +export ROCM_HOME=$ROCM_PATH +export HIP_PLATFORM=amd +export HIP_PATH=$ROCM_PATH +export HIP_CLANG_PATH=$ROCM_PATH/llvm/bin +export HIP_INCLUDE_PATH=$ROCM_PATH/include +export HIP_LIB_PATH=$ROCM_PATH/lib +export HIP_DEVICE_LIB_PATH=$ROCM_PATH/lib/llvm/amdgcn/bitcode +export PATH="$ROCM_PATH/bin:$HIP_CLANG_PATH:$HOME/primus-build/openmpi/bin:$PATH" +export LD_LIBRARY_PATH="$HIP_LIB_PATH:$ROCM_PATH/lib:$ROCM_PATH/lib64:$ROCM_PATH/llvm/lib:$ROCM_PATH/lib/host-math/lib:$ROCM_PATH/lib/rocm_sysdeps/lib:$HOME/primus-build/openmpi/lib" +export LIBRARY_PATH="$HIP_LIB_PATH:$ROCM_PATH/lib:$ROCM_PATH/lib64" +export CPATH=$HIP_INCLUDE_PATH +export PKG_CONFIG_PATH="$ROCM_PATH/lib/pkgconfig" + +# TransformerEngine: attention backend selection used by Primus +export NVTE_FLASH_ATTN=0 +export NVTE_FUSED_ATTN=1 + +# TransformerEngine: CK performance knobs +export NVTE_USE_CAST_TRANSPOSE_TRITON=1 +export NVTE_CK_USES_FWD_V3=1 +export NVTE_CK_USES_BWD_V3=1 +export CK_TILE_FLOAT_TO_BFLOAT16_DEFAULT=2 +export NVTE_CK_HOW_V3_BF16_CVT=2 +# gfx942 (MI300X/MI325X) needs fp32 atomics for the CK v3 backward kernel: with +# the Dockerfile's 0 it produces Inf gradients at the first step. Use 0 on gfx950. +export NVTE_CK_IS_V3_ATOMIC_FP32=1 + +# Runtime: let aiter detect the local GPU architecture. +# NOTE: this is the runtime value. If you rebuild any source kernel later, +# re-export GPU_ARCHS="$PYTORCH_ROCM_ARCH" first, then set it back to native. +export GPU_ARCHS=native + +# Multi-node comms (only if built) +export UCX_HOME=$HOME/primus-build/ucx-1.18.0/install +export MPI_HOME=$HOME/primus-build/openmpi +# ---- end Primus host environment ---- +EOF +``` + +--- + +## 7. Verify the installation + +```bash +# Scripts: export the same PRIMUS_BASE and source env.sh +# Manual: source ~/primus-env/bin/activate + +# GPUs visible to ROCm? +rocm-smi || ls -l /dev/kfd /dev/dri + +# PyTorch sees the GPUs? +python -c "import torch; print('torch', torch.__version__); \ +print('gpu available:', torch.cuda.is_available()); \ +print('device count:', torch.cuda.device_count()); \ +print('device 0:', torch.cuda.get_device_name(0))" + +# Key kernel libs import cleanly? +python -c "import transformer_engine, flash_attn, aiter, primus_turbo, mamba_ssm; print('kernels OK')" + +# Run a Primus benchmark / training directly (no container) +cd "$WORKSPACE_DIR/Primus" # or your Primus checkout +./primus-cli direct -- benchmark gemm -M 4096 -N 4096 -K 4096 +./primus-cli direct -- train pretrain \ + --config examples/megatron/configs/MI300X/llama3.1_8B-BF16-pretrain.yaml \ + --train_iters 10 +``` + +A healthy run reports a decreasing `lm loss`, `number of nan iterations: 0`, and a steady `throughput per GPU (TFLOP/s/GPU)` after the first couple of iterations. + +Use `primus-cli direct` (not `container`) since you are running on bare metal with everything installed in your environment. + +--- + +## 8. Other important considerations + +- **GPU device access without root**: the user running training must be able to read/write `/dev/kfd` and `/dev/dri/*`. This usually means membership in the `video` and `render` groups (`sudo usermod -aG video,render $USER`, then re-login). This is a one-time admin action. +- **Hugging Face access**: if your config downloads weights or tokenizers from the Hub, export your token: `export HF_TOKEN=hf_xxx` (and/or `huggingface-cli login`). The token is needed for gated models like Llama. +- **RDMA / multi-node limits**: high-performance networking typically requires locked-memory limits raised (`ulimit -l unlimited`) and possibly hugepages. These are configured in `/etc/security/limits.conf` and need admin help. Verify NICs with `ibv_devinfo` and `ibstat`. +- **Disk and time**: source builds of aiter, Primus-Turbo, FBGEMM and (on older glibc) TransformerEngine are large and slow. Reserve plenty of disk and expect a multi-hour first build. Lower `MAX_JOBS` if the build runs out of memory. +- **`ccache`**: installed in [Section 3.1](#31-build-toolchain-and-core-libraries); it dramatically speeds up rebuilds, with no extra configuration needed for a basic speedup. +- **Architecture pinning**: building for only your actual GPU arch (e.g. `gfx942` for MI300X/MI325X, `gfx950` for MI350X/MI355X) significantly reduces build time and binary size versus building both. The scripts already do this automatically; it only needs stating explicitly for a manual build. +- **Version drift is the main hazard.** The nightly wheels and source commits are a tested combination; several of the Dockerfile's *unpinned* transitive dependencies have since released versions that break the build or silently disable kernels. The scripts pin those explicitly — see [Section 1.6](#16-where-the-scripts-deliberately-differ-from-the-dockerfile). Treat the reference `Dockerfile` as the source of truth for versions, and the scripts as the source of truth for the workarounds. + +--- + +## 9. Quick reference: minimal vs. full install + +If you only need **single-node Megatron/TorchTitan LLM pretraining**, you can skip several optional components: + +| Component | Needed for | +| ------------------------------------------------------- | ------------------------------------ | +| Flash Attention, TransformerEngine, aiter, Primus-Turbo | Core LLM training (install these) | +| grouped_gemm | MoE models | +| causal-conv1d, mamba | Hybrid / Mamba-family models | +| torchtune, torchao | Post-training (SFT/LoRA), fp8 | +| torchrec, FBGEMM, DLRM | Recommendation (DLRM) workloads only | +| Flux / AMDiffusionBenchmark | Diffusion benchmark only | +| UCX, OpenMPI, AINIC | Multi-node distributed training | + +Install the core rows first, validate with [Section 7](#7-verify-the-installation), then add the optional components as your workload requires. diff --git a/docs/01-getting-started/glossary.md b/docs/01-getting-started/glossary.md new file mode 100644 index 000000000..4ede3de7b --- /dev/null +++ b/docs/01-getting-started/glossary.md @@ -0,0 +1,250 @@ +# Glossary + +Alphabetical reference for terms used in Primus documentation and configuration. Cross-links point to other production docs where applicable. + +--- + +### AINIC + +**AMD AI NIC**—AMD’s AI-optimized network interface for multi-node GPU communication (for example, the **AMD Pensando™ Pollara 400 AI NIC**). + +--- + +### Backend + +A training framework integrated into Primus (for example **Megatron-LM**, **TorchTitan**, **MaxText**, **Megatron Bridge**, **HummingbirdXT**). + +--- + +### BackendAdapter + +Abstract class in Primus that connects a backend: discovery of setup paths, config conversion, and trainer loading. + +--- + +### BackendRegistry + +Registry mapping backend names to adapter classes, often with **lazy import** to avoid loading unused frameworks. + +--- + +### BaseTrainer + +Abstract trainer defining the lifecycle: **setup** → **init** → **train** → **cleanup**. + +--- + +### BF16 / FP16 / FP8 / FP4 + +Floating-point precisions: **Brain Float 16**, **IEEE half**, **8-bit float**, and **4-bit float** training or inference formats (exact support depends on backend and hardware). + +--- + +### CP (context parallelism) + +Parallelism that **splits the sequence dimension** across devices for long-context training. + +--- + +### DP (data parallelism) + +Replicates the model across GPUs; each rank processes **different data batches**. + +--- + +### DeepEP + +**Deep Expert Parallelism**—Primus-Turbo’s acceleration path for **MoE token dispatch** and related expert-parallel work. + +--- + +### EP (expert parallelism) + +Distributes **Mixture-of-Experts** expert networks across devices. + +--- + +### Experiment config + +Top-level **YAML** describing `work_group`, **modules**, and **overrides** for a training run. + +--- + +### FSDP + +**Fully Sharded Data Parallel**—shards parameters, gradients, and optimizer states across devices (PyTorch FSDP and similar concepts per backend). + +--- + +### GBS (global batch size) + +Total **batch size across all data-parallel ranks** for one optimizer step (may combine micro-batching and gradient accumulation). + +--- + +### Gradient accumulation + +Accumulates gradients over **multiple micro-batches** before an optimizer update. + +--- + +### HipBLASLt + +AMD’s high-performance **BLAS** library with **autotuning** for GEMM and related kernels. + +--- + +### Hook + +Shell or Python scripts under `runner/helpers/hooks/` executed at defined **lifecycle** points. + +--- + +### LoRA + +**Low-Rank Adaptation**—parameter-efficient fine-tuning that trains small adapter matrices. + +--- + +### MBS (micro batch size) + +Batch size **per GPU** (per rank) for **one forward/backward pass** within a gradient-accumulation window. + +--- + +### MLA (multi-latent attention) + +Compressed **KV-cache** attention architecture used in models such as DeepSeek. + +--- + +### MoE (mixture of experts) + +Architecture with **multiple expert** sub-networks and a **router** that assigns tokens to experts. + +--- + +### Model config + +YAML **preset** describing architecture (hidden size, layers, attention heads, and so on). + +--- + +### Module config + +YAML **preset** for training behavior: learning rate, batch sizes, optimizer, schedules. + +--- + +### NCCL / RCCL + +**NVIDIA Collective Communications Library** / **ROCm** equivalent—libraries for **GPU collective** operations in distributed training. + +--- + +### PP (pipeline parallelism) + +Splits **model layers** into **stages** on different devices. + +--- + +### Patch + +Runtime **monkey-patch** registered in **PatchRegistry** and applied at a named training phase. + +--- + +### PatchRegistry + +Registry of **phase-aware** patches (for example `build_args`, `setup`, `before_train`, `after_train`). + +--- + +### Platform config + +YAML describing **cluster environment** mappings (for example `platform_azure.yaml`): env vars, paths, and scheduler hints. + +--- + +### Preflight + +Cluster **diagnostic** tooling that checks host, GPU, network, and baseline performance before long jobs. See `primus/tools/preflight/` in the repository. + +--- + +### Preset + +Reusable YAML fragment under `primus/configs/` (**module**, **model**, or **platform**). + +--- + +### PrimusRuntime + +Core **orchestrator**: loads configuration, resolves the backend, applies patches, and drives the **trainer lifecycle**. + +--- + +### Primus-SaFE + +**Stability and Fault-tolerance Engine**—external ecosystem component for **cluster management** and resilience. This repository references it in auxiliary tooling but does not include a production integration guide. + +--- + +### Primus-Turbo + +High-performance **operator** library (for example FlashAttention-style kernels, GEMM, collectives, grouped GEMM). + +--- + +### Projection + +Tools that **estimate memory** and **training performance** without requiring a full production cluster. + +--- + +### ROCm + +**Radeon Open Compute**—AMD’s GPU computing platform (drivers, compilers, libraries). + +--- + +### SFT (supervised fine-tuning) + +Supervised fine-tuning that typically **updates all** (or a defined subset of) model parameters, as opposed to adapter-only methods. + +--- + +### SP (sequence parallelism) + +Parallelism that extends tensor-parallel regions to **non-TP** parts of the model to **reduce activation memory**. + +--- + +### TP (tensor parallelism) + +Splits **layer weights** across GPUs within a node (or defined process group). + +--- + +### Transformer engine (TE) + +Library stack for **FP8** and related training optimizations (availability depends on backend and build). + +--- + +### VPP (virtual pipeline parallelism) + +**Interleaved** pipeline parallelism with **multiple virtual stages** per device to improve utilization. + +--- + +### Zero-bubble + +Pipeline scheduling that **reduces or eliminates pipeline bubbles** (idle time between micro-batches). + +--- + +## Related documentation + +- [Overview](./overview.md) +- [Configuration system](../02-user-guide/configuration-system.md) diff --git a/docs/01-getting-started/installation.md b/docs/01-getting-started/installation.md new file mode 100644 index 000000000..ca054197b --- /dev/null +++ b/docs/01-getting-started/installation.md @@ -0,0 +1,266 @@ +# Installation and setup + +This guide covers supported platforms, prerequisites, and how to set up the training environment: **container (recommended)** and **bare metal**, plus **multi-node distributed training** (Slurm recommended). + +--- + +## Supported platforms + + +| Requirement | Notes | +| ----------- | ----------------------------------------------------------------------------------------------------------- | +| **OS** | Linux (ROCm-supported distributions per AMD documentation). | +| **ROCm** | **≥ 7.0** recommended. | +| **GPUs** | AMD Instinct™ **MI300X**, **MI325X**, **MI355X** (or other ROCm-supported Instinct SKUs your site supports). | + + +--- + +## Prerequisites + + +| Prerequisite | Purpose | +| ------------------------------------------------------------- | --------------------------------------------- | +| **AMD Instinct GPUs** | Training and benchmarks execute on GPU. | +| **ROCm drivers and user-space stack** | Required for HIP, RCCL, and ML frameworks. | +| **Docker ≥ 24.0** (or Podman with compatible GPU passthrough) | Container mode and reproducible environments. | +| **git** | Clone the repository and submodules. | + + +### Quick environment checks + +```bash +rocm-smi +docker --version +``` + +`rocm-smi` should list your GPUs; `docker --version` should report **24.0** or newer. + +--- + +## Container setup (recommended) + +AMD publishes training Docker images monthly, providing a consistent, ready-to-run environment optimized for AMD GPUs. It is recommended to use the AMD-published training Docker images together with this Primus-LM repository to run your training jobs. The images support pre-training and post-training workflows with multiple backends including Megatron-LM, TorchTitan, and JAX MaxText, alongside ROCm-optimized components. + +Check the AMD-published training Docker images here: + +- For Megatron-LM and TorchTitan backends: [https://hub.docker.com/r/rocm/primus/tags](https://hub.docker.com/r/rocm/primus/tags) +- For MaxText backend: [https://hub.docker.com/r/rocm/jax-training/tags](https://hub.docker.com/r/rocm/jax-training/tags) + +### 1. Pull the image + +```bash +# For Megatron-LM and TorchTitan backends +docker pull rocm/primus:v26.4 +# For MaxText backend +docker pull rocm/jax-training:maxtext-v26.4-jax0.9.1-te2.12.0 +``` + +### 2. Clone the repository + +Submodules are required for third-party backends and tools: + +```bash +git clone --recurse-submodules https://github.com/AMD-AGI/Primus.git +cd Primus +# checkout the branch for the specific release +git checkout release/v26.4 +git submodule update --init --recursive +``` + +### 3. Run a verification benchmark + +From the repository root: + +```bash +./primus-cli container --image rocm/primus:v26.4 -- \ + benchmark gemm --M 4096 --N 4096 --K 4096 +``` + +A successful run validates the GPU stack and Primus CLI wiring without launching a full training job. + +--- + +## Bare-metal (host) setup + +> **The [container setup](#container-setup-recommended) above is strongly recommended.** The AMD-published training Docker image is the tested, reproducible, and best-supported path. Build the full stack on a bare-metal host only when containers are not an option (for example, due to policy or operational constraints). + +### What to expect + +Reproducing the training environment on the host means building the **same stack the Docker image ships**, mostly from source. This is a **long, build-heavy process** that requires: + +- Several **source-built kernel libraries** (Flash Attention, TransformerEngine, AITER, Primus-Turbo, Grouped GEMM, causal-conv1d, Mamba) compiled against ROCm. +- A **machine with many CPU cores, ample RAM, and tens of GB of free disk**. +- A long time (expect a **multi-hour first build**). + +### What a complete host environment needs + +| Layer | What it provides | How it is installed | +| ----------------------- | -------------------------------------------------------------------------------- | -------------------------------- | +| Kernel/hardware | AMD GPU driver (amdgpu KMD) and device access (`/dev/kfd`, `/dev/dri`) | OS/administrator (root, one-time) | +| OS libraries | Build toolchain and runtime libraries (`g++`, `git`, RDMA, hwloc, etc.) | `apt` (root, one-time) | +| ROCm user-space | `rocm-sdk-devel` + device wheels—**no system-wide ROCm install required** | `pip` (TheRock wheels, in `venv`) | +| Deep learning framework | ROCm-enabled PyTorch (`torch`, `torchvision`, `torchaudio`, `apex`) | `pip` (TheRock wheels, in `venv`) | +| Accelerated kernels | Flash Attention, TransformerEngine, AITER, Primus-Turbo, Grouped GEMM, Mamba | build from source (in `venv`) | +| Multi-node communications | UCX, OpenMPI, rocSHMEM, AMD AINIC—only for distributed (multi-node) training | build from source or `apt` | +| Primus + Python dependencies | Primus, submodules, and training libraries (datasets, transformers, wandb, etc.) | `git` + `pip` (in `venv`) | + +### General approach + +1. **System packages (root, one-time):** install the build toolchain and, for multi-node, the RDMA/networking libraries via `apt`. The GPU kernel driver must already be loaded. +2. **Python virtual environment (no root):** create a `venv`, then install ROCm and PyTorch from AMD's TheRock multi-arch wheels—this replaces a system ROCm install and keeps everything unprivileged. +3. **Build the accelerated kernels from source** against the ROCm in `venv` and your GPU architecture (`gfx942` for MI300X/MI325X, `gfx950` for MI350X/MI355X). +4. **Install Primus and its Python dependencies**, then persist the required environment variables (ROCm paths, `NVTE_*` flags) in your `venv` activation script. +5. **(Optional) Build the multi-node communication stack** (UCX, OpenMPI, rocSHMEM) only if you require RDMA-based distributed training. + +### Detailed instructions + +Follow the full, step-by-step guide here, which includes the exact pinned versions, environment variables, and automated install scripts: + +- **[Bare-metal installation (PyTorch: Megatron-LM / TorchTitan): build the Primus training stack from source (no Docker)](./bare-metal-installation.md)** +- **[Bare-metal installation (JAX / MaxText): build the Primus JAX training stack from source (no Docker)](./bare-metal-installation-jax.md)** + +### Verify + +After the build, validate the environment and run a benchmark directly on the host (no container): + +```bash +./primus-cli direct -- benchmark gemm --M 4096 --N 4096 --K 4096 +``` + +--- + +## Multi-node distributed training (Slurm recommended) + +For training jobs that span **multiple nodes**, we recommend using **[Slurm](https://slurm.schedmd.com/)**. Slurm is a cluster workload manager and job scheduler: it allocates nodes and GPUs, places your job on them, launches one task per node, and injects the topology information (node list, node count, per-node rank) that distributed PyTorch needs. `primus-cli` has a built-in **`slurm` mode** that wraps `srun`/`sbatch` and wires this topology into the training launcher for you. + +### Cluster baseline + +Before launching distributed jobs, ensure every participating node has: + +- The **same software stack**—use the **same container image** on all nodes (recommended), or an identical bare-metal install (see sections above). +- A **shared filesystem** for code, datasets, checkpoints, and logs (e.g. NFS/Lustre), mounted at the same path on every node. +- **Working inter-node networking**: for best performance, use a high-speed RDMA fabric (InfiniBand, RoCE, or AMD AINIC) and ensure RCCL can select the right interface. + +### Setting up Slurm + +Setting up Slurm itself is a cluster-administration task and is outside Primus's scope. Follow the official documentation: + +- [Slurm Quick Start (users)](https://slurm.schedmd.com/quickstart.html) +- [Slurm Quick Start Administrator Guide (install & configure)](https://slurm.schedmd.com/quickstart_admin.html) + +Once `sinfo` and `srun` work on your login node, Primus can submit jobs to it. If you don't administer the cluster, your site administrator typically provides the partition, account, and reservation names you need. + +### Launching with `primus-cli slurm` + +The Slurm wrapper uses a single `--` separator: + +- **Before the `--`**: put the launcher (`srun` or `sbatch`, default `srun`) and Slurm flags (`-N`, `-p`, `--nodelist`, `--account`, `--qos`, `--reservation`, …). +- **After the `--`**: put the Primus command to run (`train` / `benchmark` / …). It runs inside the container image (see [Selecting the container image](#selecting-the-container-image) below). + +```bash +cd /path/to/Primus + +# Pretrain on 2 nodes via srun +./primus-cli slurm srun -N 2 \ + -- train pretrain \ + --config examples/megatron/configs/MI300X/llama3.1_8B-BF16-pretrain.yaml +``` + +Add the global `--dry-run` flag before the launcher to print the exact command without executing it: + +```bash +./primus-cli --dry-run slurm srun -N 2 \ + -- train pretrain --config .yaml +``` + +> See `runner/README.md` and the [CLI reference](../02-user-guide/cli-reference.md) for the full set of launcher flags and scenarios. + +#### Selecting the container image + +The image used for the job is resolved according to the following priority order (highest first): + +1. **`DOCKER_IMAGE` environment variable**—overrides everything else. This is the simplest way to switch images and it propagates to all nodes: + +```bash +export DOCKER_IMAGE=rocm/primus:v26.4 +./primus-cli slurm srun -N 2 \ + -- train pretrain --config .yaml +``` + +2. **`--image` CLI flag**—place it immediately after the `--`, before the Primus command (ignored if `DOCKER_IMAGE` is set): + +```bash +./primus-cli slurm srun -N 2 \ + -- --image rocm/primus:v26.4 train pretrain --config .yaml +``` + +3. **Config file default**—`container.options.image` in `runner/.primus.yaml` (or your `~/.primus.yaml`), which is set to `rocm/primus:v26.4` by default. + +### Distributed environment variables + +Primus launches training with `torchrun`, which needs to know the cluster topology. These are the key variables: + + +| Variable | Role | Default | +| --------------- | -------------------------------------------------------- | ----------- | +| `MASTER_ADDR` | Hostname/IP of rank 0; all ranks rendezvous here. | `localhost` | +| `MASTER_PORT` | Port on the master used for rendezvous. | `1234` | +| `NNODES` | Number of nodes in the job. | `1` | +| `NODE_RANK` | Index of this node (0-based, unique per node). | `0` | +| `GPUS_PER_NODE` | GPUs (processes) to launch per node. | `8` | + +The total number of training processes (world size) is `NNODES × GPUS_PER_NODE`. + +**Under Slurm, the values of these variables are derived automatically.** `primus-cli slurm` reads Slurm's own variables and sets the Primus ones for you: `NNODES` from `SLURM_NNODES`, `NODE_RANK` from `SLURM_NODEID`, and `MASTER_ADDR` from the first host in `SLURM_NODELIST` (with `MASTER_PORT` defaulting to `1234`). You normally only need to set `GPUS_PER_NODE` if your nodes don't have 8 GPUs. You can still override `MASTER_PORT` (e.g. to avoid a port clash) via `--env`. + +### Without Slurm: Kubernetes or `parallel-ssh` + +Slurm is recommended but not required. The mechanism underneath is simple: **set the same distributed environment variables on every node, point them all at the same `MASTER_ADDR`, give each node a unique `NODE_RANK`, and run the same `primus-cli direct` command on each node.** Any tool that can run a command across nodes works—for example Kubernetes (e.g. a `PyTorchJob` / indexed Job) or `parallel-ssh`/`pdsh`. + +For a 2-node job you would run, on the master (rank 0): + +```bash +export NNODES=2 GPUS_PER_NODE=8 NODE_RANK=0 MASTER_ADDR= MASTER_PORT=1234 +./primus-cli direct -- train pretrain --config .yaml +``` + +and on the worker (rank 1), the same command with `NODE_RANK=1` and the same `MASTER_ADDR`: + +```bash +export NNODES=2 GPUS_PER_NODE=8 NODE_RANK=1 MASTER_ADDR= MASTER_PORT=1234 +./primus-cli direct -- train pretrain --config .yaml +``` + +With Kubernetes, inject these as container environment variables (deriving `NODE_RANK` from the pod's index); with `parallel-ssh`, pass them per host. The training command itself is identical on every node. + +### Other important considerations + +- **Cluster validation.** Run the built-in preflight check across your nodes before a long job: `./primus-cli slurm srun -N -- preflight`. +- **Networking/RCCL.** On RDMA fabrics, make sure the correct interface is selected (Primus should auto-detect this; if it doesn't, set `NCCL_SOCKET_IFNAME` / `NCCL_IB_HCA`). Use `NCCL_DEBUG=INFO` (passed via `--env`) to diagnose hangs at startup. +- **RDMA limits.** High-performance networking usually needs locked-memory limits raised (`ulimit -l unlimited`) and sometimes hugepages—configured by your admin. +- **`MASTER_PORT` must be free** on the master node and reachable from all workers; firewalls between nodes will cause rendezvous timeouts. +- **Hugging Face access.** If your configuration downloads gated models or tokenizers, export `HF_TOKEN` (and ensure it is propagated to all nodes and into the containers). + +--- + +## Post-installation verification checklist (for all setup approaches) + + +| Step | Check | +| -------------------- | ------------------------------------------------------------------------------------------------------- | +| **ROCm** | `rocm-smi` shows expected GPUs and no driver errors. | +| **Container engine** | `docker run --rm ... rocm/primus:v26.4` (or your site’s GPU test) succeeds. | +| **GEMM benchmark** | `./primus-cli` **container** or **direct** benchmark completes (see sections above). | +| **Preflight** | Run preflight diagnostics: `./primus-cli direct -- preflight` (single node) or `./primus-cli slurm srun -N -- preflight` (cluster). | + + +If your training pulls models or tokenizers from Hugging Face Hub, configure tokens (for example `HF_TOKEN`) in the environment or container flags as required by your configuration. + +--- + +## Related documentation + +- [Overview](./overview.md) +- [Quickstart](./quickstart.md) +- [CLI reference](../02-user-guide/cli-reference.md) diff --git a/docs/01-getting-started/overview.md b/docs/01-getting-started/overview.md new file mode 100644 index 000000000..3bbabe351 --- /dev/null +++ b/docs/01-getting-started/overview.md @@ -0,0 +1,137 @@ +# Primus overview + +## Executive summary + +**Primus** is a YAML-driven training framework for large-scale foundation model work on AMD GPUs. It targets **machine learning engineers**, **researchers**, and **platform/operations teams** who need reproducible, multi-backend training pipelines on AMD Instinct™ hardware. This project, also referred to as **Primus-LM**, serves as the training component within the broader [Primus ecosystem](#primus-ecosystem). + +**Repository:** [https://github.com/AMD-AGI/Primus](https://github.com/AMD-AGI/Primus) + +--- + +## What Primus provides + +| Area | Description | +|------|-------------| +| **Multi-backend training** | One workflow surface over Megatron-LM, TorchTitan, JAX MaxText, Megatron Bridge, and HummingbirdXT. | +| **Unified CLI** | `primus-cli` with **direct** (bare metal), **container** (Docker/Podman), and **slurm** (cluster) execution modes. | +| **YAML-driven configuration** | Experiment, model, module, and platform presets composed from reusable fragments under `primus/configs/`. | +| **Benchmark suite** | Built-in benchmarks (for example, GEMM) for quick hardware and stack validation. | +| **Preflight diagnostics** | Cluster-oriented checks for host, GPU, and network health before long jobs. | +| **Performance projection** | Tools to estimate memory use and throughput without occupying a full cluster. | + +Workflows span **pretraining** and **post-training** (including SFT and LoRA). Some Megatron configuration files expose RL-related parameters, but reinforcement-learning workflows are outside the scope of this documentation set: they are not part of the tested, supported paths described here, and this documentation does not cover how to run them. + +--- + +## Primus ecosystem + +The training component (Primus-LM) serves as the layer between the stability and platform services above it and the low-level operator libraries below it: + +``` + +------------------+ + | Primus-SaFE | + | (stability / | + | cluster mgmt) | + +--------+---------+ + | + +--------v---------+ + | Primus-LM | + | (this repo: | + | training) | + +--------+---------+ + | + +--------v---------+ + | Primus-Turbo | + | (operators / | + | kernels) | + +------------------+ +``` + +- **Primus-SaFE**: Stability and fault-tolerance oriented cluster management referenced by auxiliary tooling (not documented here). +- **Primus-LM**: Training orchestration, backends, CLI, and configurations (maintained in the [AMD-AGI/Primus](https://github.com/AMD-AGI/Primus/) repository). +- **Primus-Turbo**: High-performance operators (for example, FlashAttention-style kernels, GEMM, and collectives). + +--- + +## Supported backends + +| Backend | Typical use | +|---------|-------------| +| **Megatron-LM** | Broadest model coverage; default for many GPT-style and MoE recipes. | +| **TorchTitan** | PyTorch-native large-model training paths. | +| **JAX MaxText** | JAX/Flax training stacks aligned with MaxText. | +| **Megatron Bridge** | Post-training and bridge workflows on top of Megatron-related stacks. | +| **HummingbirdXT** | Additional integrated training path when enabled by your deployment. | + +Backend choice is specified in the configuration YAML and resolved through Primus’s adapter layer (see [glossary](./glossary.md)). + +--- + +## Supported hardware and stack + +| Item | Requirement | +|------|----------------| +| **GPUs** | AMD Instinct™ **MI300X**, **MI325X**, **MI355X** | +| **Platform** | **ROCm** (version **≥ 7.0** recommended) | +| **Container image (reference)** | `docker.io/rocm/primus:v26.4` | + +Exact kernel and driver packages should match AMD’s documentation for your GPU SKU and ROCm release. + +--- + +## Key dependencies + +Primus depends on the following categories of software (the list is non-exhaustive): + +| Category | Examples | +|----------|----------| +| **Framework** | PyTorch (Megatron-LM, TorchTitan paths); JAX/Flax (MaxText path). | +| **AMD stack** | ROCm, RCCL, HipBLASLt (GEMM), GPU drivers. | +| **Execution** | Docker or Podman for container mode; Slurm for cluster mode. | +| **Observability & tooling** | **loguru** (logging), **Weights & Biases** (`wandb`) and other optional trackers (see `requirements.txt`). | + +Install specifics are covered in [Installation and setup](./installation.md). + +--- + +## Runtime model + +At a high level, a run follows this pipeline: + +1. **YAML configuration** defines work group, modules, model preset, and overrides. +2. **`primus-cli`** selects execution mode (direct, container, or slurm) and forwards to the runner. +3. **Backend adapter** maps the resolved configuration to the target framework (Megatron-LM, TorchTitan, and so on). +4. **Distributed launch** typically uses **`torchrun`** (or the backend’s equivalent) to start workers across GPUs and nodes. + +For CLI shape and options, see [Quickstart](./quickstart.md) and [CLI reference](../02-user-guide/cli-reference.md). + +--- + +## Repository layout (top level) + +The following table shows the top-level layout of the Primus project repository, [AMD-AGI/Primus](https://github.com/AMD-AGI/Primus/): + +| Path | Role | +|------|------| +| `primus/` | Core library: configurations, runtime, trainers, backend adapters, tools (including preflight). | +| `runner/` | CLI implementation, helpers, hooks, and launch glue. | +| `examples/` | End-to-end example YAML and recipes per backend and GPU SKU. | +| `docs/` | Project documentation (this documentation set). | +| `tests/` | Automated tests. | +| `tools/` | Auxiliary scripts and utilities. | +| `benchmark/` | Benchmark drivers and related assets. | +| `third_party/` | Vendored or submodule dependencies. | + +--- + +## Next steps + +- [Installation and setup](./installation.md): ROCm, Docker, pip, and Slurm setup. +- [Quickstart](./quickstart.md): run a minimal training job in minutes. +- [Glossary](./glossary.md): terms used across Primus documentation. + +--- + +## Licensing + +Primus is distributed under the terms described in the project's `LICENSE` file and `README`. If you encounter differing license references between the `README` and the repository root `LICENSE` file, treat licensing as project-specific: confirm the intended terms with the maintainers and your own compliance process before redistributing. diff --git a/docs/01-getting-started/quickstart.md b/docs/01-getting-started/quickstart.md new file mode 100644 index 000000000..bb9a4789a --- /dev/null +++ b/docs/01-getting-started/quickstart.md @@ -0,0 +1,171 @@ +# Quickstart (about five minutes) + +This guide runs a **small Megatron-LM pretraining example** with **mock data** so you can validate the stack without preparing a full dataset. The same sample YAML works across **direct**, **container**, and **Slurm** modes. + +> **Recommended: run this example inside AMD-published training Docker images.** AMD publishes ready-to-run ROCm training images (`rocm/primus` for Megatron-LM and TorchTitan, `rocm/jax-training` for MaxText) with all dependencies and the complete training software stack already installed and validated. Using them means you don't have to build or tune the environment yourself, and—most important for multi-node jobs—**every node runs an identical, tested environment**. That consistency helps avoid version-skew and configuration issues that often occur with per-host installations. Host-based installation is supported, but is recommended only for advanced users (see [Installation and setup](./installation.md)). + +See [Installation and setup](./installation.md) for prerequisites and environment setup. + +--- + +## Prerequisites + +- AMD ROCm drivers (version ≥ 7.0 recommended) +- Docker (version ≥ 24.0) with ROCm support +- ROCm-compatible AMD GPUs (e.g., Instinct MI300 series) +- Proper permissions for Docker and GPU device access + +--- + +## Option 1: Clone the repository and run training in a container (recommended) + +### Step 1: Pull the container image + +Check the AMD published training Docker images: + +- Megatron-LM and TorchTitan backends: +- MaxText backend: + +```bash +# For Megatron-LM and TorchTitan backends +docker pull rocm/primus:v26.4 +# For MaxText backend +docker pull rocm/jax-training:maxtext-v26.4-jax0.9.1-te2.12.0 +``` + +### Step 2: Clone the repository + +```bash +git clone --recurse-submodules https://github.com/AMD-AGI/Primus.git +cd Primus +# checkout the branch for the specific release +git checkout release/v26.4 +git submodule update --init --recursive +``` + +### Step 3: Run training inside container + +Run the training from the repository root. If your configuration downloads weights or tokenizers from Hugging Face Hub, pass `HF_TOKEN` into the container: + +```bash +./primus-cli container --image rocm/primus:v26.4 \ + --env HF_TOKEN="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ + -- train pretrain \ + --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml +``` + +--- + +## Option 2: Install Primus from a wheel and run training in a container + +### Step 1: Install Primus as a Python package + +Install Primus in a virtual environment: + +```bash +python -m venv primus-env +source primus-env/bin/activate +pip install "primus==26.4.0" --no-deps --extra-index-url https://amd-agi.github.io/Primus/simple/ +``` + +> **Note:** This installs only the Primus CLI into your virtual environment (under `site-packages`), without other dependencies. Third-party submodules are downloaded on the first run of the container, and the complete training software stack is provided in the AMD-published Docker images. You can launch `primus-cli` from any directory. + +### Step 2: Run training in a container using the pip-installed Primus + +```bash +primus-cli container --image rocm/primus:v26.4 \ + --env HF_TOKEN="hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" \ + --volume /path/to/your/data:/data -- --log_file /data/run.log \ + -- train pretrain --config /data/your/config.yaml +``` + +> **Note:** `--volume` mounts a local data directory into the container. `--log_file` writes the training log there; if omitted, logs go to the Primus install directory (`site-packages/primus/logs` by default). + +--- + +## Expected output + +You should see the backend initialize distributed processes, load the training configuration, and emit **iteration-level logs** (with loss, throughput, step index, etc.). Exact fields depend on the backend and logging configuration; a typical pattern resembles: + +``` +... [INFO] starting training ... +... iteration 1 | loss: 10.xxx | ... +... iteration 2 | loss: 9.xxx | ... +``` + +Let the job run briefly to confirm stability; stop with `Ctrl+C` when satisfied. + +--- + +## Same configuration, three execution modes + +Use one configuration file: `examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml`. + +| Mode | Example command | +|------|------------------| +| **Container** | `./primus-cli container --image rocm/primus:v26.4 -- train pretrain --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml` | +| **Direct** | `./primus-cli direct -- train pretrain --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml` | +| **Slurm** | `./primus-cli slurm srun -N ... -- train pretrain --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml` | + +Replace `` and Slurm resource flags with values appropriate for your cluster. **Container** and **Slurm** runs execute inside a Docker image (Slurm dispatches through the container launcher on each node); **Direct** runs execute inside a Docker container (you start the container yourself and run the command) or directly on the host if the training environment is already set up. + +> **Multi-node networking:** Primus auto-detects RDMA settings (`NCCL_IB_HCA`, `NCCL_SOCKET_IFNAME`, …) on each node. If auto-detection selects the wrong NIC, or your fabric needs specific values (RoCE `NCCL_IB_GID_INDEX`, AMD AINIC, etc.), override them via `--env` or your config. See [Multi-node networking](../04-technical-guides/multi-node-networking.md). + +### Selecting the container image + +For container and Slurm runs, Primus resolves which Docker image to use in the following order (**highest priority first**): + +1. **`DOCKER_IMAGE` environment variable**—if set, it overrides every other source (including `--image`): + +```bash +export DOCKER_IMAGE=rocm/primus:v26.4 +./primus-cli container -- train pretrain --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml +``` + +2. **`--image` command-line argument**—the usual per-run override, passed as a container mode argument (before `--`): + +```bash +./primus-cli container --image rocm/primus:v26.4 -- train pretrain --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml +``` + +3. **`image` field in a config file** (`container.options.image`)—used when neither of the above is set: + +```yaml +container: + options: + image: "rocm/primus:v26.4" +``` + +Primus loads a **single** config file—the first that exists among `--config `, then `~/.primus.yaml`, then the shipped `runner/.primus.yaml` (these files are **not** merged). Because `runner/.primus.yaml` ships with a default image, a bare `./primus-cli container -- ...` works out of the box. + +>**Check the logs to make sure actual image being used is the one you wanted.** + + +--- + +## Command structure + +`primus-cli` parses **global options**, a **mode** (`direct`, `container`, `slurm`, …), optional **mode-specific arguments**, then a **`--` separator** followed by the **subcommand and its arguments** (for example `train` or `benchmark`). + +``` +primus-cli [global-options] [mode-args] -- [command-args...] +``` + +Example: + +```text +primus-cli container --image rocm/primus:v26.4 -- train pretrain --config path/to/experiment.yaml + ^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + mode mode args command + args +``` + +--- + +## Next steps + +| Topic | Document | +|-------|----------| +| Full CLI flags and subcommands | [CLI reference](../02-user-guide/cli-reference.md) | +| YAML presets, overrides, and composition | [Configuration system](../02-user-guide/configuration-system.md) | +| Pretraining workflows and backend notes | [Pretraining workflows](../02-user-guide/pretraining.md) | +| Terminology | [Glossary](./glossary.md) | diff --git a/docs/02-user-guide/README.md b/docs/02-user-guide/README.md new file mode 100644 index 000000000..3c34cb574 --- /dev/null +++ b/docs/02-user-guide/README.md @@ -0,0 +1,20 @@ +# User guide + +Core workflows and day-to-day usage. + +- [Primus tools](primus-tools.md): start here—an at-a-glance catalog of all Primus tools and ecosystem projects with how-to starting points +- [CLI reference](cli-reference.md): `primus-cli` modes, flags, and subcommands +- [Configuration system](configuration-system.md): YAML configuration model, presets, overrides, inheritance +- [Pretraining](pretraining.md): pretraining **concepts**: backends, YAML structure, parallelism, configuration inventory +- [Backend training recipes](training-recipes.md): pretraining **commands**: copy-paste, GPU-arch-specific run commands +- [Post-training](posttraining.md): SFT and LoRA fine-tuning via Megatron Bridge +- [Node-smoke test instruction](node-smoke-test-instruction.md): screen a cluster fast and exclude bad nodes before launching a real training job +- [Preflight](preflight.md): cluster diagnostics and environment validation +- [Run preflight without a container](preflight-without-container.md): run cluster-diagnostic tool directly on the host +- [Benchmarking](benchmarking.md): GEMM, RCCL, and dense-GEMM benchmark suites +- [Projection](projection.md): memory and performance projection tools +- [Tuning agent](tuning-agent.md): LLM-driven search for an optimal training configuration (uses projection as an oracle) + +--- + +[← Documentation home](../README.md) diff --git a/docs/02-user-guide/benchmarking.md b/docs/02-user-guide/benchmarking.md new file mode 100644 index 000000000..722865593 --- /dev/null +++ b/docs/02-user-guide/benchmarking.md @@ -0,0 +1,204 @@ +# Benchmark suite + +Primus ships microbenchmarks for GPU compute and distributed communication. They are exposed as the `benchmark` subcommand of the Primus CLI. Use them to validate a node or cluster before long training jobs. + +**Implementation:** `primus/cli/subcommands/benchmark.py` (initializes distributed execution, runs the selected suite, then finalizes). + +Related documentation: [Preflight diagnostics](./preflight.md) (broader cluster checks), [Memory and performance projection](./projection.md) (training-scale estimates), [Installation](../01-getting-started/installation.md) (environment setup). + +--- + +## Overview and command syntax + +```bash +primus-cli [global-options] [mode-args] -- benchmark [suite-specific-args] +``` + +- **``** is typically `direct`, `container`, or `slurm` so that `WORLD_SIZE`, `RANK`, `MASTER_ADDR`, and related variables are set consistently. +- **`benchmark`** runs inside the Primus Python CLI; the runner wires up the process environment the same way as training. + +The CLI also registers an `attention` suite; the subsections below cover **`gemm`**, **`gemm-dense`**, **`gemm-deepseek`**, **`strided-allgather`**, and **`rccl`**. + +--- + +## Quick start + +Single-node GEMM: + +```bash +primus-cli direct -- benchmark gemm --M 4096 --N 4096 --K 4096 --dtype bf16 --duration 10 +``` + +Multi-node RCCL on Slurm: + +```bash +primus-cli slurm srun -N 4 -- benchmark rccl --op all_reduce --min-bytes 1M --max-bytes 128M +``` + +--- + +## Suite reference + +### `gemm` + +Single-shape general matrix multiply (GEMM) microbenchmark. + +| Argument | Description | +|----------|-------------| +| `--M`, `--N`, `--K` | Matrix dimensions (defaults: 4096 / 4096 / 4096). | +| `--trans_a` | Transpose the A matrix. | +| `--trans_b` | Transpose the B matrix. | +| `--dtype` | `bf16`, `fp16`, `fp32`, or `fp8` (`fp8` requires torchao). Default: `bf16`. | +| `--duration` | Run duration in seconds (default: 10). | +| `--output-file` | Destination for results (`.md`, `.csv`, `.tsv`, `.jsonl`, `.jsonl.gz`). Default: `./gemm_report.md`. Use `-` or omit for Markdown on stdout. | + +**Example** + +```bash +primus-cli direct -- benchmark gemm --M 8192 --N 8192 --K 8192 --dtype bf16 --duration 10 --output-file ./gemm_report.md +``` + +--- + +### `gemm-dense` + +Dense GEMM workload using Llama-like shape parameters (model-derived GEMMs). + +| Argument | Description | +|----------|-------------| +| `--model` | Optional label (for example `Llama3.1_8B`). | +| `--seqlen` | Sequence length (default: 2048). | +| `--hidden-size` | Hidden size (default: 4096). | +| `--intermediate-size` | FFN intermediate size (default: 11008). | +| `--num-attention-heads` | Attention heads (default: 32). | +| `--num-key-value-heads` | KV heads (default: 32). | +| `--head-dim` | Per-head dimension (default: 128). | +| `--vocab-size` | Vocabulary size (default: 32000). | +| `--dtype` | `bf16`, `fp16`, `fp32`, or `fp8` (`fp8` requires torchao). Default: `bf16`. | +| `--mbs` | Microbatch size (default: 1). | +| `--duration` | Seconds per shape (default: 3). | +| `--output-file` | Report path (default: `./gemm-dense_report.md`). | + +**Example** + +```bash +primus-cli direct -- benchmark gemm-dense --model Llama3.1_8B --seqlen 4096 --dtype bf16 +``` + +--- + +### `gemm-deepseek` + +Dense GEMM workload using DeepSeek-style shapes (MoE / MLA-related dimensions). + +| Argument | Description | +|----------|-------------| +| `--model` | Label (for example `Deepseek_V2`, `Deepseek_V3`). | +| `--seqlen` | Sequence length (default: 4096). | +| `--hidden-size` | Hidden size (default: 4096). | +| `--intermediate-size` | Dense FFN intermediate (default: 12288). | +| `--kv-lora-rank` | KV LoRA rank (default: 512). | +| `--moe-intermediate-size` | MoE expert intermediate (default: 1536). | +| `--num-attention-heads` | Attention heads (default: 64). | +| `--num-experts-per-tok` | Experts per token (default: 6). | +| `--n-routed-experts` | Number of routed experts (default: 128). | +| `--n-shared-experts` | Shared experts (default: 2). | +| `--q-lora-rank` | Optional Q LoRA rank. | +| `--qk-nope-head-dim`, `--qk-rope-head-dim`, `--v-head-dim` | Head dimensions for MLA-style attention (defaults: 128 / 64 / 128). | +| `--vocab-size` | Vocabulary size (default: 128256). | +| `--dtype` | `bf16` or `fp16` (default: `bf16`). | +| `--mbs` | Microbatch size (default: 1). | +| `--duration` | Seconds per shape (default: 3). | +| `--output-file` | Report path (default: `./gemm-deepseek_report.md`). | +| `--append` | Append to an existing report instead of overwriting. | + +**Example** + +```bash +primus-cli direct -- benchmark gemm-deepseek --model Deepseek_V3 --dtype bf16 --append +``` + +--- + +### `strided-allgather` + +Strided all-gather microbenchmark (useful for multi-rank communication patterns). + +| Argument | Description | +|----------|-------------| +| `--sizes-mb` | Comma-separated message sizes in MB per rank (default: `64,128,256`). | +| `--stride` | Rank stride for group formation (default: 8). | +| `--parallel` | Run multiple groups’ all-gathers in parallel. | +| `--iters` | Timed iterations per size (default: 50). | +| `--warmup` | Warmup iterations per size (default: 10). | +| `--dtype` | `fp16`, `bf16`, or `fp32` (default: `bf16`). | +| `--backend` | `nccl`, `gloo`, or `mpi` (default: `nccl`). | + +**Example** + +```bash +primus-cli slurm srun -N 2 -- benchmark strided-allgather --sizes-mb 64,128 --stride 8 --iters 50 +``` + +--- + +### `rccl` + +RCCL collective benchmark: sweeps message sizes and reports bandwidth and latency statistics. + +| Argument | Description | +|----------|-------------| +| `--op` | One or more of: `all_reduce`, `broadcast`, `reduce_scatter`, `all_gather`, `alltoall` (default: `all_reduce`). | +| `--sizes` | Explicit size list (for example `1K,2K,4K,8K,1M`). Overrides generated sweep. | +| `--min-bytes` | Minimum message size for generated sweep (default: `1K`). | +| `--max-bytes` | Maximum message size (default: `128M`). | +| `--num-sizes` | Number of points in generated sweep (default: 12). | +| `--scale` | `log2` or `linear` for generated sweeps (default: `log2`). | +| `--dtype` | `bf16`, `fp16`, or `fp32` (default: `bf16`). | +| `--warmup` | Warmup iterations (default: 20). | +| `--iters` | Timed iterations (default: 100). | +| `--repeat` | Repeat each `(op, size)` for stability (default: 1). | +| `--aggregate-repeat` | Emit an extra summary row aggregating repeat runs. | +| `--check` | Enable lightweight correctness checks. | +| `--output-file` | Report path (`.md`, `.csv`, `.tsv`, `.jsonl`, `.jsonl.gz`; default: `./rccl_report.md`). | +| `--append` | Append instead of overwrite. | +| `--per-rank` | Per-rank summary lines. | +| `--per-rank-file` | Path for per-rank stats (if empty, derived from `--output-file` with `_rank` suffix). | +| `--per-iter-trace` | Emit per-iteration trace (can be large). | +| `--trace-file` | Trace output path (if empty, derived from `--output-file`). | +| `--trace-limit` | Max iterations to record per `(op, size)`; `0` means all. | +| `--trace-ops` | Comma-separated ops to include in trace (empty = all). | +| `--trace-sizes` | Comma-separated sizes to include in trace (empty = all). | +| `--cluster` | Label for the report preamble. Defaults to `$PRIMUS_CLUSTER`, falling back to a built-in placeholder (`amd-aig-poolside`) when it is unset—set `PRIMUS_CLUSTER` or pass `--cluster` to record your own cluster name. | + +**Example** + +```bash +primus-cli slurm srun -N 4 -- benchmark rccl --op all_reduce --min-bytes 1M --max-bytes 128M --dtype bf16 +``` + +--- + +## Understanding results + +- **GEMM suites** emit throughput-oriented metrics suitable for comparing dtypes, shapes, and durations across runs. Keep `duration` long enough to smooth variance on shared clusters. +- **`rccl`** reports collective latency and bandwidth across a size sweep; use it to verify inter-node behavior and to compare against expected NIC bandwidth. +- **Markdown / CSV / TSV / JSONL** output formats support post-processing in notebooks or CI; gzip JSONL is supported for large traces. + +--- + +## Tips + +1. **Distributed initialization:** If jobs hang or report uninitialized distributed state, launch through `primus-cli` (`direct` / `container` / `slurm`) rather than calling Python entrypoints manually without the right environment. +2. **Paths:** Prefer **absolute** paths for `--output-file` when using containers or Slurm so the working directory matches your expectations. +3. **Multi-node:** Use your scheduler integration (`primus-cli slurm …`) so rank and address assignment matches your cluster. +4. **Full cluster validation:** Combine targeted `benchmark` runs with [Preflight](./preflight.md) for host, GPU, network, and integrated perf checks. + +--- + +## Related documentation + +- [Preflight diagnostics](./preflight.md) +- [Memory and performance projection](./projection.md) +- [Post-training workflows](./posttraining.md) +- [Installation](../01-getting-started/installation.md) diff --git a/docs/02-user-guide/cli-reference.md b/docs/02-user-guide/cli-reference.md new file mode 100644 index 000000000..e7928ba8f --- /dev/null +++ b/docs/02-user-guide/cli-reference.md @@ -0,0 +1,233 @@ +# CLI reference + +This section describes the unified Primus launcher (`runner/primus-cli`) and how it invokes the Python CLI (`primus/cli/main.py`). For deeper background, see [CLI architecture](../06-developer-guide/cli-architecture.md). + +--- + +## Command structure + +```text +primus-cli [global-options] [mode-args] -- [command] +``` + +- **Global options** go before the mode name and they affect configuration loading and logging for the whole run. +- **Mode** is one of `direct`, `container`, or `slurm`. +- **`--` (required)** separates launcher options from the Primus Python CLI. Everything after the first `--` is passed to `primus/cli/main.py` (or another script if you override it in direct mode). + +From the repository root, invoke the launcher as `./runner/primus-cli` (or install or link it as `primus-cli` on your `PATH`). + +--- + +## Global options + +These flags are parsed in `runner/primus-cli` before the mode name is read and passed on to `runner/primus-cli-.sh`. + +| Option | Description | +| --- | --- | +| `--config FILE` | Load a YAML file for launcher defaults (see [Configuration precedence](#configuration-precedence-launcher-yaml)). | +| `--debug` | Verbose logging; sets `PRIMUS_LOG_LEVEL=DEBUG`. | +| `--dry-run` | Print the command that would run and exit without executing the mode script. | +| `--version` | Print the CLI version and exit. | +| `-h`, `--help` | Show top-level usage and exit. | + +Mode-specific help: + +```bash +./runner/primus-cli direct --help +./runner/primus-cli container --help +./runner/primus-cli slurm --help +``` + +Primus Python CLI help (after `--`): + +```bash +./runner/primus-cli direct -- --help +./runner/primus-cli direct -- train --help +./runner/primus-cli direct -- benchmark --help +``` + +--- + +## Direct mode + +Run training, benchmarks, or diagnostics on the current host (or inside an environment you already prepared). GPU-specific tuning is applied via `runner/helpers/envs/.sh` when present. + +### Syntax + +```bash +primus-cli direct [options] -- +``` + +### Options + +| Option | Description | +| --- | --- | +| `--config FILE` | Launcher YAML (same resolution as [global `--config`](#configuration-precedence-launcher-yaml)). | +| `--debug` | Debug logging for the direct launcher. | +| `--dry-run` | Show the resolved command that would be launched without running training. | +| `--single` | Run with `python3` instead of `torchrun` (single process). | +| `--script PATH` | Python entry script (default: `primus/cli/main.py`). | +| `--env KEY=VALUE` | Set an environment variable before launch (repeatable). A path without `=` is treated as an env file (`--env_file`), loaded later in the launch sequence. | +| `--patch script.sh` | Run a shell snippet before the main script (repeatable). | +| `--log_file PATH` | Redirect logs to a file. | +| `--numa` | Force NUMA binding on. | +| `--no-numa` | Force NUMA binding off. | + +### Distributed environment variables + +For multi-node or multi-process runs, set these via `export` or `--env`: + +| Variable | Role | Typical default | +| --- | --- | --- | +| `NNODES` | Number of nodes | `1` | +| `NODE_RANK` | Rank of this node | `0` | +| `GPUS_PER_NODE` | GPUs per node | `8` (see `runner/.primus.yaml` `direct.gpus_per_node`) | +| `MASTER_ADDR` | Hostname or IP of rank 0 | `localhost` | +| `MASTER_PORT` | TCP port for the process group | `1234` | + +--- + +## Container mode + +Run the same Python CLI inside Docker or Podman with ROCm-oriented defaults from `runner/.primus.yaml`. + +### Syntax + +```bash +primus-cli container [options] -- +``` + +### Common options + +| Option | Description | +| --- | --- | +| `--image NAME` | Image tag (default from config: `rocm/primus:v26.4`). | +| `--volume HOST[:CONTAINER]` | Bind mount (repeatable). | +| `--env KEY=VALUE` | Pass into the **inner** `primus-cli direct` as `--env` (repeatable). | +| `--device PATH` | Extra device nodes (repeatable; defaults include GPU/RDMA devices). | +| `--name`, `--user`, `--network`, `--ipc` | Standard container runtime options. | +| `--clean` | Remove all containers before launch. | +| `--cpus N` | CPU limit. | +| `--memory SIZE` | Memory limit (e.g. `128G`). | +| `--shm-size SIZE` | Shared memory size. | +| `--gpus N` | GPU limit (when using a runtime that supports this flag). | + +### Auto-mounted devices + +When using `runner/.primus.yaml`, the default container section includes: + +- `/dev/kfd`—ROCm kernel fusion driver +- `/dev/dri`—GPU render nodes +- `/dev/infiniband`—InfiniBand character devices (when present) + +### Environment forwarding + +`container.options.env` in `runner/.primus.yaml` lists **names** that are forwarded into the container as inner `--env` arguments when the variable is set in the host environment (for example `MASTER_ADDR`, `HF_TOKEN`, `NCCL_SOCKET_IFNAME`). The container script also auto-forwards host variables whose names start with `PRIMUS_`, `NCCL_`, `RCCL_`, `GLOO_`, `IONIC_`, or `HIPBLASLT_` when not already listed. + +--- + +## Slurm mode + +Launch distributed jobs with `srun` or `sbatch`. The Slurm launcher builds `srun` or `sbatch` flags, merges them with `slurm.*` entries from the loaded YAML, then runs `runner/primus-cli-slurm-entry.sh` on allocated nodes. + +### Syntax + +```text +primus-cli slurm [--config FILE] [--debug] [--dry-run] [srun|sbatch] [SLURM_FLAGS...] -- +``` + +| Part | Meaning | +| --- | --- | +| First `--` | Separates Slurm launcher flags from the Primus Python CLI command (for example `train pretrain ...`). | +| Default launcher | If you omit `srun` and `sbatch`, **`srun` is used** (`LAUNCH_CMD` in `runner/primus-cli-slurm.sh`). | + +### Examples + +```bash +# Interactive multi-node training +./runner/primus-cli slurm srun -N 4 -p gpu -- train pretrain --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml + +# Batch job +./runner/primus-cli slurm sbatch -N 8 -t 8:00:00 -o train.log -- train pretrain --config exp.yaml +``` + +On each node, `primus-cli-slurm-entry.sh` sets `NNODES`, `NODE_RANK`, `GPUS_PER_NODE`, `MASTER_ADDR`, and `MASTER_PORT` from Slurm and invokes `primus-cli-container.sh` with matching `--env` injections (see `runner/primus-cli-slurm-entry.sh`). Container options such as `--image` should come from `runner/.primus.yaml` or the launcher config file rather than appearing as an inner `container` command after the Slurm separator. + +--- + +## Python subcommands (after `--`) + +These run under `primus/cli/main.py` unless you change `--script` in direct mode. + +| Subcommand | Purpose | +| --- | --- | +| `train pretrain --config ` | Pretraining (Megatron-LM, TorchTitan, MaxText, Megatron Bridge, etc., per configuration YAML). | +| `train posttrain --config ` | Post-training (SFT or LoRA-style workflows; same top-level flags as pretrain in the parser). | +| `benchmark [args]` | Performance microbenchmarks (see table below). | +| `preflight [--host] [--gpu] [--network] [--perf-test]` | Cluster and node diagnostics. | +| `projection memory --config ` | Memory estimation from a merged config. | +| `projection performance --config ` | Performance projection from a merged config. | +| `projection both --config ` | Single benchmark → both performance and memory projections (cluster sizing). | + +### Benchmark suites + +Implemented in `primus/cli/subcommands/benchmark.py`: + +| Suite | Notes | +| --- | --- | +| `gemm` | General GEMM microbenchmark. | +| `gemm-dense` | Dense GEMM variant. | +| `gemm-deepseek` | DeepSeek-style dense GEMM. | +| `strided-allgather` | Communication microbenchmark. | +| `rccl` | RCCL collective microbenchmark. | + +The same file also registers an `attention` suite for attention microbenchmarks. + +--- + +## Configuration precedence (launcher YAML) + +Resolution is implemented in `runner/lib/config.sh` (functions `resolve_config_file` and `load_config_auto`): + +1. **`--config FILE`** on the command line (if given). +2. **`~/.primus.yaml`** if it exists. +3. **`runner/.primus.yaml`** (system default). + +Within a chosen file, nested keys follow normal YAML structure. Slurm and container scripts merge CLI flags with their sections so that **explicit CLI arguments override file values** where applicable. + +**Note:** This precedence applies to the **shell launcher** YAML. Training YAML merge order for configurations is documented in [Configuration system](configuration-system.md). + +--- + +## Common examples + +| Goal | Example | +| --- | --- | +| Direct pretrain | `./runner/primus-cli direct -- train pretrain --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml` | +| Direct GEMM | `./runner/primus-cli direct -- benchmark gemm --M 4096 --N 4096 --K 4096` | +| Container pretrain | `./runner/primus-cli container --volume /data:/data -- train pretrain --config /data/exp.yaml` | +| Slurm training | `./runner/primus-cli slurm srun -N 4 -- train pretrain --config exp.yaml` | +| Preflight (fast) | `./runner/primus-cli slurm srun -N 4 -- preflight --host --gpu --network` | +| Inspect launch command | `./runner/primus-cli --dry-run direct -- train pretrain --config exp.yaml` | +| Dry-run Slurm | `./runner/primus-cli --dry-run slurm srun -N 2 -- train pretrain --config exp.yaml` | + +--- + +## Exit codes + +From `runner/primus-cli`: + +| Code | Meaning | +| --- | --- | +| 0 | Success | +| 1 | Library or dependency failure | +| 2 | Invalid arguments or configuration | +| 3 | Runtime execution failure | + +--- + +## Related documentation + +- [Getting started: Quickstart](../01-getting-started/quickstart.md): installation and first steps +- [Configuration system](configuration-system.md): YAML configuration model, presets, overrides, inheritance +- [Pretraining](pretraining.md): pretraining workflows and backend notes diff --git a/docs/02-user-guide/configuration-system.md b/docs/02-user-guide/configuration-system.md new file mode 100644 index 000000000..90b8cb6fe --- /dev/null +++ b/docs/02-user-guide/configuration-system.md @@ -0,0 +1,170 @@ +# Configuration system + +Primus experiments are described in YAML. The loader resolves **environment variables**, **`extends:` inheritance**, and **module/model/platform presets** before training starts. This document focuses on the Python configuration pipeline (`primus/core/config/` and `primus/core/launcher/parser.py`). + +**Related documentation** + +| Topic | Location | +| --- | --- | +| CLI launcher and `--config` | [CLI Reference](cli-reference.md) | +| Backend parameter references | [Megatron parameters](../03-configuration-reference/megatron-parameters.md), [TorchTitan parameters](../03-configuration-reference/torchtitan-parameters.md), [MaxText parameters](../03-configuration-reference/maxtext-parameters.md) | + +--- + +## Overview: Three-layer YAML + +A typical experiment ties together: + +1. **Experiment YAML**—your run: identity, workspace, and a `modules` section naming framework presets plus overrides. +2. **Module preset**—training defaults for a backend (optimizer, schedule, parallelism hooks) under `primus/configs/modules//`. +3. **Model preset**—architecture and tokenizer metadata under `primus/configs/models//`. + +All of these are **deep-merged** (see `primus/core/config/yaml_loader.py` and `primus/core/config/merge_utils.py`). A **platform preset** (`primus/configs/platforms/`) maps distributed environment variable names and logging defaults; if omitted, the parser injects `platform_azure.yaml` (see `PrimusParser.parse_platform` in `primus/core/launcher/parser.py`). + +--- + +## Experiment config structure + +```yaml +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:my_experiment} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron # backend name (megatron, torchtitan, maxtext, megatron_bridge, …) + config: pre_trainer.yaml # module preset file under primus/configs/modules// + model: llama3_8B.yaml # model preset file under primus/configs/models// + overrides: # training overrides (deep-merged last) + train_iters: 50 + micro_batch_size: 4 +``` + +Required top-level keys are validated in `PrimusParser.parse_meta_info`: `work_group`, `user_name`, `exp_name`, `workspace`. + +--- + +## Module presets + +- **Location:** `primus/configs/modules//` (for example `primus/configs/modules/megatron/pre_trainer.yaml`). +- **Purpose:** Default training behavior (iterations, batching, optimizer, logging, parallelism-related flags for that backend). +- **Inheritance:** Use `extends:` to compose files in the same directory (or relative paths). Multiple entries merge in order; the **current file wins** on conflicts (`_apply_extends` in `yaml_loader.py`). + +Example chain (excerpt): `pre_trainer.yaml` extends `trainer_base.yaml`, which extends `../module_base.yaml` and other shared fragments (`primus/configs/modules/megatron/trainer_base.yaml`). + +--- + +## Model presets + +- **Location:** `primus/configs/models//` (for example `primus/configs/models/megatron/llama3_8B.yaml`). +- **Purpose:** Architecture dimensions, tokenizer identifiers, and other model metadata. +- **Inheritance:** Same `extends:` mechanism as modules (for example `llama3_8B.yaml` → `llama3_base.yaml` → …). + +--- + +## Platform presets + +- **Location:** `primus/configs/platforms/` (for example `primus/configs/platforms/platform_azure.yaml`). +- **Purpose:** Names of environment variables used for distributed launch (`NNODES`, `NODE_RANK`, `MASTER_ADDR`, …) and defaults such as `master_sink_level` and `workspace`. +- **Default:** If the experiment omits `platform`, the parser sets `config: platform_azure.yaml` (`primus/core/launcher/parser.py`). + +--- + +## Environment variable substitution + +`primus/core/config/yaml_loader.py` expands: + +| Pattern | Behavior | +| --- | --- | +| `${VAR}` | **Required.** Raises if `VAR` is unset. | +| `${VAR:default}` | Uses `default` when `VAR` is unset. | + +After substitution, purely numeric strings may be converted to `int` or `float`. + +--- + +## `extends:` inheritance + +For each YAML file: + +1. Each path in `extends:` is resolved **relative to the directory of the current file**. +2. Presets are loaded recursively (each may have its own `extends:`). +3. Merge order: earlier presets in the list are merged first; **later presets override earlier ones**; the **current file overrides all** (`_apply_extends`). + +`PresetLoader.load` (`primus/core/config/preset_loader.py`) resolves `primus/configs///.yaml` and runs the same `parse_yaml` pipeline. + +--- + +## CLI overrides (training) + +After the main arguments are parsed, unknown tokens are interpreted as **key=value overrides** and deep-merged into the active training module namespace (`module_cfg.params`). The core runtime applies them in `PrimusRuntime._apply_overrides` (`primus/core/runtime/train_runtime.py`) using `parse_cli_overrides` (`primus/core/utils/arg_utils.py`) followed by `deep_merge`. Both `key=value` and `--key value` forms are accepted. Unknown keys are merged in (not rejected) and forwarded to the backend; the stricter key-existence check in `parse_args` / `_check_keys_exist` (`primus/core/launcher/parser.py`) belongs to a legacy path that the `train` subcommand does not exercise. + +Example (conceptual): + +```bash +./runner/primus-cli direct -- train pretrain --config exp.yaml \ + --train_iters 100 --micro_batch_size 2 +``` + +--- + +## Merge priority (training config) + +The effective ordering of training parameters is: + +1. **CLI overrides** (key=value after the main `train` arguments)—highest. Applied last by the runtime (`PrimusRuntime._apply_overrides`), after preset and experiment merging. +2. **`modules.pre_trainer.overrides`** in the experiment YAML (applied in `PrimusParser.parse_trainer_module`). +3. **Module preset with model preset additions**: the module preset is loaded first, then the model preset is merged in with `allow_override=False`, so duplicate top-level module keys are preserved while non-duplicate model keys are added (`merge_namespace` in `parse_trainer_module`). +4. **Preset chains** via `extends:` inside those files—base layers first, specialized layers later, file body last. + +A concise mental model: + +**CLI overrides > experiment `overrides` > module preset with model preset additions > each preset's own `extends:` chain > shared bases such as `module_base.yaml`.** + +--- + +## Config resolution walkthrough: `llama2_7B-BF16-pretrain.yaml` + +Example experiment: `examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml`. + +1. **Load experiment**—`parse_yaml` reads the file; `${PRIMUS_*:…}` placeholders resolve. +2. **Meta**—`work_group`, `user_name`, `exp_name`, `workspace` are checked. +3. **Platform**—If not present, default `platform_azure.yaml` loads from `primus/configs/platforms/`. +4. **Module preset**—`PresetLoader.load("pre_trainer.yaml", "megatron", "modules")` loads `primus/configs/modules/megatron/pre_trainer.yaml` and applies its `extends:` chain (for example `trainer_base.yaml` → …). +5. **Model preset**—`PresetLoader.load("llama2_7B.yaml", "megatron", "models")` loads `primus/configs/models/megatron/llama2_7B.yaml` (which extends `llama2_base.yaml` → `llama_base.yaml` → …). +6. **Merge**—Module and model namespaces are merged for `pre_trainer`. +7. **Experiment overrides**—Keys under `modules.pre_trainer.overrides` in the example file (for example `mock_data: true`, parallelism, LR) are applied on top. +8. **CLI overrides**—Any key=value pairs from the command line are merged last. + +--- + +## How to write a new config for a new model + +1. **Add or reuse a model preset** under `primus/configs/models//`, using `extends:` from the closest existing architecture (for example copy `llama3_8B.yaml` and adjust hidden size, layers, tokenizer). +2. **Point an experiment YAML at it**—set `modules.pre_trainer.framework`, `config: .yaml`, and `model: .yaml`. +3. **Set overrides** in the experiment file for run-specific values (batch sizes, paths, `mock_data`, parallelism). Prefer small experiment files that reference presets instead of duplicating hundreds of keys. +4. **Validate** with `--dry-run` and by tracing the referenced experiment, module, and model presets (see below). +5. **Optional:** add an example under `examples//configs//` for others to copy. + +--- + +## Debugging config issues + +| Technique | What it does | +| --- | --- | +| `--export_config PATH` | Parsed by the training config parser, but the default core `PrimusRuntime` path does not currently write the resolved YAML. Treat this as legacy/future functionality unless your deployment implements it. | +| `./runner/primus-cli --dry-run …` | Shows the launcher command without executing (shell layer). | +| `--debug` | Enables verbose logging for launcher and Python (`PRIMUS_LOG_LEVEL=DEBUG`). | +| Inspect presets | Open the resolved `extends:` chain under `primus/configs/modules/` and `primus/configs/models/` for the framework you use. | + +If `${VAR}` substitution fails, set the variable or switch to `${VAR:default}` in the YAML. + +--- + +## Cross-references + +- Default launcher YAML: `runner/.primus.yaml` +- YAML loader (env + extends): `primus/core/config/yaml_loader.py` +- Parser and merge: `primus/core/launcher/parser.py` +- Preset paths: `primus/core/config/preset_loader.py` diff --git a/docs/02-user-guide/node-smoke-test-instruction.md b/docs/02-user-guide/node-smoke-test-instruction.md new file mode 100644 index 000000000..41f163be1 --- /dev/null +++ b/docs/02-user-guide/node-smoke-test-instruction.md @@ -0,0 +1,452 @@ +# Node-smoke test instruction + +A lightweight, distributed-rendezvous-free preflight check that runs on every node in parallel under SLURM. It produces a **single PASS/FAIL verdict per node** plus SLURM-ready `passing_nodes.txt` / `failing_nodes.txt` you can pipe straight into `srun --nodelist=` / `--exclude=`. + +Use it to **screen a cluster fast and exclude bad nodes before launching a real training job**. A bad GPU, NIC, wedged driver, or leaked process on any node surfaces as a node FAIL — without a single global rendezvous, so a stuck node can't wedge its peers. + +- **Recommended launcher**: `runner/primus-cli slurm srun -- direct -- node_smoke ...` (auto-resolves the distributed env, applies `slurm.*` config defaults, same pattern as `train` / `benchmark`). The shorter `runner/primus-cli direct -- node_smoke ...` (bare `srun` + `direct`) is equivalent and handy for ad-hoc runs. +- **Companion tool**: [`preflight`](./preflight.md) — the heavier diagnostic with a global rendezvous and inter-node bandwidth tests. The recommended workflow is **node-smoke first, preflight second** (see [§10](#10-comparison-with-the-full-preflight)). + +--- + +## 1. What it does + +Node-smoke answers one question fast: **"which nodes are healthy enough to run anything?"** Because training jobs allocate whole nodes, a single degraded GPU (or NIC, or wedged driver) takes an entire node out of rotation. Node-smoke checks each node independently and emits a per-node verdict plus a ready-to-use exclude list, so you can prune broken nodes before committing a large job to a global rendezvous. + +It deliberately does **not** measure cross-node bandwidth — that's what [`preflight`](./preflight.md) is for. + +--- + +## 2. How it works + +- **Per-node and independent** — every node runs the checks on its own. No `MASTER_ADDR`, no `MASTER_PORT`, no global `torch.distributed` rendezvous, so a stuck node cannot wedge its peers. +- **Per-GPU isolation** — each GPU's checks run in their own Python subprocess with a hard timeout (`--per-gpu-timeout-sec`, default 15 s). A stuck `torch.cuda.set_device()` (which `signal.alarm` cannot interrupt because it sits inside a driver syscall) is `SIGKILL`'d from the parent without affecting the rest of the node's checks. +- **Local-only RCCL** — the optional Tier 2 all-reduce uses `torch.multiprocessing.spawn` over `tcp://127.0.0.1`. No cross-node communication. +- **Rank-0 aggregation** — `NODE_RANK==0` polls for the expected number of per-node JSONs (with a timeout), computes cluster-wide drift, writes the Markdown report + pass/fail lists, and returns non-zero if any node FAILs or never reports. + +--- + +## 3. Prerequisites + +| Prerequisite | How | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Python venv on a shared filesystem | Same venv used by `primus-cli direct -- preflight` (see [`preflight-without-container.md`](./preflight-without-container.md) §2). | +| `VENV_ACTIVATE` exported | `export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate` (optional inside the container path). | +| Inside an existing SLURM allocation | One task per node. Recommended: `runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 -- direct -- node_smoke ...`. Equivalent bare form: `srun ... --ntasks-per-node=1 runner/primus-cli direct -- node_smoke ...`. Either way the `direct -- node_smoke` path auto-selects `--single`, so each task spawns one Python process and per-GPU subprocesses are launched internally. | + +No `MASTER_ADDR`, no `MASTER_PORT`, no global rendezvous required. + +--- + +## 4. Quick start + +**Git clone the Primus repository to a shared filesystem that all nodes can read.** + +```bash +git clone --recurse-submodules https://github.com/AMD-AGI/Primus.git +cd Primus +``` + +**Note: remember to set up the Python virtual environment and NCCL / fabric environment variables as described in [§3 Prerequisites](#3-prerequisites).** + +> ⚠ **Set the NCCL / RCCL environment first** if you plan to run with `--tier2-perf` (the local 8-GPU RCCL all-reduce). Even though the smoke test never opens a cross-node rendezvous, the Tier 2 RCCL step calls `dist.init_process_group(backend="nccl", ...)`, and RCCL **enumerates every transport at init** (XGMI / PCIe P2P + IB + sockets). A misconfigured `NCCL_IB_HCA` / `NCCL_SOCKET_IFNAME` / `NCCL_IB_GID_INDEX` can stall init or make the all-reduce silently fall back to a slow path. The launcher's `base_env.sh` auto-detects these, **but auto-detect sometimes picks the wrong values inside a container** (devices masked by the network namespace, frontend NICs picked up instead of fabric NICs, etc.), so check them and set them explicitly if auto-detection is wrong. +> +> Minimum-viable checklist before running with `--tier2-perf`: +> +> ```bash +> # Pin the RDMA / RoCE training NICs the container can actually see. +> # On a bare-metal host the auto-detect in base_env.sh usually picks +> # the right set; inside a container or on a multi-role node, list +> # them explicitly. Use the same set you would pass to a training job. +> export NCCL_IB_HCA="rdma0,rdma1,rdma2,rdma3,rdma4,rdma5,rdma6,rdma7" +> +> # Pick the RoCE v2 GID index for your fabric: +> # - Mellanox / Broadcom: typically 3 (base_env.sh default). +> # - Pensando Pollara (AINIC): 1. +> export NCCL_IB_GID_INDEX=3 +> +> # The bootstrap socket interface. Auto-detect prefers the first +> # non-loopback interface from `hostname -I`; override when that +> # picks a frontend NIC instead of the data-plane interface. +> export NCCL_SOCKET_IFNAME=eno0 +> export GLOO_SOCKET_IFNAME=eno0 +> ``` +> +> See [`preflight-without-container.md` §4 Cluster-specific NCCL configuration](./preflight-without-container.md#4-cluster-specific-nccl-configuration) for the canonical Broadcom / Pensando Pollara values (the same `NCCL_*` set is used by both tools). If you skip `--tier2-perf`, the RCCL step is not executed and none of the above applies — Tier 1 (host limits, RDMA roll-call, leaked-process detection, etc.) does not depend on RCCL. +> +> Quick verification: `runner/primus-cli direct --dry-run -- node_smoke --tier2-perf` prints the resolved `NCCL_*` block under "Environment Variables" so you can confirm the values before launching for real. + +Recommended — through the `primus-cli slurm srun` wrapper (auto-resolves `MASTER_ADDR`/`NNODES`/`NODE_RANK`, applies `slurm.*` config defaults): + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate + +# Basic Tier 1 check (~5 s/GPU, ~30 s total) +runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + -- direct -- node_smoke + +# Tier 1 + Tier 2 perf sanity (GEMM TFLOPS, HBM GB/s, local 8-GPU RCCL) +runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + -- direct -- node_smoke --tier2-perf + +# Then re-run training, excluding any node the smoke test failed: +srun --exclude=$(paste -sd, output/preflight/failing_nodes.txt) ... your-real-job +``` + +Equivalent with bare `srun` (works the same; useful when composing with custom `srun` flags): + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke + +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf +``` + +Single-node sanity check (no SLURM): + +```bash +runner/primus-cli direct -- node_smoke +``` + +> **Both forms produce the same workload.** The wrapper form is recommended because it resolves the distributed env once on the launching node and propagates it via `--env`, and applies any `slurm.`* config defaults (partition / time / etc.). The `direct` keyword between the two `--`s is **mandatory** to take the no-container path — without it the wrapper routes through the container path. See [`preflight-without-container.md` § Wrapper vs. bare-srun](./preflight-without-container.md#wrapper-vs-bare-srun) for the precedence table. + +--- + +## 5. What's checked + +A `level='fail'` finding in any check FAILs the node. Everything else is reported as info / warn. + +### Tier 1 — always runs (~5 s/GPU) + +**Per-GPU liveness** (each GPU in its own subprocess with a hard timeout): + +- `torch.cuda.set_device(i)` — proves the device is bindable (a stale / wedged GPU often fails here). +- 256 MB allocation. +- Tiny 2048² bf16 GEMM with an `isfinite()` check on the result. + +**Host / GPU / network inventory** (no rendezvous): + +- **dmesg recent-error scan** — greps the last `--dmesg-minutes` (default 15) of `dmesg` for known patterns (`xid`, `gpu reset`, `hung_task`, `mce:`, `amdgpu.*error`, ...). Matches are surfaced in the report. +- **A. Software-stack fingerprint** — kernel / OS / Python, ROCm version, amdgpu kernel-module version, PyTorch / `torch.version.hip` / RCCL versions, and per-IB-device firmware + HCA model. Used for cluster drift detection. +- **B. NIC / RDMA roll-call** — per-port state read from `/sys/class/infiniband` (works inside containers; no `ibv_devinfo` / `ibstat` dependency). Many clusters expose more RDMA ports than the training job uses, so the hard-fail rules only run against the *training-NIC* subset, selected by this precedence: + 1. `--rdma-nic-allowlist` (`NCCL_IB_HCA` syntax: comma-separated `device[:port]`, `^...` denylist, `=dev` exact-match). + 2. `NCCL_IB_HCA` env (same syntax) — so the smoke test and the training launch agree by construction. + 3. Heuristic: auto-exclude any port whose `phys_state` is `Disabled` or `Sleep` (admin-disabled). + 4. Fallback: every IB port must be ACTIVE / LinkUp. + + **Hard-fail rules** (on the included set only): port not ACTIVE / not LinkUp; active port with zero RoCE v2 GIDs (RoCE) or zero valid GIDs (IB); included-NIC count ≠ `--expected-rdma-nics N` (when set). If *every* discovered port gets excluded, the node still fails — a node with zero training NICs cannot participate in inter-node training. Excluded ports stay visible in the report for diagnostics but don't contribute to the FAIL signal. +- **C. Host limits / system tunables** — `RLIMIT_MEMLOCK` below `--ulimit-l-min-gb` (default 32 GiB) → "RDMA pin will fail under load"; `/dev/shm` below `--shm-min-gb` (default 8 GiB) → "NCCL shared-mem may fail". NUMA node count, CPU count, and `cpu0` scaling governor are collected for drift detection. +- **Foreign / leaked process detection** — foreign PIDs holding a GPU FAIL the node by default (the most common cause of "training fails to launch on a healthy-looking node"). Allowed by default: `gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter`. See the [container note in §6.5](#65-allow--extend-the-foreign-process-whitelist) — running inside a container almost always needs `--allow-foreign-procs`. +- **rocm-smi self-latency** — a `rocm-smi --version` call slower than `--rocm-smi-timeout-sec` (default 5 s) is a node FAIL; a wedging amdgpu driver typically hangs `rocm-smi` for 30–60 s before the GPU itself stops responding. + +### Tier 2 — optional perf sanity (`--tier2-perf`) + +Per-GPU steady-state metrics, with iteration counts aligned to the preflight `--quick` preset so smoke and preflight numbers are directly comparable. It's a single switch — you cannot enable just one half. + +- **GEMM TFLOPS** — 8192³ bf16 `torch.matmul`; FAIL below `--gemm-tflops-min` (default 600). +- **HBM GB/s** — 512 MB device-to-device `torch.Tensor.copy_` (counts read + write); FAIL below `--hbm-gbs-min` (default 2000; a healthy MI300X is ≈ 4500–5000). +- **Local 8-GPU RCCL all-reduce GB/s** — algorithmic bandwidth `2·S·(P-1)/P / t / 1e9` at `--rccl-size-mb` (default 64 MB); FAIL below `--rccl-gbs-min` (default 100). Local only, no cross-node traffic. + +--- + +## 6. Examples (by configuration knob) + +> **Convention used below.** The examples are written with bare `srun` for brevity. Anywhere you see `srun runner/primus-cli direct -- node_smoke ...`, the equivalent wrapper form is `runner/primus-cli slurm srun -- direct -- node_smoke ...`. Pick whichever matches your habits; both target the same launcher. + +### 6.1 Hard-fail on partial NIC enumeration + +Catches "7 of 8 RDMA NICs visible" — a common cause of crashes after RoCE init. The count is compared against the *training-NIC* set (after the selector chain), so frontend / storage RoCE NICs do not inflate it. + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf --expected-rdma-nics 8 +``` + +### 6.2 Pin the training-NIC set explicitly + +When auto-detection picks the wrong ports, name the training NICs directly (otherwise `NCCL_IB_HCA` env is used; otherwise admin-disabled ports are auto-excluded): + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf \ + --rdma-nic-allowlist 'rocep158s0:1,rocep190s0:1,rocep206s0:1,rocep222s0:1,rocep28s0:1,rocep62s0:1,rocep79s0:1,rocep96s0:1' +``` + +### 6.3 Tighten Tier 2 perf thresholds + +Reject GPUs that come in below your acceptance bar. Defaults: GEMM 600 TFLOPS, HBM 2000 GB/s, local RCCL 100 GB/s. + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf \ + --gemm-tflops-min 700 --hbm-gbs-min 4500 --rccl-gbs-min 180 +``` + +### 6.4 Tighten host limits + +Fail nodes whose `RLIMIT_MEMLOCK` or `/dev/shm` is too small for production training. + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke \ + --ulimit-l-min-gb 64 --shm-min-gb 16 +``` + +### 6.5 Allow / extend the foreign-process whitelist + +By default, leaked / foreign processes holding a GPU FAIL the node. Allowed by default: `gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter`. + +```bash +# Add a site-specific monitoring agent to the whitelist +srun ... runner/primus-cli direct -- node_smoke \ + --allowed-procs gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter,my-monitor + +# Don't fail at all on foreign processes (still reported in the markdown) +srun ... runner/primus-cli direct -- node_smoke --allow-foreign-procs +``` + +> ⚠ **Running node_smoke inside a container almost always trips this check.** `amd-smi process --json` reports `name="N/A"` for kernel/system PIDs like `gpuagent` whose `/proc//comm` it cannot read, and the fallback name resolution inside `node_smoke` also fails because the container's `/proc` typically does not expose host PIDs (private PID namespace without `--pid=host`, or a `hidepid=2` mount). The unresolved name doesn't match the allowlist, so the check fires and the node FAILs — even though the only "foreign" processes are well-known system daemons holding zero HBM. +> +> **In the container path, pass `--allow-foreign-procs`:** +> +> ```bash +> srun ... runner/primus-cli direct -- node_smoke --tier2-perf --allow-foreign-procs +> ``` +> +> The processes are still listed in `smoke_report.md` under "Busy GPUs / leaked processes", so a real leak is still visible; only the FAIL verdict is downgraded. +> +> **Narrower alternative** — add the literal sentinel `N/A` to the allowlist so the check still catches leaks with resolvable names (e.g. a leftover `python` rank): +> +> ```bash +> srun ... runner/primus-cli direct -- node_smoke --tier2-perf \ +> --allowed-procs gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter,N/A +> ``` +> +> Name resolution runs first, so whenever a real name *can* be resolved it overrides `N/A` and the normal allowlist applies — the `N/A` entry only matches PIDs whose name genuinely could not be recovered. +> +> **Root-cause fix** (preferred long-term): grant the container access to host PIDs so names resolve and the report shows `gpuagent` etc. instead of `N/A`. Typical fixes: launch with `--pid=host` (Docker / Podman); mount `/proc` without `hidepid=2`; or loosen `ptrace_scope` / grant `CAP_SYS_PTRACE`. + +### 6.6 Require specific tools + +Make missing CLI tools a hard FAIL (default: warn-only). + +```bash +srun ... runner/primus-cli direct -- node_smoke --require-tools amd-smi,rocm-smi,lsof +``` + +### 6.7 Skip dmesg scan (containers with no privileges) + +```bash +srun ... runner/primus-cli direct -- node_smoke --skip-dmesg +``` + +### 6.8 Custom dump path + +Keep one report per smoke run instead of overwriting the default location. + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf \ + --dump-path /shared/smoke-archive/$(date +%Y%m%d-%H%M%S) +``` + +### 6.9 Re-aggregate from existing per-node JSONs (no re-run) + +The primus-cli wrapper always runs both phases (per-node run + rank-0 aggregate). To *only* re-render the report from JSONs collected earlier, use the standalone `aggregate` subcommand — it reads the existing `/smoke/*.json` without re-running the per-node step: + +```bash +python -m primus.tools.preflight.node_smoke aggregate \ + --dump-path output/preflight --expected-nodes 6 --wait-timeout-sec 5 +``` + +### 6.10 Silent mode (for CI) + +Suppresses wrapper stdout, but the **final report path is still printed** and stderr / exit code are preserved. + +```bash +srun ... runner/primus-cli direct --silent -- node_smoke --tier2-perf +``` + +### 6.11 Combined "production-ready screen" + +A representative one-shot for a production cluster screen: + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct --silent -- node_smoke --tier2-perf \ + --expected-rdma-nics 8 \ + --gemm-tflops-min 700 --hbm-gbs-min 4500 --rccl-gbs-min 180 \ + --ulimit-l-min-gb 64 --shm-min-gb 16 \ + --require-tools amd-smi,rocm-smi,lsof \ + --dump-path /shared/smoke-archive/$(date +%Y%m%d-%H%M%S) +``` + +--- + +## 7. Outputs + +All written under `--dump-path` (default `output/preflight/`). + +| File | Purpose | +| ------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `smoke/.json` | Per-node verdict + every collected metric. One file per node. | +| `smoke_report.md` | Human-readable cluster report (status table, drift sections, perf summary, failing-node detail). | +| `passing_nodes.txt` | Newline-separated short hostnames. Pipe into `srun --nodelist=`. | +| `failing_nodes.txt` | Newline-separated short hostnames. Pipe into `srun --exclude=`. | +| `expected_nodes.txt` | Auto-populated from `scontrol show hostnames "$SLURM_JOB_NODELIST"`. Lets the report name nodes that never reported. | + +Read the cluster verdict at a glance: + +```bash +head -10 output/preflight/smoke_report.md +``` + +Feed bad nodes into a re-run: + +```bash +srun --exclude=$(paste -sd, output/preflight/failing_nodes.txt) ... your-real-job +``` + +--- + +## 8. Understanding the report + +`smoke_report.md` renders in a stable order. Each section short-circuits to a placeholder (e.g. `*All nodes match.*`, `*No NIC issues.*`) on a healthy cluster, so a clean report stays short. In order: + +1. **Status table** — one row per node: `node_rank`, hostname, PASS/FAIL, duration, top fail reason. +2. **Stack drift across cluster** — per fingerprint key, outliers vs the cluster majority (catches "1 of N nodes on a different RCCL build"). +3. **NIC firmware drift across cluster** — per-IB-device firmware drift. +4. **NIC / RDMA roll-call issues** — every offending node + port (included set only). +5. **NIC port-count summary** — cluster-majority *training-NIC* count and any node that disagrees (catches partial-NIC degradation even without `--expected-rdma-nics`). +6. **NIC excluded ports (informational)** — ports the selector chain dropped, grouped by source. Does not contribute to FAIL. +7. **Host limits issues** — per-node hard-limit violations. +8. **GPU visibility issues** — nodes where torch couldn't see the GPUs, or amd-smi sees more GPUs than torch (stale ROCm / wedged driver). +9. **GPU low-level outliers (PCIe link / HBM)** — per-GPU outliers vs the cluster majority on PCIe width/speed and HBM total. +10. **XGMI link issues** — any non-XGMI GPU pair (intra-node collectives silently fall back to PCIe). +11. **Cluster clock + time daemons** — wall-clock spread plus per-node time-daemon health. +12. **Tooling self-latency (`rocm-smi --version`)** — slow / timed-out tool calls (precursor to a wedged driver). +13. **Tooling availability** — inventory of `amd-smi` / `rocm-smi` / `lsof` per node, plus which Tier 1 checks have no working tool on each node. +14. **Busy GPUs / leaked processes** — foreign PIDs holding GPUs at smoke start. +15. **GPU pre-touch HBM usage outliers** — GPUs with non-trivial HBM in use *before* smoke touched the device. +16. **GPU compute-activity outliers** — GPUs above `--gpu-activity-warn-pct` at smoke start (warn-only). +17. **Tier 2 perf summary** (only when at least one node ran Tier 2) — per-node GEMM TFLOPS / HBM GB/s as `min / median / max`, plus local RCCL GB/s. +18. **Failing nodes — full reasons** (only when there are failing nodes) — every fail reason, expanded per node. + +--- + +## 9. Configuration reference + +### 9.1 Common knobs (cheat sheet) + +| Flag | Default | When you'd change it | +| ------------------------ | ------------------------------------------------ | ------------------------------------------------------------------------------------- | +| `--tier2-perf` | off | Always on for production screens — adds GEMM TFLOPS, HBM GB/s, local RCCL all-reduce. | +| `--gemm-tflops-min N` | 600 | Site-specific acceptance bar. | +| `--hbm-gbs-min N` | 2000 | Site-specific acceptance bar (MI300X healthy ≈ 4500–5000). | +| `--rccl-gbs-min N` | 100 | Site-specific acceptance bar. | +| `--expected-rdma-nics N` | unset | Hard-fail on partial NIC enumeration. | +| `--ulimit-l-min-gb GB` | 32 | Raise for production training profiles. | +| `--shm-min-gb GB` | 8 | Raise for large-batch / many-rank profiles. | +| `--allow-foreign-procs` | off | Co-tenant clusters, shared GPUs, or the container path (see [§6.5](#65-allow--extend-the-foreign-process-whitelist)). | +| `--allowed-procs LIST` | `gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter` | Add site-specific monitoring agents. | +| `--require-tools LIST` | `""` | Fail-fast if a CLI tool is missing in PATH. | +| `--skip-dmesg` | off | Inside unprivileged containers. | +| `--dump-path DIR` | `output/preflight` | Archive each run separately. | +| `--silent` (launcher) | off | CI / scripted runs. | + +### 9.2 Full `node_smoke` (per-node) flags + +Authoritative source: `python -m primus.tools.preflight.node_smoke run --help`. + +| Flag | Default | Purpose | +|---|---|---| +| `--dump-path` | `output/preflight` | Output directory. | +| `--expected-gpus N` | auto | Override GPU count (auto-detected from `LOCAL_WORLD_SIZE` / `GPUS_PER_NODE` / `torch.cuda.device_count()`). | +| `--per-gpu-timeout-sec` | 15 | Hard timeout per per-GPU subprocess. | +| `--tier2-perf` | off | Enable Tier 2 perf sanity (per-GPU GEMM TFLOPS + HBM GB/s + node-local RCCL all-reduce). Single switch. | +| `--gemm-tflops-min` | 600 | Tier 2 GEMM threshold. | +| `--hbm-gbs-min` | 2000 | Tier 2 HBM threshold. | +| `--rccl-size-mb` | 64 | Local RCCL message size. | +| `--rccl-gbs-min` | 100 | Local RCCL bandwidth threshold. | +| `--rccl-timeout-sec` | 120 | Hard timeout for the RCCL phase. | +| `--skip-dmesg` | off | Skip dmesg scan (e.g. inside containers). | +| `--dmesg-minutes` | 15 | dmesg `--since` window. | +| `--expected-rdma-nics N` | auto (report-only) | When set, a mismatch between the included (training-NIC) count and N becomes a node FAIL. | +| `--rdma-nic-allowlist LIST` | unset | Explicit training-NIC selector in `NCCL_IB_HCA` syntax (`device[:port],...`, `^...` denylist, `=dev` exact-match). Wins over `NCCL_IB_HCA` env. When neither is set, ports whose `phys_state` is `Disabled` / `Sleep` are auto-excluded. | +| `--ulimit-l-min-gb GB` | 32 | `RLIMIT_MEMLOCK` threshold (0 disables). | +| `--shm-min-gb GB` | 8 | `/dev/shm` size threshold (0 disables). | +| `--rocm-smi-timeout-sec SEC` | 5.0 | Hard timeout for the `rocm-smi --version` self-latency canary; hitting it is a node FAIL. | +| `--hbm-busy-threshold-gib GiB` | 2.0 | FAIL if any GPU has ≥ this much HBM in use before smoke touches the device. Boundary inclusive. | +| `--allow-foreign-procs` | off | Do NOT FAIL on foreign processes holding a GPU. They are still reported. | +| `--allowed-procs LIST` | `gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter` | Process names OK to find holding the GPU. Set to `""` to disable the whitelist. | +| `--gpu-activity-warn-pct PCT` | 20.0 | Warn (does NOT fail) if any GPU's `gfx_activity_pct` exceeds this at smoke start. | +| `--require-tools LIST` | `""` (warn-only) | CLI tools that MUST be in PATH (`amd-smi`, `rocm-smi`, `lsof`); anything missing becomes a hard node FAIL. | +| `--no-clean-dump-path` | off | Do NOT auto-wipe stale per-node JSONs / aggregator outputs from `--dump-path` on rank 0 at startup. | + +### 9.3 Standalone `aggregate` flags + +The primus-cli wrapper runs the aggregator automatically on rank 0 and fills `--expected-nodes` / `--expected-nodelist-file` from SLURM. These matter only when you invoke `python -m primus.tools.preflight.node_smoke aggregate` yourself (see [§6.9](#69-re-aggregate-from-existing-per-node-jsons-no-re-run)). + +| Flag | Default | Purpose | +|---|---|---| +| `--dump-path` | `output/preflight` | Same as `run`. | +| `--expected-nodes N` | none | If fewer JSONs land within `--wait-timeout-sec`, missing nodes are added as FAIL placeholders. | +| `--wait-timeout-sec` | 60 | Polling timeout. | +| `--rocm-smi-warn-sec SEC` | 1.0 | Flag (warn-only) any node where `rocm-smi --version` took longer than this. | +| `--clock-skew-warn-sec SEC` | 30.0 | Warn when wall-clock spread across nodes exceeds this many seconds (includes srun launch jitter). | +| `--hbm-busy-threshold-gib GiB` | 2.0 | Mirrors the `run` default; labels the "GPU pre-touch HBM usage outliers" section. | +| `--gpu-activity-warn-pct PCT` | 20.0 | Mirrors the `run` default; labels the "GPU compute-activity outliers" section. | +| `--expected-nodelist-file FILE` | none | One short hostname per line. Missing nodes get their real short hostname in the report and `failing_nodes.txt`. The wrapper auto-populates this from `scontrol show hostnames "$SLURM_JOB_NODELIST"` under SLURM. | + +### 9.4 Launcher-level knobs (`primus-cli direct`) + +Consumed by `primus-cli-direct.sh` **before** the `--` separator (not forwarded to the `node_smoke` Python tool): + +| Flag | Purpose | +|---|---| +| `--silent` | Redirect launcher + tool stdout to `/dev/null`. Launcher errors (`LOG_ERROR` / `LOG_WARN` on stderr) and the log file are preserved. Exit code propagated. | +| `--debug` | Verbose launcher logging. | +| `--dry-run` | Print the resolved configuration and command without executing. | +| `--env KEY=VALUE` | Inject an env var into the Python process. | + +> **Run vs. aggregate.** The primus-cli `node_smoke` subcommand always runs the per-node checks on every rank, then aggregates on rank 0 — which is what you want ~100% of the time, so there is no `--aggregate-only` wrapper flag. For the rare single-phase cases, call the standalone CLI directly: `python -m primus.tools.preflight.node_smoke run ...` (per-node only, no report) or `... aggregate ...` (report only, from existing JSONs). + +--- + +## 10. Comparison with the full `preflight` + +| Aspect | `node_smoke` | full `preflight` | +|---|---|---| +| Rendezvous | None — every node independent | Global `torch.distributed` | +| Wall clock | ~30–60 s for 6 nodes (Tier 1+2) | Minutes; scales with N for inter-node tests | +| Granularity | Per-node PASS/FAIL | Per-rank measurements (no auto-fail by default) | +| GEMM | Hard threshold per GPU | Reports per-GPU numbers, no auto-fail | +| HBM bandwidth | Yes (D2D `copy_`) | Not measured | +| Inter-node all-reduce / all-to-all | Not tested (intentionally) | Yes | +| Drift detection | Yes (versions, NIC firmware, port count) | No | +| Host limits / RDMA roll-call | Yes (hard fail) | Reported via `collect_*_info` only | +| Output format | Per-node JSON + cluster md + SLURM-ready txt | Markdown + PDF | + +Use `node_smoke` to **screen** a cluster fast and exclude bad nodes. Use the full [`preflight`](./preflight.md) when you want **deep cross-node measurements** (inter-node bandwidth matrix, ring-P2P, etc.). The recommended sequence is node-smoke first, then `preflight --quick` on the surviving nodes. + +--- + +## 11. Troubleshooting + +| Symptom | Likely cause / fix | +| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `[ERROR] [direct] VENV_ACTIVATE is set but file does not exist: ...` | Fix the path (`export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate`), or `unset VENV_ACTIVATE` to fall back to system / container Python. | +| Every node FAILs with `gpu_processes: ... name='N/A'` | Container `/proc` can't resolve host PID names. See [§6.5](#65-allow--extend-the-foreign-process-whitelist): pass `--allow-foreign-procs`, or grant host-PID visibility. | +| Some nodes never produce a JSON | The aggregator names them in `failing_nodes.txt` via `expected_nodes.txt`. If `scontrol` was unavailable, they appear as ``. | +| Tier 2 perf numbers below threshold on a known-good node | Almost always insufficient CPU cores on `srun` — pass `-c ` so RCCL proxy threads have CPU. | +| Re-run on a smaller nodelist still shows the previously removed nodes as PASS | Default behavior cleans stale JSONs on rank 0. If you passed `--no-clean-dump-path`, either remove it or `rm -rf output/preflight` between runs. | + +--- + +## 12. See also + +- [`preflight.md`](./preflight.md) — the heavier `preflight` tool with a global rendezvous and inter-node bandwidth tests. +- [`preflight-without-container.md`](./preflight-without-container.md) — running `preflight` directly on the host (no container), including the shared venv + NCCL setup. +- [`primus/cli/subcommands/node_smoke.py`](../../primus/cli/subcommands/node_smoke.py) — the primus-cli subcommand wiring (two-phase dispatch: per-rank run + rank-0 aggregate). +- [`primus/tools/preflight/node_smoke/cli.py`](../../primus/tools/preflight/node_smoke/cli.py) — canonical flag definitions and per-node / aggregate phase bodies. diff --git a/docs/02-user-guide/posttraining.md b/docs/02-user-guide/posttraining.md new file mode 100644 index 000000000..dedd39235 --- /dev/null +++ b/docs/02-user-guide/posttraining.md @@ -0,0 +1,216 @@ +# Post-training workflows + +Post-training (supervised fine-tuning) adapts a pre-trained foundation model to new tasks or domains. In Primus, post-training runs through the **Megatron Bridge** backend using the `train posttrain` subcommand. Example YAML configurations live under `examples/megatron_bridge/configs/` in the [Primus repository](https://github.com/AMD-AGI/Primus). + +For YAML field details, see [Megatron Bridge parameters](../03-configuration-reference/megatron-bridge-parameters.md). For related tooling, see [Benchmark suite](./benchmarking.md), [Preflight diagnostics](./preflight.md), [Memory and performance projection](./projection.md). + +--- + +## Overview: SFT vs LoRA + +The following table shows how SFT (Supervised Fine-Tuning) and LoRA (Low-Rank Adaptation) differ in different aspects. + +| Aspect | SFT (full fine-tuning) | LoRA (parameter-efficient) | +|--------|------------------------|----------------------------| +| **PEFT setting** | `peft: "none"` | `peft: lora` | +| **What is trained** | All model parameters | Low-rank adapters only | +| **Memory** | Higher | Lower | +| **Throughput** | Typically slower per step | Often faster iteration | +| **Learning rate** | Lower: roughly `5e-6` to `1e-5` | Higher: roughly `1e-4` to `5e-4` | +| **Typical use** | Maximum adaptation when memory allows | Limited GPU memory, many task-specific adapters, rapid experimentation | + +--- + +## Quick start commands + +General form: + +```bash +./primus-cli -- train posttrain --config +``` + +From a clone of the Primus repository, the same entrypoint is often invoked as `./runner/primus-cli`. + +**Prerequisites:** AMD ROCm (recommended ≥ 7.0) and Docker with ROCm support (optional but typical) installed on systems with AMD Instinct™ GPUs (for example MI300X, MI355X). Run this for a quick check on these prerequisites: `rocm-smi && docker --version`. See [Installation and setup](../01-getting-started/installation.md) for the full prerequisites and container setup. + +### Direct mode (bare metal or inside a Docker container) + +```bash +# SFT — example: Qwen3 32B on MI355X +./runner/primus-cli direct -- train posttrain \ + --config ./examples/megatron_bridge/configs/MI355X/qwen3_32b_sft_posttrain.yaml + +# LoRA — same model family +./runner/primus-cli direct -- train posttrain \ + --config ./examples/megatron_bridge/configs/MI355X/qwen3_32b_lora_posttrain.yaml +``` + +### Container mode + +```bash +./runner/primus-cli container --image rocm/primus:v26.4 -- \ + train posttrain \ + --config ./examples/megatron_bridge/configs/MI355X/qwen3_32b_sft_posttrain.yaml +``` + +--- + +## Configuration reference + +These keys are commonly set under `modules.post_trainer.overrides` in your configuration YAML (see the examples in the repository under `examples/megatron_bridge/configs/`). + +| Area | Parameters | Notes | +|------|------------|--------| +| **Method** | `peft` | `"none"` for SFT; `lora` for LoRA. | +| **Learning rate** | `finetune_lr`, `min_lr`, `lr_warmup_iters`, `lr_decay_iters` | LoRA usually needs a higher `finetune_lr` than SFT. | +| **Precision** | `precision_config` | Typical: `bf16_mixed`. Alternatives include `fp16_mixed` and `fp32` depending on backend support. | +| **Parallelism** | `tensor_model_parallel_size`, `pipeline_model_parallel_size`, `context_parallel_size`, `sequence_parallel` | Increase TP/PP when the model does not fit on fewer GPUs. | +| **Recompute (memory)** | `recompute_granularity`, `recompute_method`, `recompute_num_layers` | Use to trade compute for activation memory (for example `recompute_granularity: full` with uniform recompute). | +| **Batching / length** | `train_iters`, `global_batch_size`, `micro_batch_size`, `seq_length` | `micro_batch_size` is per-GPU; tune with sequence length and memory. | + +Snippet of an SFT configuration (illustrative only): + +```yaml +modules: + post_trainer: + framework: megatron_bridge + config: sft_trainer.yaml + model: qwen3_32b.yaml + overrides: + peft: "none" + finetune_lr: 5.0e-6 + precision_config: bf16_mixed + tensor_model_parallel_size: 1 + global_batch_size: 8 + micro_batch_size: 1 + seq_length: 8192 +``` + +Snippet of a LoRA configuration (illustrative only): + +```yaml +modules: + post_trainer: + framework: megatron_bridge + config: sft_trainer.yaml + model: qwen3_32b.yaml + overrides: + peft: lora + finetune_lr: 1.0e-4 + precision_config: bf16_mixed + recompute_granularity: full + recompute_method: uniform + recompute_num_layers: 1 +``` + +--- + +> The reference configurations below are organized by GPU architecture. **MI325X** uses the same configurations as **MI300X** (both are `gfx942`), and **MI350X** uses the same configurations as **MI355X** (both are `gfx950`). + +## MI300X configurations + +Paths are relative to `examples/megatron_bridge/configs/` in the [Primus repository](https://github.com/AMD-AGI/Primus). + +| Model | Method | Config path | TP | GBS | MBS | Seq len | +|-------|--------|-------------|----|-----|-----|---------| +| Qwen3 32B | SFT | `MI300X/qwen3_32b_sft_posttrain.yaml` | 2 | 8 | 2 | 8192 | +| Qwen3 32B | LoRA | `MI300X/qwen3_32b_lora_posttrain.yaml` | 1 | 32 | 2 | 8192 | + +**Legend:** TP = tensor parallel size; GBS = global batch size; MBS = micro batch size per GPU; Seq len = `seq_length`. + +Sample command for running the post-training: + +```bash +./runner/primus-cli direct -- train posttrain \ + --config ./examples/megatron_bridge/configs/MI300X/qwen3_32b_sft_posttrain.yaml +``` + +--- + +## MI355X configurations + +Paths are relative to `examples/megatron_bridge/configs/` in the [Primus repository](https://github.com/AMD-AGI/Primus). + +| Model | Method | Config path | TP | GBS | MBS | Seq len | +|-------|--------|-------------|----|-----|-----|---------| +| Qwen3 32B | SFT | `MI355X/qwen3_32b_sft_posttrain.yaml` | 1 | 8 | 1 | 8192 | +| Qwen3 32B | LoRA | `MI355X/qwen3_32b_lora_posttrain.yaml` | 1 | 32 | 4 | 8192 | + +Sample command for running the post-training: + +```bash +./runner/primus-cli direct -- train posttrain \ + --config ./examples/megatron_bridge/configs/MI355X/qwen3_32b_lora_posttrain.yaml +``` + +--- + +## Best practices + +### Use SFT or LoRA? + +- **SFT is preferred** when you need the strongest possible task fit, have enough GPU memory, and can afford longer runs. +- **LoRA is preferred** when memory is tight, you want fast iteration, or you plan to maintain multiple adapters for different tasks. + +### Learning rates + +- **SFT:** start in the `5e-6`–`1e-5` range; adjust with validation loss. +- **LoRA:** often `1e-4`–`5e-4`; still use warmup (`lr_warmup_iters`) for stability. + +### Batch sizes + +- **SFT**: starting with `global_batch_size: 8` is a reasonable default for development; scale up when stable (for example to 64, 128, or higher) if memory and throughput allow. +- **LoRA**: larger global batches are often feasible (for example 32 in the reference configs); align `micro_batch_size` with sequence length and available HBM. +- Very long sequences (for example 8192) may require smaller micro-batches or more parallelism. + +### Parallelism + +- **SFT:** large models may need higher `tensor_model_parallel_size` (for example TP 8 for very large models). The bundled 32B examples use TP 2 on MI300X and TP 1 on MI355X for SFT. +- **LoRA:** adapters reduce memory pressure; lower TP is often sufficient for a given model size. + +--- + +## Troubleshooting + +### Out of memory (OOM) + +**SFT** + +1. Increase `tensor_model_parallel_size` (and/or pipeline parallelism for very large models). +2. Reduce `micro_batch_size` or `seq_length`. +3. Enable activation recomputation (`recompute_granularity`, `recompute_method`, `recompute_num_layers`). + +**LoRA** + +1. Confirm `peft: lora` is set. +2. Reduce `micro_batch_size` if OOM persists. +3. Apply the same recompute settings as for SFT. + +### Training instability (loss spikes, NaNs) + +1. Decrease `finetune_lr`. +2. Increase `lr_warmup_iters`. +3. Keep mixed precision stable (`precision_config: bf16_mixed` where supported). +4. Monitor gradients and clipping settings if exposed by your trainer config. + +### Slow training + +1. Increase effective batch size where memory allows (`global_batch_size` / `micro_batch_size` tuning). +2. Revisit TP/PP/CP for your cluster topology. +3. Run [benchmarks](./benchmarking.md) or [preflight](./preflight.md) to isolate network or GPU issues. + +### Configuration errors + +1. Verify YAML paths and indentation. +2. Set `PRIMUS_WORKSPACE` and other environment variables expected by your team’s templates. +3. Confirm checkpoint and data paths by reviewing the experiment YAML and any presets it references. The current core training runtime parses `--export_config`, but resolved-config export is not implemented on the default `PrimusRuntime` path. + +--- + +## Related documentation + +- [Megatron Bridge parameters](../03-configuration-reference/megatron-bridge-parameters.md) +- [Native SFT / LoRA quick start](../04-technical-guides/native-sft-lora.md) +- [Benchmark suite](./benchmarking.md) +- [Preflight diagnostics](./preflight.md) +- [Memory and performance projection](./projection.md) diff --git a/docs/02-user-guide/preflight-without-container.md b/docs/02-user-guide/preflight-without-container.md new file mode 100644 index 000000000..9dbb23192 --- /dev/null +++ b/docs/02-user-guide/preflight-without-container.md @@ -0,0 +1,792 @@ +# Run preflight without a container + +> ⚠ **Run the [node-smoke test](./node-smoke-test-instruction.md) first.** `preflight` opens a global `torch.distributed` rendezvous, so a single sick node (wedged driver, leaked rank holding HBM, partial NIC enumeration, time-sync drift, etc.) can stall the whole job for up to `--dist-timeout-sec` seconds — long before any cross-node bandwidth number is produced. The node-smoke test catches those exact failure modes *without* a rendezvous in ~30–60 s and emits a SLURM-ready `failing_nodes.txt` you can pipe straight into `srun --exclude=`. Treat node-smoke as a hard prerequisite; only run `preflight` on the nodes node-smoke marked PASS. See [§0 "Which test should I run?"](#0-which-test-should-i-run) for the side-by-side comparison and the recommended 3-step workflow. + +This guide explains how to run Primus's [`preflight`](./preflight.md) cluster-diagnostic tool **directly on the host** (no Docker / Podman), via the standard Primus launcher. + +**Git clone the Primus repository to a shared filesystem that all nodes can read.** + +```bash +git clone --recurse-submodules https://github.com/AMD-AGI/Primus.git +cd Primus +``` + +**Recommended (through the primus-cli SLURM wrapper):** + +For some clusters, you may need to explicitly request CPU and GPU resources with `srun -N -c --gpus-per-node=`. + +``` +runner/primus-cli slurm srun -N --ntasks-per-node=1 -- direct -- preflight [PREFLIGHT_ARGS...] +``` + +**Equivalent (bare srun, useful when composing with custom srun flags):** + +``` +srun -N --ntasks-per-node=1 runner/primus-cli direct -- preflight [PREFLIGHT_ARGS...] +``` + +Both forms produce the **same workload** on the same ranks. The wrapper form is recommended because it auto-resolves `MASTER_ADDR` / `MASTER_PORT` / `NNODES` / `NODE_RANK` / `GPUS_PER_NODE` once on the launching node and passes them to every rank via `--env`, applies any `slurm.`* config defaults (partition / time / etc.) from your YAML, and is the same pattern used for `train` / `benchmark` / `node_smoke`. See [§ Wrapper vs. bare-srun](#wrapper-vs-bare-srun) below for the exact precedence / caveats. + +`primus-cli direct` activates an optional Python virtualenv (`VENV_ACTIVATE`), auto-derives the distributed environment variables (`NNODES`, `NODE_RANK`, `MASTER_ADDR`, `MASTER_PORT`, `GPUS_PER_NODE`) from `SLURM_*` when running inside a SLURM allocation, and then launches the `preflight` Python subcommand via `torchrun` (one worker per GPU). It is the recommended entry point when: + +- You're running on a SLURM cluster but cannot (or don't want to) use the container-based path. +- Your nodes share a Python virtual environment on a network-mounted filesystem. +- You want a single-node sanity check with no extra configuration. + +--- + +## 0. Which test should I run? + +Primus ships **two** complementary cluster screens. Pick the right one — and ideally run them in this order. + + +| Aspect | `node-smoke` (start here) | `preflight` (this doc) | +| ----------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| Purpose | "Which nodes are healthy enough to run anything?" | "What is the actual cross-node performance on the surviving nodes?" | +| Rendezvous | None — every node independent | Global `torch.distributed` rendezvous | +| Wall clock | ~30–60 s for 6 nodes (Tier 1+2) | A few minutes; scales with N for inter-node tests | +| Granularity | Per-node PASS/FAIL | Per-rank perf measurements | +| Safety | A stuck node cannot wedge its peers | A single hung NIC can stall the whole rendezvous | +| Output | Per-node JSON + cluster md + SLURM-ready `passing_nodes.txt` / `failing_nodes.txt` | Markdown + PDF perf report | +| Entry point | `primus-cli direct -- node_smoke` | `primus-cli direct -- preflight` (this doc) | +| Quick-start guide | [`node-smoke-test-instruction.md`](./node-smoke-test-instruction.md) | This doc, §3+ | + + +### Recommended workflow + +> **Before running any of the commands below, complete the one-time setup:** +> +> 1. **Python virtualenv** on a shared filesystem — see [§2 Set up the Python virtual environment](#2-set-up-the-python-virtual-environment), then point the launcher at it via `export VENV_ACTIVATE=...` (details in [§2 → Tell the launcher where the venv is](#tell-the-launcher-where-the-venv-is)). +> 2. **NCCL / fabric environment variables** — usually the defaults in `base_env.sh` are fine, but multi-NIC nodes may need `NCCL_IB_HCA` / `NCCL_IB_GID_INDEX` / `NCCL_SOCKET_IFNAME` overrides. See [§4 Cluster-specific NCCL configuration](#4-cluster-specific-nccl-configuration) for known-good values per fabric (Broadcom, Pensando Pollara/AINIC). + +Through the `primus-cli slurm srun -- direct --` wrapper (recommended): + +```bash +# 1) Prune broken nodes with node-smoke (fast, no rendezvous). +runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + -- direct -- node_smoke --tier2-perf + +# 2) Re-allocate excluding the bad nodes, and run preflight --quick +# for a fast cross-node sanity check. +runner/primus-cli slurm srun -N -c 128 --gpus-per-node=8 \ + --ntasks-per-node=1 \ + --exclude=$(paste -sd, output/preflight/failing_nodes.txt) \ + -- direct -- preflight --quick + +# 3) Optional: full preflight on the same set if --quick numbers +# look off, or if you want the full bandwidth matrix. +runner/primus-cli slurm srun -N -c 128 --gpus-per-node=8 \ + --ntasks-per-node=1 \ + --exclude=$(paste -sd, output/preflight/failing_nodes.txt) \ + -- direct -- preflight +``` + +Equivalent with bare `srun` (works identically; useful when scripting around custom srun flags that don't compose with the wrapper): + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf + +srun -N -c 128 --gpus-per-node=8 --ntasks-per-node=1 \ + --exclude=$(paste -sd, output/preflight/failing_nodes.txt) \ + runner/primus-cli direct -- preflight --quick + +srun -N -c 128 --gpus-per-node=8 --ntasks-per-node=1 \ + --exclude=$(paste -sd, output/preflight/failing_nodes.txt) \ + runner/primus-cli direct -- preflight +``` + +Why this ordering matters: + +- A single broken node can stall a `torch.distributed.init_process_group()` for `--dist-timeout-sec` seconds (default 120), so feeding a known-good list to preflight is much faster. +- `node-smoke` catches things preflight cannot — leaked / foreign processes, wedged drivers, partial NIC enumeration, time-sync drift, RDMA roll-call issues — that produce *misleading* preflight failures. +- `preflight --quick` adds the cross-node bandwidth signal that `node-smoke` deliberately does not measure. + +--- + +## 1. Prerequisites + +- A working AMD ROCm installation on every node. +- Network reachability between nodes (Ethernet for bootstrap, RDMA / InfiniBand recommended for perf tests). +- A Python ≥ 3.10 virtual environment **on a shared filesystem** that all nodes can read (the same path is sourced on every node). +- The Primus repository checked out somewhere readable from every node. + +--- + +## 2. Set up the Python virtual environment + +The environment must live on a path visible from every node (e.g. NFS-mounted home, Lustre, or any shared filesystem). All nodes will `source` the same activation script. + +You can use any tool you like; `uv` is the fastest. Either of the following works. + +### What you actually need to install + +The `preflight` and `node-smoke` tools deliberately use **only a small subset** of Primus's full dependency tree. You do **not** need to install the entire `requirements.txt` — that pulls in trainer / dataset / experiment-tracking packages that neither tool ever imports. + + +| Package | Required for | Skip when | +| -------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `torch` (ROCm build) | Both tools — perf measurements (`torch.matmul`, `torch.distributed`, `torch.cuda.`*). | Never (mandatory). | +| `markdown2` | `preflight` PDF report only (Markdown → HTML). | You always pass `--disable-pdf`, or you only run `node-smoke` (which never produces PDFs). | +| `weasyprint` | `preflight` PDF report only (HTML → PDF). | Same as above. | +| `matplotlib` | `preflight --plot` only (per-test bandwidth bar charts). | You don't pass `--plot`. | + + +Everything else in the preflight / node-smoke code path is Python stdlib (`os`, `subprocess`, `socket`, `argparse`, `dataclasses`, `json`, `time`, ...) — no extra installs needed. + +### Option A — `uv` (recommended), minimal install + +```bash +mkdir -p ~/envs/preflight +cd ~/envs/preflight + +uv venv --python 3.12 +source .venv/bin/activate + +# 1) ROCm-built PyTorch (pin to your ROCm version; rocm7.1 shown here) +uv pip install torch torchvision --index-url https://download.pytorch.org/whl/rocm7.1 --no-cache-dir + +# 2) Optional: only if you want preflight PDF reports (omit to use --disable-pdf) +uv pip install markdown2 weasyprint + +# 3) Optional: only if you want preflight --plot bar charts +uv pip install matplotlib +``` + +### Option B — `python -m venv`, minimal install + +```bash +mkdir -p ~/envs/preflight +python3.12 -m venv ~/envs/preflight/.venv +source ~/envs/preflight/.venv/bin/activate + +pip install torch torchvision --index-url https://download.pytorch.org/whl/rocm7.1 --no-cache-dir +pip install markdown2 weasyprint # optional, for preflight PDFs +pip install matplotlib # optional, for preflight --plot +``` + +### Option C — full Primus runtime (only if you also want the rest of Primus) + +```bash +cd /path/to/Primus +uv pip install -r requirements.txt # or: pip install -r requirements.txt +``` + +This installs every Primus runtime dependency (trainer, dataset loaders, experiment trackers, ...). Use only if you're going to run more than just preflight / node-smoke from this environment. + +### Per-tool minimum install matrix + +If you want the absolute smallest footprint, install only what your intended invocations need: + + +| Invocation | `torch` | `markdown2` | `weasyprint` | `matplotlib` | +| ------------------------------------------------ | -------- | --------------------------------- | --------------------------------- | ------------ | +| `node-smoke` (any flags) | required | — | — | — | +| `preflight --host --gpu --network --disable-pdf` | required | — | — | — | +| `preflight --host --gpu --network` (with PDF) | required | required | required | — | +| `preflight --quick --disable-pdf` | required | — | — | — | +| `preflight --quick` (with PDF) | required | required | required | — | +| `preflight ... --plot` | required | required (unless `--disable-pdf`) | required (unless `--disable-pdf`) | required | + + +### Tell the launcher where the venv is + +`primus-cli direct` reads the `VENV_ACTIVATE` environment variable. When set, it sources the path before launching the Python process; when unset, it is a no-op (the container path, which uses the container's bundled Python, leaves this unset): + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate +``` + +`VENV_ACTIVATE` is the only optional environment variable specific to the direct flow. Everything else has a sensible default; distributed-env variables (`NNODES`, `NODE_RANK`, `MASTER_ADDR`, ...) are auto-derived from SLURM when not pre-exported. + +--- + +## 3. Run preflight + +### Single node (no SLURM) + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate + +# Info report only (fast) +runner/primus-cli direct -- preflight --host --gpu --network + +# Info + perf report +runner/primus-cli direct -- preflight + +# Perf report only +runner/primus-cli direct -- preflight --perf-test +``` + +When SLURM is not detected the script defaults to `NNODES=1`, `NODE_RANK=0`, `MASTER_ADDR=localhost`. Any of those can be overridden by exporting them before calling the script. + +### Multi-node without SLURM (parallel SSH) + +When no scheduler is available (bare-metal, cloud VMs, lab nodes), launch +`primus-cli direct` on each node yourself via SSH. The script works +identically — you just pre-export the distributed variables that SLURM +would normally provide. + +#### Requirements + +- All nodes share the same filesystem (or at least the same Primus checkout + venv path). +- Nodes can reach each other on a **data-plane** network interface (not the management NIC). +- SSH key-based access to each node from the launching host. + +#### Required environment variables + + +| Variable | Description | +| -------------------- | ---------------------------------------------------------------------- | +| `NNODES` | Total number of nodes | +| `NODE_RANK` | This node's rank (`0` through `NNODES-1`) | +| `MASTER_ADDR` | IP of rank-0 node **on the data-plane interface** | +| `MASTER_PORT` | Rendezvous port (default `1234`; increment between concurrent runs) | +| `GPUS_PER_NODE` | GPUs per node (default `8`) | +| `NCCL_SOCKET_IFNAME` | Data-plane NIC name (e.g. `enp159s0np0`) — **critical for multi-node** | +| `GLOO_SOCKET_IFNAME` | Same as `NCCL_SOCKET_IFNAME` | +| `VENV_ACTIVATE` | Path to virtualenv `activate` script | + + +> **Warning**: `NCCL_SOCKET_IFNAME` auto-detection often picks a management interface +> (e.g. `enp28s0np0`, `eno8303`) instead of the high-bandwidth data NIC. For multi-node +> runs this causes `init_process_group` to hang or NCCL to fail silently. Always set it +> explicitly. + +#### Identifying the correct data-plane interface + +```bash +# On any node, find the interface whose IP matches the MASTER_ADDR subnet: +ip -4 addr show | grep "10.245.134" +# → enp159s0np0 inet 10.245.134.129/24 + +# Or check which interface routes to the master: +ip route get 10.245.134.129 | awk '{print $5; exit}' +``` + +### Multi-node via SLURM + +`primus-cli direct` auto-detects a SLURM allocation (via `SLURM_JOB_ID`) and derives all distributed variables from `SLURM_*` automatically. **Pre-exported values always win**, so the same launcher script also works inside the `primus-cli slurm srun ... -- direct -- ...` chain (where `slurm-entry` has already set these via `--env`): + +| Variable | Resolved as | +| --------------- | -------------------------------------------------------------------- | +| `NNODES` | `NNODES` → `SLURM_NNODES` → `SLURM_JOB_NUM_NODES` → `1` | +| `NODE_RANK` | `NODE_RANK` → `SLURM_NODEID` → `SLURM_PROCID` → `0` | +| `MASTER_ADDR` | `MASTER_ADDR` (if not empty / not `localhost`) → first hostname from `scontrol show hostnames "$SLURM_NODELIST"` | +| `MASTER_PORT` | `MASTER_PORT` → `1234` | +| `GPUS_PER_NODE` | `GPUS_PER_NODE` → `8` | + +Run it as a single task per node (the script invokes `torchrun` internally, which spawns one worker per GPU): + +> **Verify NCCL / network env first.** The script sets sensible `NCCL_`* defaults via `base_env.sh`, but auto-detection can pick the wrong device on multi-NIC nodes. Always confirm `NCCL_IB_HCA`, `NCCL_IB_GID_INDEX`, `NCCL_SOCKET_IFNAME`, and `GLOO_SOCKET_IFNAME` (set to the same value as `NCCL_SOCKET_IFNAME`) are correct for your fabric, and `export` overrides before running. See [§4](#4-cluster-specific-nccl-configuration) for cluster-specific values. + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate + +# export NCCL_IB_HCA=rdma1,rdma2,rdma3,rdma4,rdma5,rdma6,rdma7 +# export NCCL_IB_GID_INDEX=3 +# export NCCL_SOCKET_IFNAME=eno0 +# export GLOO_SOCKET_IFNAME=eno0 + +# Recommended: through the primus-cli SLURM wrapper. +runner/primus-cli slurm srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 \ + --nodelist --ntasks-per-node=1 \ + -- direct -- preflight --perf-test + +# Or, equivalently, with bare srun: +srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --nodelist \ + --ntasks-per-node=1 \ + runner/primus-cli direct -- preflight --perf-test +``` + + + +#### Wrapper vs. bare-srun + +Both forms target the **same** `primus-cli-direct.sh` launcher and produce identical workloads. The difference is only in how the SLURM context is constructed: + + +| Aspect | `primus-cli slurm srun -- direct --` (recommended) | Bare `srun ... primus-cli direct --` | +| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `MASTER_ADDR` resolution | Resolved **once** on the launching node via `scontrol show hostnames "$SLURM_NODELIST" | head -n1`, then propagated to every rank via `--env MASTER_ADDR=...`. | Each rank re-derives it inside `primus-cli-direct.sh` STEP 4.7 from `SLURM_`* (same result, more `scontrol` calls). | +| `NNODES` / `NODE_RANK` / `GPUS_PER_NODE` | Set explicitly by `slurm-entry.sh` via `--env`. | Derived from `SLURM_NNODES` / `SLURM_NODEID` / `SLURM_PROCID` inside `direct.sh`. | +| `slurm.*` config defaults | Honored (partition, time, ntasks-per-node, etc. from the active YAML). | Not consulted — you pass every flag explicitly to `srun`. | +| Default wall-time | `-t 4:00:00` is auto-added if you don't pass `--time`. | None — `srun` uses the cluster default (may reject the job). | +| `direct` keyword | **Required**: `primus-cli slurm srun ... -- direct -- `. Without `direct`, the wrapper routes through the **container** path. | N/A — there's only one path. | +| `--ntasks-per-node=1` | **Not auto-added**. Pass it on the CLI (before the first `--`) or set it in the `slurm.`* config. | **Not auto-added**. Pass it as an `srun` flag. | +| Best for | Production / repeatable runs. Same pattern as `train` / `benchmark` / `node_smoke`. | Ad-hoc runs where you want to compose with arbitrary `srun` flags (`--nodelist=$(...)`, `--exclude=...` from a runtime file, etc.). | + + +For the rest of this doc the examples use bare `srun` for brevity, but every example also works with the wrapper form by substituting `srun runner/primus-cli direct --` → `runner/primus-cli slurm srun -- direct --`. + +### Key `srun` flags + + +| Flag | Why it's necessary | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-c 128` | Allocate all CPU cores per task. Without this, SLURM may default to 1 core, which starves the RCCL network proxy threads and can cause >30× slowdown on perf tests. Set this to your node's core count. | +| `--gpus-per-node=8` | Grants GPU device access (`/dev/kfd`, `/dev/dri`). Required for non-container execution. | +| `--ntasks-per-node=1` | One launcher invocation per node; `primus-cli direct` then spawns 8 workers per node via `torchrun`. | +| `-t 00:45:00` | Wall-clock limit. Full perf tests on 8N usually finish well under 10 min. | + + +> Tip — check core count: `srun -N 1 --gpus-per-node=8 bash -c 'nproc'` + +--- + +## 4. Cluster-specific NCCL configuration + +`primus-cli direct` sources `runner/helpers/envs/base_env.sh`, which sets sensible defaults for `NCCL_`* and auto-detects `NCCL_IB_HCA` / `NCCL_SOCKET_IFNAME`. Pre-exported values from your shell take precedence, so the standard pattern is: + +```bash +export VAR=value +runner/primus-cli direct -- preflight ... +``` + +### Broadcom NICs (no AINIC) + +Most clusters fall here. The defaults from `base_env.sh` are usually fine, but the two values most commonly worth overriding are: + +```bash +export NCCL_CROSS_NIC=1 # default in base_env.sh is 0 +export NCCL_PXN_DISABLE=0 # default in base_env.sh is 1 + +srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --nodelist \ + --ntasks-per-node=1 \ + runner/primus-cli direct -- preflight --perf-test +``` + +### Pensando Pollara (AINIC) RDMA + +```bash +export USING_AINIC=1 +export NCCL_IB_GID_INDEX=1 # AINIC uses index 1 (default in base_env.sh is 3) +export NCCL_PXN_DISABLE=0 + +srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --nodelist \ + --ntasks-per-node=1 \ + runner/primus-cli direct -- preflight +``` + +> `primus-cli direct` *does* accept `--env KEY=VALUE` on its own command line (placed before `--`), in addition to the conventional `export`/`srun --export=` approaches. + +--- + +## 5. Launcher flags vs. preflight flags + +Anything you place **after** the `--` separator is forwarded verbatim to the `preflight` Python tool. The launcher (`primus-cli-direct.sh`) consumes a small set of flags **before** `--`. The one most users care about is `--silent`. + +### Launcher-only flags (before `--`) + + +| Flag | Effect | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--silent` | Back-pocket knob: redirect the launcher's and the Python tool's `stdout` to `/dev/null`. Launcher errors (`LOG_ERROR` / `LOG_WARN`, written to `stderr`) are preserved so real failures still surface; the log file under `logs/` captures everything. Exit code is propagated unchanged. **Not recommended** for normal use — you lose live progress; prefer the log file. | +| `--debug` | Verbose launcher logging (`PRIMUS_LOG_LEVEL=DEBUG`). Forwarded to the Python tool as `--debug` too. | +| `--dry-run` | Print the resolved configuration and final `torchrun` / `python3` command without executing. | +| `--single` | Force `python3` instead of `torchrun`. `node_smoke` auto-selects this; for `preflight` you usually want the default (`torchrun`). | +| `--env KEY=VALUE` | Inject an env var into the Python process (in addition to anything `export`-ed in the shell). | +| `--log_file PATH` | Redirect the captured tee log to a specific path (default: `logs/log_.txt`). | + + +See `runner/primus-cli direct --help` for the full set. + +### Forwarded `preflight` flags (after `--`, most common) + +See [Preflight](./preflight.md) for the full list. The most common are: + +- Mode selection: `--host`, `--gpu`, `--network`, `--perf-test`, `--tests`, `--quick` +- Test tuning: `--comm-sizes-mb`, `--intra-comm-sizes-mb`, `--inter-comm-sizes-mb`, `--intra-group-sizes`, `--inter-group-sizes`, `--ring-p2p-sizes-mb` +- Reporting: `--dump-path`, `--report-file-name`, `--disable-pdf`, `--plot` +- Reliability: `--comm-cleanup-delay-sec`, `--dist-timeout-sec` + +If you do not pass `--report-file-name`, `preflight` auto-generates a unique one of the form: + +``` +preflight-${NNODES}N-YYYYMMDD-HHMMSS +``` + +This guarantees that each run lands in its own files and prevents stale leftovers from earlier runs from being mistaken for fresh output. The auto-name logic now lives in the Python tool itself, so every call site (host `srun ... primus-cli direct`, `primus-cli slurm ... -- direct`, `primus-cli slurm ... -- container`) gets the same fresh name. + +### Examples + +The examples below all assume one of the two equivalent shell-prefix conventions. Pick whichever matches your habits — every example block in this section works with either definition: + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate + +# Recommended: through the primus-cli SLURM wrapper. Auto-resolves +# MASTER_ADDR/NNODES/NODE_RANK once on the launching node and propagates +# them via --env; honors slurm.* config defaults. +SRUN="runner/primus-cli slurm srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --ntasks-per-node=1 --nodelist --" +# Then in every example below, replace `$SRUN runner/primus-cli direct --` +# with just `$SRUN direct --`. (The wrapper expects the entry-mode keyword +# `direct` as the first token after the inner `--`.) + +# Equivalent: bare srun. NNODES/NODE_RANK/MASTER_ADDR get derived inside +# primus-cli-direct.sh's STEP 4.7 directly from SLURM_*; same net effect. +SRUN="srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --ntasks-per-node=1 --nodelist " +``` + +The examples in this section use the **bare-srun** form below for brevity (since `$SRUN runner/primus-cli direct -- preflight` reads naturally as one command line). To use the wrapper form instead, substitute `$SRUN runner/primus-cli direct --` → `$SRUN direct --` after exporting `SRUN` to the wrapper variant. + +#### A. Mode selection + +```bash +# Default: info report + every perf test +$SRUN runner/primus-cli direct -- preflight + +# Info-only (fast, no torch.distributed rendezvous) +$SRUN runner/primus-cli direct -- preflight --host --gpu --network --disable-pdf + +# Perf-only, every test +$SRUN runner/primus-cli direct -- preflight --perf-test + +# Fast pre-launch sanity preset (gemm + intra-AR + inter-AR @ 64,1024 MB, +# full intra-node group, full N-node inter group, low warmup/iter) +$SRUN runner/primus-cli direct -- preflight --quick +``` + +> **Note**: Mixing perf-mode flags (`--perf-test` / `--tests` / `--quick`) with info selectors (`--host` / `--gpu` / `--network`) makes preflight drop the info selectors with a `WARN`. Run two invocations if you want both reports. + +#### B. Test selection (`--tests`) + +```bash +# Only GEMM +$SRUN runner/primus-cli direct -- preflight --tests gemm + +# Only the inter-node bandwidth tests +$SRUN runner/primus-cli direct -- preflight --tests inter-allreduce,inter-alltoall + +# Only the inter-node ring P2P +$SRUN runner/primus-cli direct -- preflight --tests inter-ring-p2p + +# Combine: GEMM + inter-AR with overridden sizes/groups +$SRUN runner/primus-cli direct -- preflight \ + --tests gemm,inter-allreduce \ + --comm-sizes-mb 64,1024 \ + --inter-group-sizes all +``` + +Valid `--tests` tokens: `gemm`, `intra-allreduce`, `intra-alltoall`, `inter-allreduce`, `inter-alltoall`, `inter-p2p`, `inter-ring-p2p`, `all`. Unknown tokens fail fast (before NCCL init). + +#### C. Message sizes + +```bash +# One CSV applied to both intra and inter +$SRUN runner/primus-cli direct -- preflight --tests intra-allreduce,inter-allreduce \ + --comm-sizes-mb 8,128 + +# Different sizes for intra vs inter (override wins over --comm-sizes-mb) +$SRUN runner/primus-cli direct -- preflight --tests intra-allreduce,inter-allreduce \ + --comm-sizes-mb 8,128 --intra-comm-sizes-mb 4,32 + +# Inter-only override (also covers inter-p2p when enabled) +$SRUN runner/primus-cli direct -- preflight --tests inter-allreduce,inter-p2p \ + --comm-sizes-mb 8,128 --inter-comm-sizes-mb 16,512 +``` + +#### D. Group sizes + +```bash +# Custom intra-node group sizes (each must divide LOCAL_WORLD_SIZE) +$SRUN runner/primus-cli direct -- preflight \ + --tests intra-allreduce \ + --intra-group-sizes 4,8 + +# Custom inter-node groups: 2-node pairs and the full N-node group +$SRUN runner/primus-cli direct -- preflight \ + --tests inter-allreduce \ + --inter-group-sizes 2,all +``` + +> Note: for `inter-alltoall` only, every requested per-group node count is internally capped at **16** (real-world MoE training rarely dispatches across more nodes; see [`preflight.md` §5.2](./preflight.md#52-group-sizes) for the rationale). The other inter-node tests use the requested sizes unchanged. So on a 128-node cluster, `--tests inter-alltoall --inter-group-sizes all` actually runs at 16-node sub-groups, while `--tests inter-allreduce --inter-group-sizes all` runs at 128 nodes as written. + +#### E. Ring P2P sizes + +```bash +$SRUN runner/primus-cli direct -- preflight \ + --tests inter-ring-p2p \ + --ring-p2p-sizes-mb 5,20,80 +``` + +#### F. Plotting + +```bash +# Generate per-test bandwidth bar charts under //*.png +$SRUN runner/primus-cli direct -- preflight \ + --tests intra-allreduce,inter-allreduce --plot +``` + +#### G. Reliability knobs + +```bash +# Bump the per-phase cleanup delay. Default 2.0 is sufficient at every +# cluster size for the comm shapes preflight exercises (inter-alltoall +# is internally capped at 16 nodes; see preflight.md §5.2). Only bump +# this on very flaky networks or unusual kernel TIME_WAIT settings. +$SRUN runner/primus-cli direct -- preflight --quick --comm-cleanup-delay-sec 5 + +# Fail fast if torch.distributed rendezvous can't complete in 30s +$SRUN runner/primus-cli direct -- preflight --perf-test --dist-timeout-sec 30 +``` + +> Operating clusters at ≥ 128 nodes? See [`preflight.md` §7](./preflight.md#7-running-on-very-large-clusters--64-nodes) for the recommended OS-level tuning (widening `ip_local_port_range`) and per-test invocation patterns. With the §5.2 inter-alltoall cap in place, a default invocation is safe at every cluster size; the §7.3 OS tuning remains best-practice for any RDMA host. + +#### H. Reporting & output layout + +```bash +# Quick info-only check on 4 nodes, no PDF +$SRUN runner/primus-cli direct -- preflight --host --gpu --network --disable-pdf \ + --report-file-name info-4N + +# Perf test only, silenced (CI-friendly), explicit name. Note that --silent +# is consumed by primus-cli-direct.sh and must appear BEFORE the `--` +# separator; everything after `--` is forwarded to the preflight Python tool. +$SRUN runner/primus-cli direct --silent -- preflight --perf-test \ + --report-file-name nightly-4N-perf + +# Archive each run under its own directory +$SRUN runner/primus-cli direct -- preflight --quick \ + --dump-path /shared/preflight-archive/$(date +%Y%m%d-%H%M%S) +``` + +#### I. Backward-compat aliases + +These still work and are equivalent to flags above. Use them only when retrofitting older scripts. + +```bash +# Same as --host --gpu --network +$SRUN runner/primus-cli direct -- preflight --check-host --check-gpu --check-network + +# Same as --inter-group-sizes all AND drops inter-p2p +$SRUN runner/primus-cli direct -- preflight --perf-test --no-split-nodes-subgroup +``` + +#### J. Combined "production-ready" pre-launch screen + +```bash +# 1) Smoke first to prune broken nodes (note: --silent goes BEFORE `--`) +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct --silent -- node_smoke --tier2-perf + +# 2) Quick perf sanity on the survivors +srun -N -c 128 --gpus-per-node=8 --ntasks-per-node=1 \ + --exclude=$(paste -sd, output/preflight/failing_nodes.txt) \ + runner/primus-cli direct --silent -- preflight --quick \ + --comm-cleanup-delay-sec 5 --dist-timeout-sec 60 \ + --report-file-name screen-$(date +%Y%m%d-%H%M%S) +``` + +--- + +## 6. Outputs + +Reports are written to `--dump-path` (default: `output/preflight/`), with the basename from `--report-file-name` and a `_perf` suffix for performance reports: + + +| File | Produced by | Notes | +| ----------------- | ----------------------------------------------- | ------------------------- | +| `.md` | `--host --gpu --network` (or default selection) | Info report | +| `.pdf` | same, unless `--disable-pdf` | Info report PDF | +| `_perf.md` | `--perf-test` | Perf report (GEMM + comm) | +| `_perf.pdf` | same, unless `--disable-pdf` | Perf report PDF | + + +Only **rank 0** writes the report. After preflight completes, the Python tool prints the absolute path of every report file it produced to stdout. Under `--silent` these prints go to `/dev/null` along with everything else (one of the trade-offs of using `--silent`); without `--silent` the announcement is visible live. Sample output: + +``` +[Primus:Preflight] Report: /home/.../Primus/output/preflight/preflight-4N-20260428-201925.md +[Primus:Preflight] Report: /home/.../Primus/output/preflight/preflight-4N-20260428-201925_perf.md +``` + +--- + +## 7. Environment variable reference + +Variables read by `primus-cli direct` itself: + + +| Variable | Required | Default | Purpose | +| --------------- | -------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `VENV_ACTIVATE` | no | — | Path to the venv `bin/activate` script. Unset = no-op (use system / container Python). Set + missing file = fail-fast. | +| `NNODES` | no | `1` (or auto-derived from `SLURM_NNODES` / `SLURM_JOB_NUM_NODES`) | Number of nodes. Pre-exported always wins. | +| `NODE_RANK` | no | `0` (or auto-derived from `SLURM_NODEID` / `SLURM_PROCID`) | This node's rank. Pre-exported always wins. | +| `GPUS_PER_NODE` | no | `8` | GPUs per node | +| `MASTER_ADDR` | no | `localhost` (or first host from `scontrol show hostnames "$SLURM_NODELIST"`) | Rendezvous host. Pre-exported always wins. | +| `MASTER_PORT` | no | `1234` | Rendezvous port | + + +Variables consumed downstream by `primus-cli direct` / `base_env.sh` (set them via `export`): + + +| Variable | Default in `base_env.sh` | When to override | +| -------------------- | ------------------------ | ------------------------------------------------- | +| `NCCL_SOCKET_IFNAME` | auto-detected | Force a specific Ethernet interface for bootstrap | +| `NCCL_IB_HCA` | auto-detected | Force specific RDMA HCAs | +| `NCCL_IB_GID_INDEX` | `3` | `1` on AINIC clusters | +| `NCCL_CROSS_NIC` | `0` | `1` for multi-rail IB fabrics | +| `NCCL_PXN_DISABLE` | `1` | `0` to enable PXN multi-hop NIC sharing | +| `USING_AINIC` | unset | `1` on Pensando Pollara clusters | +| `NCCL_DEBUG` | unset | `INFO` for verbose NCCL logging | + + +--- + +## 8. Troubleshooting + +### `[ERROR] [direct] VENV_ACTIVATE is set but file does not exist: ...` + +`VENV_ACTIVATE` was set in the environment but the path it points at doesn't exist on this node. This is a fail-fast guard to prevent a silent fallback to system Python (which usually has the wrong `torch` / no ROCm). Either fix the path: + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate +``` + +… or unset it to fall back to the container / system Python: + +```bash +unset VENV_ACTIVATE +``` + +If the path looks right but the file still appears missing, confirm the venv lives on a filesystem visible from the node SLURM scheduled you onto. + +### `[Primus:Preflight] FAIL: No GPUs detected` + +The Python process inside the venv can't find ROCm. Diagnose with: + +```bash +srun --nodes=1 --nodelist= bash -c ' +echo "=== PATH ==="; echo $PATH +echo "=== LD_LIBRARY_PATH ==="; echo $LD_LIBRARY_PATH +echo "=== rocm-smi ==="; rocm-smi --showid 2>&1 +echo "=== Python torch check ===" +source ~/envs/preflight/.venv/bin/activate +python3 -c "import torch; print(\"hip:\", torch.version.hip); print(\"available:\", torch.cuda.is_available()); print(\"count:\", torch.cuda.device_count())" +' +``` + +If `LD_LIBRARY_PATH` is empty, set it explicitly: + +```bash +export LD_LIBRARY_PATH=/opt/rocm/lib:${LD_LIBRARY_PATH:-} +``` + +### Report announcement points at stale files + +This shouldn't happen with the current Python tool — the auto-generated unique report name (`preflight-${NNODES}N-`) ensures every run gets a fresh path. If you explicitly pass `--report-file-name X`, you're responsible for choosing a name that doesn't collide with prior runs. + +### Slow perf tests (~30× expected) + +Almost always a symptom of insufficient CPU cores. Pass `-c ` to `srun` so RCCL's network proxy threads have CPU to spawn on. Verify with `srun -N 1 --gpus-per-node=8 bash -c 'nproc'`. + +### Using `conda` instead of venv + +`primus-cli direct` does `source "$VENV_ACTIVATE"`, which works for venv/uv but not directly for conda. Two options: + +1. Create a venv inside the conda env and point `VENV_ACTIVATE` at that venv's activate script. +2. Write a small shim activate script (e.g. `~/envs/conda-shim.sh`) that activates conda and the desired env, then point `VENV_ACTIVATE` at it: + ```bash + # ~/envs/conda-shim.sh + source "$HOME/miniconda3/etc/profile.d/conda.sh" + conda activate + ``` + +### "Address already in use" during perf tests + +This means a node's kernel ephemeral-port pool was momentarily exhausted while preflight was building many communicators in a short window, so an outgoing `bind()` could not find a free port. It is a preflight-specific artifact of repeated communicator setup/teardown — a real training job builds its communicators once and reuses them — not a training failure mode. See [`preflight.md` §7](./preflight.md#7-running-on-very-large-clusters--64-nodes) for details. + +Preflight has two complementary defenses: + +1. The **inter-node alltoall sub-group is internally capped at 16 nodes** (see [`preflight.md` §5.2](./preflight.md#52-group-sizes)) — the only test that, at large scale, opens enough simultaneous connections to approach the per-node ephemeral-port pool. The cap eliminates this failure mode by construction. +2. A **global barrier + `--comm-cleanup-delay-sec` sleep** (default 2 s) is inserted after every comm destroy, primarily for cross-rank synchronization across the destroy → setup transition. + +If you still see `Address already in use` (e.g. on a network with an unusually narrow ephemeral-port range), the directly relevant **OS-level tuning** is widening that range — best-practice for any RDMA host: + +```bash +# Widen the ephemeral port range from ~28k to ~64k. This is the +# OS knob that directly addresses the binding constraint. +sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535" +``` + +As a fallback, raise the per-phase delay: + +```bash +# Bump the per-phase delay (default 2 s) on a particularly stressed +# network. Rarely needed in practice with the §5.2 alltoall cap. +runner/primus-cli direct -- preflight --comm-cleanup-delay-sec 5 +``` + +See [`preflight.md` §7](./preflight.md#7-running-on-very-large-clusters--64-nodes) for persistence and recommended large-cluster invocation patterns (split tests into separate runs, etc.). + +If the error occurs at `init_process_group` (before tests even start), it typically means a previous job left the rendezvous port (`MASTER_PORT`, default `1234`) in `TIME_WAIT`. Either wait ~60 s or use a different port: + +```bash +export MASTER_PORT=1235 +``` + +### Capturing full output + +The launcher already writes a complete log to `logs/log_.txt` (configurable via `--log_file PATH`), even under `--silent`. If you also want a copy at the call site, redirect there: + +```bash +srun ... runner/primus-cli direct -- preflight --perf-test \ + 2>&1 | tee preflight-$(date +%Y%m%d-%H%M%S).log +``` + +--- + +## 9. Automated node bisection (finding the bad node in an NCCL hang) + +When a cluster-wide preflight run hangs or fails, use +[`tools/preflight_bisect/bisect.py`](https://github.com/AMD-AGI/Primus/blob/main/tools/preflight_bisect/bisect.py) to +run `preflight --perf-test` on smaller Slurm node subsets until suspect nodes +are isolated. + +### Prerequisites + +1. Working non-container preflight setup from the sections above, with + `VENV_ACTIVATE` exported from a shared filesystem path. +2. Run from the SLURM login/head node, where both `scontrol` and `srun` are + available. +3. Run from inside a Slurm allocation, or provide a Slurm nodelist explicitly. + +### Example from inside an allocation + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate +mkdir -p output + +python tools/preflight_bisect/bisect.py \ + --nodelist "$SLURM_NODELIST" \ + --output-dir "output/bisect-$(date +%Y%m%d-%H%M%S)" \ + --trial-timeout-sec 600 \ + --slurm-time 00:15:00 \ + --preflight-env USING_AINIC=1 \ + --preflight-env NCCL_IB_GID_INDEX=1 \ + --preflight-env NCCL_CROSS_NIC=1 \ + --preflight-env NCCL_PXN_DISABLE=0 \ + 2>&1 | tee output/bisect-latest.log +``` + +Adjust the `--preflight-env` lines to match your cluster. Per-trial logs and a +final `summary.txt` are written under `--output-dir`. + +> Note: Set `--trial-timeout-sec` high enough for a healthy subset to finish. +> Too small a timeout can turn slow-but-good trials into false failures, causing +> the bisection to explore extra paths. +> +> Note: `--preflight-env KEY=VALUE` values are concatenated into a single +> `srun --export=ALL,...` argument, so values must not contain commas or +> whitespace. Keep comma-containing values as normal exported environment +> variables. + +--- + +## 10. See also + +- [Preflight](./preflight.md)—full reference for the `preflight` subcommand and its flags +- [CLI User Guide](./cli-reference.md)—container-based and `primus-cli slurm` workflows +- [`runner/primus-cli-direct.sh`](https://github.com/AMD-AGI/Primus/blob/main/runner/primus-cli-direct.sh)—the direct launcher itself (`primus-cli direct` dispatches here) +- [`primus/tools/preflight/`](https://github.com/AMD-AGI/Primus/tree/main/primus/tools/preflight)—preflight implementation +- [`tools/preflight_bisect/bisect.py`](https://github.com/AMD-AGI/Primus/blob/main/tools/preflight_bisect/bisect.py)—bisect wrapper for narrowing down failing nodes in multi-node preflight runs diff --git a/docs/02-user-guide/preflight.md b/docs/02-user-guide/preflight.md new file mode 100644 index 000000000..eb1b325b4 --- /dev/null +++ b/docs/02-user-guide/preflight.md @@ -0,0 +1,417 @@ +# Preflight + +`preflight` is Primus' cluster diagnostic tool. It produces: + +- A **fast info report** (host / GPU / network configuration), and +- A configurable suite of **performance tests** (GEMM TFLOPS, intra-node and inter-node communication bandwidth, P2P, ring P2P). + +Use it to spot misconfiguration, hardware degradation, or perf outliers **before** committing a large distributed training run to a global rendezvous. + +- **User-facing entry**: `primus-cli ... -- preflight [args]` +- **No-container launcher**: `runner/primus-cli direct -- preflight ...` — see [`preflight-without-container.md`](./preflight-without-container.md). +- **Implementation entrypoint**: `primus/cli/subcommands/preflight.py` → `primus/tools/preflight/preflight_perf_test.py`. + +> Looking for a faster, distributed-rendezvous-free per-node screen? See [`node-smoke-test-instruction.md`](./node-smoke-test-instruction.md). The recommended workflow is **smoke first, preflight second** — see [§10 Comparison with node-smoke](#10-comparison-with-node-smoke). + +--- + +## 1. Two run modes (and how preflight picks one) + +Preflight has two report types, controlled by a single precedence rule: + +| Mode | Triggered by | What it does | +|---|---|---| +| **Info-only** | `--host`, `--gpu`, `--network` (in any combination) | Lightweight host / GPU / network introspection. Emits a per-node report **without requiring a rendezvous**; multi-node aggregation then uses a **timeout-bounded** rendezvous (`--dist-timeout-sec`), so it never hangs indefinitely on network misconfig. | +| **Perf-only** | `--perf-test`, `--tests ...`, or `--quick` | Runs the configured perf tests under a global rendezvous. **Implied** by `--tests` and `--quick`. | +| **Default (info + perf)** | No flags at all | Runs the info report first, then every perf test. | + +### Mode precedence + +1. **Any of `--perf-test` / `--tests` / `--quick` is set → perf-only mode.** + If info selectors (`--host`/`--gpu`/`--network`) are also present, they are dropped and a `WARN` is emitted (also written as a `> Note:` at the top of the perf report). To get both reports, run two invocations. +2. **Otherwise, any of `--host`/`--gpu`/`--network` is set → info-only mode.** + Perf-only tuning knobs (e.g. `--comm-sizes-mb`) are inert in this mode and trigger a single `WARN` listing them. +3. **Otherwise (no flags) → default**: info report **first** (no rendezvous), then perf tests. + +The default order ensures you always get a report even if `torch.distributed` initialization later hangs. + +--- + +## 2. Quick start + +### Info report only (fast) + +```bash +primus-cli direct -- preflight --host --gpu --network +``` + +### Full preflight (info + every perf test) + +```bash +primus-cli direct -- preflight +``` + +### Perf tests only + +```bash +primus-cli direct -- preflight --perf-test +``` + +### Fast pre-launch sanity check + +```bash +primus-cli direct -- preflight --quick +``` + +Equivalent on SLURM via `primus-cli slurm`: + +```bash +primus-cli slurm srun -N 4 -- preflight --quick +``` + +Without a container, see [`preflight-without-container.md`](./preflight-without-container.md) for the equivalent `runner/primus-cli direct -- preflight ...` invocations. + +--- + +## 3. Test selection (`--tests`) + +`--tests` takes a comma-separated list of canonical tokens (or `all`). Implies `--perf-test`. + +| Token | What it runs | +|---|---| +| `gemm` | Single-GPU square GEMM TFLOPS sweep. | +| `intra-allreduce` | Intra-node `all_reduce` bandwidth at every selected `--intra-group-sizes` x `--intra-comm-sizes-mb`. | +| `intra-alltoall` | Intra-node `all_to_all` bandwidth, same configuration matrix. | +| `inter-allreduce` | Inter-node `all_reduce` bandwidth at every selected `--inter-group-sizes` x `--inter-comm-sizes-mb`. | +| `inter-alltoall` | Inter-node `all_to_all` bandwidth, same configuration matrix. | +| `inter-p2p` | Inter-node point-to-point send/recv between fixed **adjacent 2-node pairs** (does not use `--inter-group-sizes`). Sized by `--inter-comm-sizes-mb`, falling back to `--comm-sizes-mb`. | +| `inter-ring-p2p` | Inter-node ring-pattern P2P, sized by `--ring-p2p-sizes-mb`. | +| `all` | Every token above. Default when `--tests` is omitted. | + +Examples: + +```bash +# GEMM only +primus-cli direct -- preflight --tests gemm + +# Just the inter-node bandwidth tests +primus-cli direct -- preflight --tests inter-allreduce,inter-alltoall + +# Combine with size overrides +primus-cli direct -- preflight \ + --tests gemm,inter-allreduce \ + --comm-sizes-mb 64,1024 \ + --inter-group-sizes all +``` + +Unknown tokens fail fast (before any rendezvous): + +```text +[Primus:Preflight] ERROR: invalid perf config: --tests: unknown token 'gem'. +Valid tokens: gemm, intra-allreduce, intra-alltoall, inter-allreduce, +inter-alltoall, inter-p2p, inter-ring-p2p, all +``` + +--- + +## 4. Quick preset (`--quick`) + +`--quick` is the recommended **pre-launch sanity** preset. Implies `--perf-test`. It substitutes: + +| Knob | `--quick` value | +|---|---| +| `--tests` | `gemm,intra-allreduce,inter-allreduce` | +| `--comm-sizes-mb` | `64,1024` | +| `--intra-group-sizes` | `LOCAL_WORLD_SIZE` (full intra-node group only) | +| `--inter-group-sizes` | `all` (full N-node group only) | +| `warmup` | `5` | +| `iteration` | `20` | + +**User-supplied flags override the preset.** For example: + +```bash +# Quick preset, but with a custom size set +primus-cli direct -- preflight --quick --comm-sizes-mb 32,256 +``` + +A full perf run with default knobs takes minutes; `--quick` typically finishes in <60s on healthy hardware. + +--- + +## 5. Tuning the perf tests + +All perf tuning knobs default to `None` so preflight can tell whether you set them. When unset, the documented defaults below apply. + +### 5.1 Message sizes (collective + P2P) + +| Flag | Default | Applies to | +|---|---|---| +| `--comm-sizes-mb CSV` | `2,4,8,16,32,64,128,256,512,1024` | Default for both intra- and inter-node `allreduce` / `alltoall` and `inter-p2p` when no specific override is given. | +| `--intra-comm-sizes-mb CSV` | falls back to `--comm-sizes-mb` | Override for **intra-node** `allreduce` / `alltoall`. | +| `--inter-comm-sizes-mb CSV` | falls back to `--comm-sizes-mb` | Override for **inter-node** `allreduce` / `alltoall` / `inter-p2p`. | + +```bash +# Smaller, focused sweep +primus-cli direct -- preflight --comm-sizes-mb 8,128 + +# Different sizes for intra vs inter +primus-cli direct -- preflight \ + --tests intra-allreduce,inter-allreduce \ + --comm-sizes-mb 8,128 \ + --intra-comm-sizes-mb 4,32 +``` + +### 5.2 Group sizes + +| Flag | Default | Notes | +|---|---|---| +| `--intra-group-sizes CSV` | `2,4,8` | Each value must divide `LOCAL_WORLD_SIZE`. | +| `--inter-group-sizes CSV` | `2,4,all` | `all` means the full N-node group. Other values are subgroup sizes. **Only `inter-allreduce` and `inter-alltoall` consult this flag** — for `inter-alltoall`, every requested per-group node count is internally capped at **16** before deduping (see "Inter-node alltoall is capped at 16 nodes" below), while `inter-allreduce` uses the requested sizes unchanged. `inter-p2p` and `inter-ring-p2p` ignore this flag (fixed adjacent 2-node pairs and a full-cluster ring, respectively). | + +```bash +# All-GPU intra + full N-node inter only +primus-cli direct -- preflight \ + --tests intra-allreduce,inter-allreduce \ + --intra-group-sizes 8 \ + --inter-group-sizes all +``` + +Validation is gated by which tests are actually selected. For example, `--tests gemm --intra-group-sizes 3` does **not** abort on a host with `LOCAL_WORLD_SIZE=8`; the intra-group constraint is only checked when an intra test is enabled. + +#### Inter-node alltoall is capped at 16 nodes + +Regardless of the cluster size or what `--inter-group-sizes` requests, the `inter-alltoall` test always runs on per-group node counts of at most **16**. Concretely, every requested value `G` is replaced with `min(G, 16)`, and the resulting list is deduped. Examples: + +| Cluster | `--inter-group-sizes` | Requested (resolved) | `inter-alltoall` actually runs | +|---|---|---|---| +| 8 N | `all` | `[8]` | `[8]` (no change) | +| 64 N | `2,4,all` | `[2, 4, 64]` | `[2, 4, 16]` | +| 128 N | `2,4,16,32,all` | `[2, 4, 16, 32, 128]` | `[2, 4, 16]` | +| 128 N | `64` | `[64]` | `[16]` | + +When the cap actually changes the list, preflight emits a single one-line WARN to stdout so the row labels in the report (e.g. `alltoall-16nodes` instead of `alltoall-128nodes`) are not surprising. + +Why the cap, and why 16: + +- **It matches real-world usage.** Production MoE training rarely dispatches tokens across more than ~8 nodes (for example, DeepSeek-V3's largest published configuration uses `EP=64` over 8 nodes with per-token dispatch capped at 4 nodes). A 16-node ceiling covers every published configuration with comfortable headroom. +- **It keeps the test from exhausting per-node network resources.** A large `inter-alltoall` sub-group opens a near-full mesh of connections per rank during communicator setup. Capping it at 16 keeps that well within a node's ephemeral-port budget and avoids spurious `Address already in use` failures at scale (see [§7](#7-running-on-very-large-clusters--64-nodes)). +- **Other inter-node tests are unaffected.** The cap applies only to `inter-alltoall`: `inter-allreduce` uses `--inter-group-sizes` unchanged, while `inter-p2p` and `inter-ring-p2p` don't consult it at all. All three also open far fewer simultaneous connections than alltoall. +- **It is intentionally not configurable.** This is a known-safe ceiling for the communication shapes preflight characterizes, not a tuning knob. + +### 5.3 Ring P2P sizes + +| Flag | Default | Applies to | +|---|---|---| +| `--ring-p2p-sizes-mb CSV` | `10,20,40,80,160` | `inter-ring-p2p` only. | + +```bash +primus-cli direct -- preflight \ + --tests inter-ring-p2p \ + --ring-p2p-sizes-mb 5,20,80 +``` + +### 5.4 Plotting + +| Flag | Effect | +|---|---| +| `--plot` | After each perf test, write per-size bandwidth bar charts under `//` and reference them in the markdown report. | + +--- + +## 6. Reliability knobs + +Two knobs that are inert under happy-path conditions but matter at scale or on flaky networks. + +### 6.1 `--comm-cleanup-delay-sec FLOAT` (default `2.0`) + +Delay (seconds) inserted between destroying NCCL/RCCL process groups and creating new ones. It provides cross-rank synchronization across the destroy → setup transition, so a rank doesn't try to connect to a peer whose listener hasn't finished closing. + +- Default `2.0` is essentially free and worth keeping at every cluster size. +- Set to `0` to disable the sleep entirely (barrier only). +- Bump to e.g. `5` only on very flaky networks. + +```bash +# Small/medium clusters: the default is fine. Override only if you +# see port-reuse races on a very flaky network. +primus-cli slurm srun -N 8 -- preflight --quick --comm-cleanup-delay-sec 5 +``` + +See [§7](#7-running-on-very-large-clusters--64-nodes) for guidance on running at very large scale. + +### 6.2 `--dist-timeout-sec INT` (default `120`) + +Timeout (seconds) for `torch.distributed.init_process_group`. If init does not complete within this many seconds, preflight writes the info report (when applicable) plus a `Distributed Init` failure section to the markdown report, prints a clear error, and exits `2` — instead of hanging indefinitely. + +```bash +# Fail fast if rendezvous does not work +primus-cli direct -- preflight --perf-test --dist-timeout-sec 30 +``` + +--- + +## 7. Running on very large clusters (≥ 64 nodes) + +At very large scale there are a few practical considerations beyond what smaller runs encounter. A default `preflight` invocation still runs correctly at every scale we test (up to 128 nodes) without special flags — the points below are limitations to be aware of, plus recommendations that make large-cluster runs faster and easier to interpret. + +### 7.1 Limitations + +- **`inter-alltoall` is measured on at most 16 nodes per sub-group.** Regardless of cluster size or `--inter-group-sizes`, the alltoall test is capped at 16-node sub-groups (see [§5.2](#52-group-sizes)). This is intentional — it matches real-world MoE dispatch patterns and keeps the test from exhausting per-node network resources during communicator setup — but it does mean preflight will not report alltoall bandwidth for a larger topology. The cap applies only to `inter-alltoall`: `inter-allreduce` honors `--inter-group-sizes` unchanged, while `inter-p2p` and `inter-ring-p2p` don't use it at all. +- **preflight briefly builds many communicators.** Unlike a real training job — which creates its communicators once at startup and reuses them — preflight repeatedly builds and tears down large communicators in a short window. On a cluster with an unusually narrow ephemeral-port range this can occasionally surface as `Address already in use` during setup. It is a preflight-specific artifact rather than a training failure mode; the one-line OS fix is in [§7.3](#73-optional-os-tuning). + +### 7.2 Recommended: split large runs by test family + +For clusters at or beyond ~128 nodes, run one test family per invocation instead of one large run. Each invocation stays short, and it becomes easy to see which specific communication shape is degraded if a number looks off. + +```bash +# 1) GPU + intra-node fabric first (cheap, no inter-node OOB churn). +primus-cli slurm srun -N 128 -- preflight \ + --tests gemm,intra-allreduce,intra-alltoall + +# 2) Inter-node DP-style collectives, all-nodes group only. +primus-cli slurm srun -N 128 -- preflight \ + --tests inter-allreduce \ + --inter-group-sizes all +# Note: --inter-group-sizes all is honored here for inter-allreduce. +# For inter-alltoall it would be capped at 16 (see §5.2). +primus-cli slurm srun -N 128 -- preflight \ + --tests inter-alltoall \ + --inter-group-sizes all + +# 3) Inter-node PP-style ring P2P (the test that benefits most from +# isolation — it's the closest match to what real pipeline-parallel +# training actually exercises). +primus-cli slurm srun -N 128 -- preflight \ + --tests inter-ring-p2p + +# 4) Optional: pairwise inter-node P2P scan (useful for finding a +# single bad link, slower because it walks many pairs). +primus-cli slurm srun -N 128 -- preflight \ + --tests inter-p2p +``` + +Each invocation tears down its own `WORLD` on exit and touches only one `--tests` value, so you get a per-test wall clock, can re-run a single phase in isolation, and get a separate report per run via `--report-file-name`. + +### 7.3 Optional OS tuning + +`preflight` runs fine with default OS settings at every scale we test. If you do hit `Address already in use` on a cluster with a narrow ephemeral-port range, widen the range — this is good general practice for any RDMA host regardless of preflight: + +```bash +# Widen the per-node ephemeral port range (default ~28k → ~64k ports). +sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535" + +# Persist across reboots: +echo 'net.ipv4.ip_local_port_range = 1024 65535' | sudo tee /etc/sysctl.d/99-large-cluster.conf +sudo sysctl --system +``` + +--- + +## 8. Reporting + +| Flag | Default | Effect | +|---|---|---| +| `--dump-path DIR` | `output/preflight` | Output directory for reports + plots. | +| `--report-file-name NAME` | auto-generated `preflight-${NNODES}N-YYYYMMDD-HHMMSS` | Base name for report files. Omit to let preflight auto-generate a unique timestamped name (prevents stale leftovers from prior runs being mistaken for fresh output). Pass an explicit value when you want a stable / well-known filename. | +| `--disable-pdf` | enabled | Skip PDF generation (Markdown only). Useful when `weasyprint`/`markdown2` aren't installed. | + +Output files: + +| File | Produced when | Notes | +|---|---|---| +| `.md` / `.pdf` | Info-only mode, or default mode | Info report. | +| `_perf.md` / `_perf.pdf` | Perf-only mode, or default mode | Perf report (GEMM + comm). | + +Only **rank 0** writes the report. + +### Perf report layout + +A `_perf.md` produced by a default run contains, in order: + +1. (Optional) `> Note:` line listing dropped info selectors. +2. `# Nodes` legend — `Node N → Hostname` table, used by every subsequent table to keep host columns compact. +3. `=======IB Bandwidth roofline (GB/s)=======` — bandwidth of the first IB device on Node 0. +4. Per enabled test, in this order: `gemm`, `intra-comm`, `inter-comm`, `inter-p2p`, `inter-ring-p2p`. Each section has a configuration line, a results table (Node / Rank / hostname / per-size GB/s), optional plots, and a per-rank wall-clock summary. +5. `[Primus:Preflight] done in s` lines on stdout for at-a-glance progress on the launching shell. + +--- + +## 9. Backward-compat aliases + +| Flag | Equivalent | Notes | +|---|---|---| +| `--check-host`, `--check-gpu`, `--check-network` | `--host`, `--gpu`, `--network` | Same behavior. Keep working for older scripts. | +| `--no-split-nodes-subgroup` | `--inter-group-sizes all` **and** drops `inter-p2p` | Pre-`--tests`/`--inter-group-sizes` alias. Use the new flags in new scripts. | + +--- + +## 10. Comparison with node-smoke + +| Aspect | `node-smoke` | `preflight` | +|---|---|---| +| Rendezvous | None — every node independent | Global `torch.distributed` | +| Wall clock | ~30–60 s for 6 nodes (Tier 1+2) | Minutes; scales with N for inter-node tests | +| Granularity | Per-node PASS/FAIL | Per-rank measurements (no auto-fail by default) | +| Inter-node bandwidth matrix | Not tested (intentionally) | Yes (allreduce/alltoall/p2p/ring-p2p) | +| Drift detection | Yes (versions, NIC firmware, port count) | No | +| Host limits / RDMA roll-call | Yes (hard fail) | Reported via `collect_*_info` only | +| Output format | Per-node JSON + cluster md + SLURM-ready txt | Markdown + PDF | + +**Recommended workflow**: run `node-smoke` first to exclude broken nodes, then run `preflight` on the surviving set to get cross-node bandwidth measurements. See [`node-smoke-test-instruction.md`](./node-smoke-test-instruction.md) §4 ("Quick start") for the integration commands. + +--- + +## 11. Validation & error handling + +Preflight resolves the perf config **before** any distributed rendezvous. This means typos and bad sizes/group-sizes fail in seconds, not after a 120s NCCL init: + +```text +[Primus:Preflight] ERROR: invalid perf config: --tests: unknown token 'gem'. +[Primus:Preflight] ERROR: invalid perf config: + --intra-group-sizes: [3] do not divide LOCAL_WORLD_SIZE=8 +[Primus:Preflight] ERROR: invalid perf config: --comm-sizes-mb: values must be positive (got 0) +``` + +In info-only mode, perf-only tuning knobs trigger a single warning so you notice them but they don't abort: + +```text +[Primus:Preflight] WARN: --comm-sizes-mb,--intra-group-sizes have no effect +in info-only mode (no --perf-test/--tests/--quick). +``` + +In default mode where info selectors are dropped because perf intent was set, the preserved warning is also written into the perf report header: + +```text +> Note: info selectors --host were dropped because perf mode +> (--perf-test/--tests/--quick) takes precedence. Run them in a separate +> invocation if you want both reports. +``` + +--- + +## 12. Operational tips + +- **For multi-node runs, always use `primus-cli slurm` or `primus-cli direct` under `srun`** so distributed environment variables (`NNODES` / `NODE_RANK` / `MASTER_ADDR`) are set correctly. +- **Make sure slurm requests GPU resources**. For some clusters, you may need to explicitly request GPU resources with `srun -N --gpus-per-node=`. +- **Insufficient CPU cores cause >30x perf slowdowns** — pass `srun -c ` so RCCL's network proxy threads have CPU to spawn on. Verify with `srun -N 1 --gpus-per-node=8 bash -c 'nproc'`. +- **For a quick environment snapshot**, prefer `--host --gpu --network` — you always get a local per-node report even on a broken network, and any multi-node aggregation is timeout-bounded (`--dist-timeout-sec`), so the command never hangs. +- **Between each communication test phase**, preflight performs a global barrier + `--comm-cleanup-delay-sec` sleep (default 2 s) for cross-rank sync across the destroy → setup transition. The default works at every cluster size we test up to 128 nodes. See [§7](#7-running-on-very-large-clusters--64-nodes) for large-cluster guidance. +- **For pre-launch screening of a large cluster**, the recommended sequence is: + 1. `node-smoke` to prune broken nodes (`failing_nodes.txt`). + 2. `preflight --quick` on the surviving nodes for the perf sanity numbers. + 3. `preflight` (full) on the same set if the `--quick` numbers raise a flag. + +--- + +## 13. Running preflight without a container + +If you cannot (or prefer not to) use a container, see [`preflight-without-container.md`](./preflight-without-container.md) for the step-by-step `runner/primus-cli direct -- preflight ...` walkthrough — Python virtual-environment setup, SLURM invocation patterns, NCCL configuration for Broadcom and Pensando (AINIC) clusters, and many configurable-knob examples. + +--- + +## 14. See also + +- [`preflight-without-container.md`](./preflight-without-container.md) — quick-start guide for `primus-cli direct -- preflight` (no container). +- [`node-smoke-test-instruction.md`](./node-smoke-test-instruction.md) — full guide for the per-node smoke test (screen + exclude bad nodes). +- [`runner/primus-cli-direct.sh`](../../runner/primus-cli-direct.sh) — non-container launcher (`primus-cli direct` dispatches here). +- [`primus/tools/preflight/`](../../primus/tools/preflight/) — implementation. +- [`primus/tools/preflight/preflight_args.py`](../../primus/tools/preflight/preflight_args.py) — canonical CLI definition (single source of truth for flags + defaults). diff --git a/docs/02-user-guide/pretraining.md b/docs/02-user-guide/pretraining.md new file mode 100644 index 000000000..6734e98e5 --- /dev/null +++ b/docs/02-user-guide/pretraining.md @@ -0,0 +1,291 @@ +# Pretraining workflows + +Primus is a YAML-driven training stack for AMD GPUs. You select a **backend** (Megatron-LM, TorchTitan, JAX MaxText, Megatron Bridge), point `train pretrain` at a **configuration YAML**, and launch Primus with the unified CLI (`runner/primus-cli`) in **direct**, **container**, or **Slurm** mode. See [CLI reference](cli-reference.md) and [Configuration system](configuration-system.md). + +This section helps you understand concepts related to the Primus workflow: how backends work, YAML structure and inheritance, parallelism vocabulary, the full per-backend configuration inventory, and so on. If you already understand the concepts and just need the specific commands to run your training with Primus, see [Backend training recipes](training-recipes.md). + +--- + +## Overview + +The following table describes the four backend types supported by Primus and their typical uses. + +| Backend | Framework | Typical use | +| --- | --- | --- | +| Megatron-LM | `framework: megatron` | Large-scale transformer pretraining with Megatron-style parallelism (TP/PP/EP). | +| TorchTitan | `framework: torchtitan` | PyTorch-native scaled training (FSDP / tensor / pipeline / expert parallelism per config). | +| MaxText (JAX) | `framework: maxtext` | JAX/MaxText single- and multi-node runs; parallelism via MaxText `ici_*` / `dcn_*` settings. | +| Megatron Bridge | `framework: megatron_bridge` | Bridge-oriented workflows (configure like other backends; see parameter reference). | + +> Several setup steps apply to **all** backends (mock vs. real data, Hugging Face tokens, scaling to multiple nodes, and HipBLASLt autotuning). After you read the backend section that applies to you, see [Common patterns](#common-patterns) below. + +--- + +## Megatron-LM pretraining + +### Quick start (container mode) + +From the root of the clone of the [Primus repository](https://github.com/AMD-AGI/Primus), with Docker or Podman available, the following command starts the training in container mode: + +```bash +./runner/primus-cli container -- train pretrain \ + --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml +``` + +This uses the default image from `runner/.primus.yaml` (`rocm/primus:v26.4` unless overridden). The project tree is mounted into the container automatically by `runner/primus-cli-container.sh`. + +### Example configurations under `examples/megatron/configs/MI300X/` + +The following files ship in the repository (sorted by name). Parallelism columns are taken from `tensor_model_parallel_size` / `pipeline_model_parallel_size` / `expert_model_parallel_size` in each file (literals or `${PRIMUS_TP:…}` defaults). + +| Config | TP | PP | EP | +| --- | --- | --- | --- | +| `deepseek_v2-BF16-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:4}` | `${PRIMUS_EP:8}` | +| `deepseek_v2-FP8-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:4}` | `${PRIMUS_EP:8}` | +| `deepseek_v2_lite-BF16-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `deepseek_v2_lite-FP8-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `deepseek_v3-BF16-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `deepseek_v3-FP8-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `gpt_oss_20B-BF16-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `gpt_oss_20B-FP8-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `grok1-BF16-pretrain.yaml` | `1` | `4` | `8` | +| `grok1-FP8-pretrain.yaml` | `1` | `4` | `8` | +| `grok2-BF16-pretrain.yaml` | `1` | `4` | `8` | +| `grok2-FP8-pretrain.yaml` | `1` | `4` | `8` | +| `llama2_13B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `llama2_13B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `llama2_70B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `llama2_70B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `llama2_7B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `llama2_7B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `llama3.1_405B-BF16-pretrain.yaml` | `8` | `8` | `1` | +| `llama3.1_405B-FP8-pretrain.yaml` | `8` | `8` | `1` | +| `llama3.1_70B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `llama3.1_70B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `llama3.1_8B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `llama3.1_8B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `llama3.2_1B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `llama3.2_1B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `llama3.2_3B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `llama3.2_3B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `llama3.3_70B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `llama3.3_70B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `llama3_70B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `llama3_70B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `llama3_8B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `llama3_8B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `llama4_17B128E-BF16-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `llama4_17B128E-FP8-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `llama4_17B16E-BF16-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `llama4_17B16E-FP8-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `mamba_370M-pretrain.yaml` | `1` | `1` | `1` | +| `mixtral_8x22B_v0.1-BF16-pretrain.yaml` | `1` | `4` | `8` | +| `mixtral_8x22B_v0.1-FP8-pretrain.yaml` | `1` | `4` | `8` | +| `mixtral_8x7B_v0.1-BF16-pretrain.yaml` | `1` | `1` | `8` | +| `mixtral_8x7B_v0.1-FP8-pretrain.yaml` | `1` | `1` | `8` | +| `qwen2.5_14B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `qwen2.5_14B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `qwen2.5_32B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `qwen2.5_32B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `qwen2.5_3B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `qwen2.5_3B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `qwen2.5_72B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `qwen2.5_72B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `qwen2.5_7B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `qwen2.5_7B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `qwen3_14B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `qwen3_14B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `qwen3_235B_A22B-BF16-pretrain.yaml` | `1` | `1` | `8` | +| `qwen3_235B_A22B-FP8-pretrain.yaml` | `1` | `1` | `8` | +| `qwen3_30B_A3B-BF16-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `qwen3_30B_A3B-FP8-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `qwen3_32B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `qwen3_32B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `qwen3_4B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `qwen3_4B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `qwen3_5_35B_A3B-BF16-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `qwen3_5_35B_A3B-FP8-pretrain.yaml` | `${PRIMUS_TP:1}` | `${PRIMUS_PP:1}` | `${PRIMUS_EP:8}` | +| `qwen3_8B-BF16-pretrain.yaml` | `1` | `1` | `1` | +| `qwen3_8B-FP8-pretrain.yaml` | `1` | `1` | `1` | +| `zebra_llama_1B-pretrain.yaml` | `1` | `1` | `1` | +| `zebra_llama_3B-pretrain.yaml` | `1` | `1` | `1` | +| `zebra_llama_8B-pretrain.yaml` | `1` | `1` | `1` | + +### Sample YAML file (`llama2_7B-BF16-pretrain.yaml`) explained + +Path: `examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml` + +| Section | Role | +| --- | --- | +| `work_group`, `user_name`, `exp_name`, `workspace` | Run identity and output root (supports `${VAR:default}` substitution). | +| `modules.pre_trainer.framework` | `megatron` selects Megatron-LM integration. | +| `config: pre_trainer.yaml` | Module preset under `primus/configs/modules/megatron/`. | +| `model: llama2_7B.yaml` | Model preset under `primus/configs/models/megatron/` (extends `llama2_base.yaml` → …). | +| `overrides` | Run-specific training knobs: iterations, batching, LR, **parallelism** (`tensor_model_parallel_size`, `pipeline_model_parallel_size`, `expert_model_parallel_size`), data paths, checkpoints, Primus Turbo flags, etc. | + +The sample sets `mock_data: true` and `train_data_path: null` so you can validate the stack without real corpora. + +### Mock data versus real data + +- **Mock data:** Set `mock_data: true` and leave `train_data_path` / `valid_data_path` empty (as in `llama2_7B-BF16-pretrain.yaml`). +- **Real data:** Set `mock_data: false` and populate Megatron-compatible data paths (and tokenizer assets) in `overrides`. Use paths visible inside your container mounts. + +### Multi-node training with Slurm + +```bash +./runner/primus-cli slurm srun -N 4 -p -- train pretrain \ + --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml +``` + +`runner/primus-cli-slurm-entry.sh` derives `MASTER_ADDR`, `NNODES`, and `NODE_RANK` from Slurm and forwards them into the container. Align `tensor_model_parallel_size`, `pipeline_model_parallel_size`, and `expert_model_parallel_size` with your cluster width and job size. + +--- + +## TorchTitan pretraining + +### Quick start + +```bash +./runner/primus-cli container -- train pretrain \ + --config examples/torchtitan/configs/MI300X/llama3.1_8B-BF16-pretrain.yaml +``` + +### Example configurations under `examples/torchtitan/configs/MI300X/` + +| File | +| --- | +| `deepseek_v3_16b-BF16-pretrain.yaml` | +| `deepseek_v3_16b-FP8-pretrain.yaml` | +| `deepseek_v3_236b-BF16-pretrain.yaml` | +| `deepseek_v3_236b-FP8-pretrain.yaml` | +| `deepseek_v3_671b-pretrain.yaml` | +| `llama3.1_405B-BF16-pretrain.yaml` | +| `llama3.1_405B-FP8-pretrain.yaml` | +| `llama3.1_70B-BF16-pretrain.yaml` | +| `llama3.1_70B-FP8-pretrain.yaml` | +| `llama3.1_8B-BF16-pretrain.yaml` | +| `llama3.1_8B-FP8-pretrain.yaml` | +| `llama4_17Bx128E-BF16-pretrain.yaml` | +| `llama4_17Bx128E-FP8-pretrain.yaml` | +| `llama4_17Bx16E-BF16-pretrain.yaml` | +| `llama4_17Bx16E-FP8-pretrain.yaml` | +| `qwen3_0.6B-pretrain.yaml` | +| `qwen3_1.7B-pretrain.yaml` | +| `qwen3_14B-pretrain.yaml` | +| `qwen3_32B-pretrain.yaml` | +| `qwen3_4B-pretrain.yaml` | +| `qwen3_8B-pretrain.yaml` | + +### Sample YAML file (`llama3.1_8B-BF16-pretrain.yaml`) explained + +Path: `examples/torchtitan/configs/MI300X/llama3.1_8B-BF16-pretrain.yaml` + +| Section | Role | +| --- | --- | +| `framework: torchtitan` | Selects the TorchTitan integration. | +| `config: pre_trainer.yaml` | Module preset under `primus/configs/modules/torchtitan/`. | +| `model: llama3.1_8B.yaml` | Model preset under `primus/configs/models/torchtitan/`. | +| `overrides.training`, `lr_scheduler`, `activation_checkpoint`, `primus_turbo` | Run-specific batching, steps, checkpointing, and Turbo options. | + +Some configurations omit an explicit `parallelism:` block; in that case the default values come from the **module and model presets** (`primus/configs/modules/torchtitan/pre_trainer.yaml` and the chosen model YAML). Other examples (for example DeepSeek and Qwen) set `parallelism:` inline with `tensor_parallel_degree`, `pipeline_parallel_degree`, `expert_parallel_degree`, etc. + +--- + +## MaxText (JAX) pretraining + +### Quick start + +```bash +./runner/primus-cli container -- train pretrain \ + --config examples/maxtext/configs/MI300X/llama2_7B-pretrain.yaml +``` + +### JAX-specific requirements + +Install JAX/MaxText dependencies from the repository root: + +```bash +pip install -r requirements-jax.txt +``` + +### Example configurations under `examples/maxtext/configs/MI300X/` + +| File | Key parallelism (`ici_*` intra-node, `dcn_*` inter-node) | +| --- | --- | +| `deepseek_v2_16B-pretrain.yaml` | `ici_fsdp_parallelism: 1`, `ici_data_parallelism: 1`, `dcn_fsdp_parallelism: 1`, `dcn_data_parallelism: -1` | +| `grok1-pretrain.yaml` | `ici_fsdp_parallelism: 1`, `ici_data_parallelism: 1`, `dcn_fsdp_parallelism: 1`, `dcn_data_parallelism: -1` | +| `llama2_70B-pretrain.yaml` | `ici_fsdp_parallelism: 8`, `ici_data_parallelism: 1`, `dcn_fsdp_parallelism: 1`, `dcn_data_parallelism: -1` | +| `llama2_7B-pretrain.yaml` | `ici_fsdp_parallelism: 8`, `ici_data_parallelism: 1`, `dcn_fsdp_parallelism: 1`, `dcn_data_parallelism: -1` | +| `llama3.3_70B-pretrain.yaml` | `ici_fsdp_parallelism: 8`, `ici_data_parallelism: 1`, `dcn_fsdp_parallelism: 1`, `dcn_data_parallelism: -1` | +| `llama3_70B-pretrain.yaml` | `ici_fsdp_parallelism: 8`, `ici_data_parallelism: 1`, `dcn_fsdp_parallelism: 1`, `dcn_data_parallelism: -1` | +| `llama3_8B-pretrain.yaml` | `ici_fsdp_parallelism: 8`, `ici_data_parallelism: 1`, `dcn_fsdp_parallelism: 1`, `dcn_data_parallelism: -1` | +| `mixtral_8x7B-pretrain.yaml` | `ici_fsdp_parallelism: 1`, `ici_data_parallelism: 1`, `dcn_fsdp_parallelism: 1`, `dcn_data_parallelism: -1` | +| `qwen3_14B-pretrain.yaml` | `ici_fsdp_parallelism: 8`, `ici_data_parallelism: 1`, `dcn_fsdp_parallelism: 1`, `dcn_data_parallelism: -1` | +| `qwen3_30B_A3B-pretrain.yaml` | `ici_fsdp_parallelism: 1`, `ici_data_parallelism: 1`, `dcn_fsdp_parallelism: 1`, `dcn_data_parallelism: -1` | + +The `llama2_7B-pretrain.yaml` example also sets `dataset_type: "synthetic"` and `hf_access_token: ${HF_TOKEN:""}` for gated Hugging Face assets when you switch to real data. + +--- + +## Common patterns + +### Testing with mock data + +Set `mock_data: true` (Megatron/TorchTitan) or synthetic dataset settings (MaxText) to validate the configurations and infrastructure without I/O-heavy datasets. + +### Real training data + +- Megatron: Configure `train_data_path` / `valid_data_path` and tokenizer assets in `overrides` once `mock_data` is false. +- For **all backends**, ensure host paths are mounted in **container** mode (`--volume` or `container.options.volume` in YAML). +- TorchTitan/MaxText: Follow backend-specific dataset fields in the `overrides` and presets. + +### Scaling from single-node to multi-node + +- Use **Slurm** mode for allocation; keep the **container** entry if you want the same image on every node. +- Set environment variables consistently (`NNODES`, `NODE_RANK`, `MASTER_ADDR`, `MASTER_PORT`, `GPUS_PER_NODE`); the Slurm entry script injects them when using `primus-cli slurm`. +- Increase values in the parallelism fields (Megatron TP/PP/EP; TorchTitan `parallelism`; MaxText `ici_*` / `dcn_*`) to match topology. + +### Hugging Face token for gated models + +Export `HF_TOKEN` on the host before launching **container** mode; `runner/.primus.yaml` lists `HF_TOKEN` under `container.options.env` so it can be forwarded into the container. MaxText configurations may reference `${HF_TOKEN:""}` directly. + +### hipBLASLt autotuning (three stages) + +Controlled with `PRIMUS_HIPBLASLT_TUNING_STAGE` (see `examples/README.md`): + +| Stage | Purpose | +| --- | --- | +| 1 | Dump GEMM shapes seen during training (reduce `train_iters` for faster collection). | +| 2 | Tune kernels from dumped shapes (offline tooling under `examples/offline_tune`). | +| 3 | Train using tuned kernel artifacts from `./output/tune_hipblaslt/...`. | + +Example (from in-repo docs): + +```bash +export PRIMUS_HIPBLASLT_TUNING=1 # master switch (required; tuning is skipped without it) +export PRIMUS_HIPBLASLT_TUNING_STAGE=1 +./runner/primus-cli direct -- train pretrain \ + --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml +``` + +--- + +## Supported models + +The tables above in the Megatron, TorchTitan, and MaxText sections are curated MI300X examples from the [Primus repository](https://github.com/AMD-AGI/Primus). Use `examples//configs/` in the repository as the authoritative inventory, as new presets and hardware-specific examples may be added there before this document is updated to reflect their additions. + +| Backend | Example region | Parallelism vocabulary | +| --- | --- | --- | +| Megatron-LM | `examples/megatron/configs/MI300X/` | `tensor_model_parallel_size`, `pipeline_model_parallel_size`, `expert_model_parallel_size` (and env-driven `${PRIMUS_TP:…}` variants). | +| TorchTitan | `examples/torchtitan/configs/MI300X/` | `parallelism.*` (e.g. `tensor_parallel_degree`, `pipeline_parallel_degree`, `expert_parallel_degree`, FSDP shard settings). | +| MaxText | `examples/maxtext/configs/MI300X/` | `ici_fsdp_parallelism`, `ici_data_parallelism`, `dcn_fsdp_parallelism`, `dcn_data_parallelism`. | + +For scripting patterns that predate `primus-cli`, the repository still documents `examples/run_local_pretrain.sh` and `examples/run_slurm_pretrain.sh` in `examples/README.md`; equivalent launches are shown above using `./runner/primus-cli`. + +--- + +## Related documentation + +- [CLI reference](cli-reference.md): launcher usage +- [Configuration system](configuration-system.md): YAML merge rules +- Backend parameter references: [Megatron parameters](../03-configuration-reference/megatron-parameters.md), [TorchTitan parameters](../03-configuration-reference/torchtitan-parameters.md), [MaxText parameters](../03-configuration-reference/maxtext-parameters.md) diff --git a/docs/02-user-guide/primus-tools.md b/docs/02-user-guide/primus-tools.md new file mode 100644 index 000000000..212a3fd73 --- /dev/null +++ b/docs/02-user-guide/primus-tools.md @@ -0,0 +1,29 @@ +# Primus tools + +A quick catalog of the tools that ship with Primus and the sibling projects +around it—command-line tools, the tuning agent, ecosystem projects, and +auxiliary utilities. Each row gives a short description and a how-to starting +point; follow a tool's link for the full reference. + +| Tool | Type | What it does | How to use | +|------|------|--------------|------------| +| [`train`](./pretraining.md) | CLI | Launch pretraining or post-training on any backend from a YAML configuration. | `primus-cli -- train pretrain --config ` | +| [`benchmark`](./benchmarking.md) | CLI | GEMM, RCCL, and attention microbenchmarks for hardware and stack validation. | `primus-cli direct -- benchmark gemm --M 4096 --N 4096 --K 4096` | +| [`preflight`](./preflight.md) | CLI | Host, GPU, and network health checks before long jobs. | `primus-cli slurm srun -N 4 -- preflight --host --gpu --network` | +| [`projection`](./projection.md) | CLI | Estimate per-GPU memory and throughput without occupying a full cluster. | `primus-cli direct -- projection both --config ` | +| [Tuning agent](./tuning-agent.md) | Agent | LLM-driven search for a near-optimal training configuration, scored by projection. | `python -m primus.agents.tuning_agent --workload --target-cluster ` | +| [Primus-LM](../01-getting-started/quickstart.md) | Ecosystem | The training framework in this repository (multi-backend, unified CLI). | See the [Quickstart](../01-getting-started/quickstart.md) | +| [Primus-Turbo](https://github.com/AMD-AGI/Primus-Turbo) | Ecosystem | High-performance ROCm operators (attention, GEMM, grouped GEMM, DeepEP, FP8/FP4). | Bundled in the `rocm/primus` image; enabled via configuration flags | +| [Primus-SaFE](https://github.com/AMD-AGI/Primus-SaFE) | Ecosystem | Kubernetes-native stability, scheduling, and fault-tolerance platform. | Deployed separately on Kubernetes (Helm) | +| [IRLens](../../tools/IRLens/README.md) | Auxiliary | Parse XLA HLO dumps into a communication-vs-compute execution skeleton. | See the [README](../../tools/IRLens/README.md) | +| [model_stats](../../tools/model_stats/README.md) | Auxiliary | Chart model dimensions from the config registry. | See the [README](../../tools/model_stats/README.md) | +| [Pipeline visualization](../../tools/visualization/pp_vis/README.md) | Auxiliary | Render pipeline-parallel schedules in a local web UI. | See the [README](../../tools/visualization/pp_vis/README.md) | +| [Auto benchmark](../../tools/auto_benchmark/Primus_Auto_Benchmark_README.md) | Auxiliary | Interactive Megatron/TorchTitan benchmark menu with metrics collection. | See the [README](../../tools/auto_benchmark/Primus_Auto_Benchmark_README.md) | + +--- + +## Related documentation + +- [CLI reference](./cli-reference.md)—full launcher grammar and subcommand options. +- [Tooling](../06-developer-guide/tooling.md)—developer-guide index of the `tools/` utilities. +- [Project overview](../01-getting-started/overview.md#primus-ecosystem)—how the Primus ecosystem layers fit together. diff --git a/docs/02-user-guide/projection.md b/docs/02-user-guide/projection.md new file mode 100644 index 000000000..af64941e5 --- /dev/null +++ b/docs/02-user-guide/projection.md @@ -0,0 +1,177 @@ +# Memory and performance projection + +Primus projection tools estimate **per-GPU memory** and **training throughput** for large-scale distributed jobs without requiring the full target cluster. Two modes are available: analytical **memory** projection and **performance** projection that combines profiling with simulation. + +**Implementation:** `primus/cli/subcommands/projection.py` + +| Mode | Command | Role | +|------|---------|------| +| **Memory** | `projection memory` | Estimates per-GPU memory (parameters, optimizer state, activations) using analytical formulas. | +| **Performance** | `projection performance` | Benchmarks on a single node (or sub-node), then projects training time to multi-node configurations. | +| **Both** | `projection both` | Runs a single benchmark and produces **both** the performance and (benchmark-anchored) memory projections from it. Recommended for cluster-sizing workflows. | + +**Core logic** + +- Memory: `primus/core/projection/memory_projection/` +- Performance: `primus/core/projection/performance_projection/` + +Related: [Benchmark suite](./benchmarking.md), [Preflight diagnostics](./preflight.md), [Megatron parameters](../03-configuration-reference/megatron-parameters.md). + +--- + +## Memory projection + +### Quick start + +```bash +export NNODES=1 +export HSA_NO_SCRATCH_RECLAIM=1 + +./runner/primus-cli direct --script primus/cli/main.py -- \ + projection memory \ + --config examples/megatron/configs/MI300X/deepseek_v2_lite-BF16-pretrain.yaml +``` + +Adjust `--config` to your experiment YAML. Memory estimation is analytical; the CLI still expects a normal Primus launch path (including distributed initialization where applicable). + +### What it estimates + +| Component | Meaning | +|-----------|---------| +| **Parameter memory** | Model weights assigned to this GPU (respecting parallelism). | +| **Optimizer memory** | Optimizer state (for example Adam moments), accounting for sharding across data-parallel groups. | +| **Activation memory** | Activations retained for the backward pass for a given microbatch and sequence length. | + +The tool walks a hierarchical profiler structure aligned with the model (embeddings, dense and MoE layers, output head, loss) and aggregates per-component contributions. + +### How to interpret results + +Console output includes per-component breakdowns and a summary such as parameter count, param+optimizer memory, activation memory for the configured batch size and sequence length, and a projected total. Use these to answer whether a configuration fits in HBM before you allocate large clusters. + +--- + +## Performance projection + +### Quick start + +Minimum required nodes (derived from parallelism): + +```bash +export NNODES=1 +export HSA_NO_SCRATCH_RECLAIM=1 + +./runner/primus-cli direct --script primus/cli/main.py -- \ + projection performance \ + --config examples/megatron/configs/MI300X/deepseek_v2_lite-BF16-pretrain.yaml +``` + +### How it works + +1. **Profile** layer-level behavior on **one node** (or a subset of GPUs with automatic scaling rules). +2. **Simulate** pipeline scheduling, data parallelism, and communication using analytical models. +3. **Project** iteration time and tokens/s to a **target** node count when you specify one. + +### Projecting to a specific node count + +```bash +./runner/primus-cli direct --script primus/cli/main.py -- \ + projection performance \ + --config examples/megatron/configs/MI300X/deepseek_v2_lite-BF16-pretrain.yaml \ + --target-nodes 4 +``` + +If `--target-nodes` is omitted, the tool defaults to the **minimum** number of nodes implied by your parallelism configuration (TP, PP, EP, CP, GPUs per node). + +### Parallelism overrides (environment) + +You can override parallelism for what-if analysis: + +```bash +export PRIMUS_TP=1 +export PRIMUS_PP=3 +export PRIMUS_EP=8 + +./runner/primus-cli direct --script primus/cli/main.py -- \ + projection performance \ + --config examples/megatron/configs/MI300X/deepseek_v2_lite-BF16-pretrain.yaml \ + --target-nodes 6 +``` + +--- + +## Command reference + +### Syntax + +```bash +primus-cli [global-options] [mode-args] -- projection {memory,performance,both} [options] +``` + +### Shared options (both modes) + +| Option | Description | +|--------|-------------| +| `--config` / `--exp` | Path to the Primus YAML configuration (**required**). | +| `--data_path` | Data directory (default `./data` when included on the parser). | +| `--backend_path` | Optional Megatron/TorchTitan import path appended to `PYTHONPATH`. | +| `--export_config` | Accepted by the shared pretrain parser, but the default core runtime does not currently write a resolved YAML file. | + +### Performance-only options + +| Option | Description | +|--------|-------------| +| `--target-nodes` | Target number of nodes for scaling projection. Defaults to the minimum nodes required by TP/PP/EP/CP and GPUs per node. | +| `--target-num-nodes` | Alias-style projection override for target node count. | +| `--target-ep-size` | Override `expert_model_parallel_size` for the projection target. | +| `--benchmark-gpus` | Use fewer than `GPUS_PER_NODE` GPUs for benchmarking; results are scaled analytically back to a full node. | +| `--hardware-config` | YAML file with hardware parameters for communication modeling. | +| `--profiling-mode` | `benchmark` (default, uses GPU), `simulate` (analytical / Origami GEMM + SDPA models, no GPU), or `both` (side-by-side). | +| `--gemm-backend` | GEMM simulation backend when profiling is simulated (`origami`). | +| `--gpu-arch` | Target architecture for simulation (for example `mi300x`, `gfx942`, `mi355x`, `gfx950`); can use `PRIMUS_GPU_ARCH`. | +| `--gpu-clock-mhz` | Override GPU clock in MHz for simulation; can use `PRIMUS_GPU_CLOCK_MHZ`. | +| `--pipeline-schedule-algorithm` | Pipeline simulation scheduler (`auto`, zero-bubble variants, or `all` for comparison). | +| `--enable-zero-bubble` | Enable zero-bubble pipeline scheduling for projection. | +| `--enable-deepep` | Enable DeepEP overlap modeling. | +| `--sync-free-stage` | Override Sync-Free MoE stage (`0` off; stages `1`-`3` enable additional modeling assumptions). | +| `--num-virtual-stages-per-pipeline-rank` | Override virtual pipeline stage count for projection. | +| `--micro-batch-size`, `--global-batch-size` | Override batch sizes for projection without editing the YAML. | + +--- + +## Assumptions and limitations + +### Assumptions (performance projection) + +1. **Data-parallel scaling**—Compute time scales with ideal weak-scaling assumptions versus data-parallel width. +2. **Communication model**—Uses simplified bandwidth and latency models (defaults such as efficiency factors may apply). +3. **Pipeline scheduling**—Bubble and overlap behavior is modeled with fixed splits; real frameworks may differ. +4. **Gradients and MoE**—Gradient all-reduce overlap and MoE all-to-all behavior follow the implemented model (for example overlap flags, EP scaling). + +### Limitations + +1. **Single-node benchmark accuracy**—Reduced PP/EP on the benchmark GPU count may not capture every production behavior. +2. **Contention**—Network contention between jobs is not modeled. +3. **Memory vs speed**—Activation recomputation reduces memory but adds compute; performance projection may not fully reflect that trade-off unless modeled. +4. **Heterogeneity**—Assumes homogeneous nodes; GPU frequency drift across nodes is not modeled. + +--- + +## Tips + +1. Run **`projection memory`** first to confirm a configuration is feasible in HBM before spending time on performance projection. +2. Always establish a **single-node** baseline before interpreting multi-node projections. +3. **Data-parallel scaling** is bounded by batching: if you run out of microbatches (`global_batch_size` / `micro_batch_size`), adding nodes may not increase throughput. +4. If the YAML **requires** multiple nodes (for example large PP), the performance path may automatically reduce parallelism for benchmarking and restore it analytically—read the console summary carefully. +5. **No GPU available:** use `--profiling-mode simulate` for CPU-side analytical timing. +6. **Validate models:** use `--profiling-mode both` to compare GPU benchmark timing with simulation on the same config. +7. For **MoE** models, activation memory from MoE layers often dominates; memory projection highlights when recomputation is worth considering. + +--- + +## Related documentation + +- [Benchmark suite](./benchmarking.md) +- [Preflight diagnostics](./preflight.md) +- [Post-training workflows](./posttraining.md) +- [Tuning agent](./tuning-agent.md) +- [Megatron parameters](../03-configuration-reference/megatron-parameters.md) diff --git a/docs/02-user-guide/training-recipes.md b/docs/02-user-guide/training-recipes.md new file mode 100644 index 000000000..dec02128a --- /dev/null +++ b/docs/02-user-guide/training-recipes.md @@ -0,0 +1,260 @@ +# Backend training recipes + +Task-oriented, copy-paste commands for launching pretraining runs with each Primus backend on AMD Instinct™ GPUs. + +This section is for users who already know what they want to run and need the specific command for a given model, precision, and GPU. To understand concepts related to the Primus workflow (how backends work, YAML structure and inheritance, parallelism vocabulary, the full per-backend configuration inventory, etc.), see **[Pretraining](pretraining.md)**. + +> **Authoritative full matrices.** AMD publishes per-model reproduction pages with verified images, commits, and tuned batch sizes for every supported model at the following locations—treat them as the source of truth for achieving the expected performance; this page only gives the canonical *pattern* plus a representative example per backend and links to other reference materials. +> +> - [Training with Primus + Megatron-LM](https://rocm.docs.amd.com/en/latest/how-to/rocm-for-ai/training/benchmark-docker/primus-megatron.html) +> - [Training with Primus + PyTorch (TorchTitan)](https://rocm.docs.amd.com/en/latest/how-to/rocm-for-ai/training/benchmark-docker/primus-pytorch.html) +> - [Training with Primus + JAX MaxText](https://rocm.docs.amd.com/en/latest/how-to/rocm-for-ai/training/benchmark-docker/jax-maxtext.html) + +--- + +## How recipes are structured + +Every recipe follows the same four-step pattern: + +1. **Pull and launch the AMD Docker image** (for a reproducible environment). +2. **Set the GPU-architecture environment** (performance environment variable settings differ by GPU). +3. **Pick the configuration YAML** for your GPU architecture under `examples//configs//` in the [Primus repository](https://github.com/AMD-AGI/Primus). +4. **Launch** with `runner/primus-cli` in `direct`, `container`, or `slurm` mode. + +### GPU-architecture config folders + +Configuration YAMLs are organized by GPU architecture. Always pick the folder that matches your hardware: + + +| Backend | `MI300X` | `MI325X` | `MI355X` / `MI350X` | +| ----------------------------------- | -------- | -------- | ------------------- | +| `examples/megatron/configs/` | yes | yes | yes | +| `examples/torchtitan/configs/` | yes | yes | yes | +| `examples/maxtext/configs/` | yes | — | yes | +| `examples/megatron_bridge/configs/` | yes | — | yes | + + +> MI350X uses the same configurations as MI355X because both are based on the gfx950 architecture. If a configuration for your model is not available in the architecture-specific folder, use the closest match from the same generation as a starting point. + +### GPU-architecture environment variables + +MI300X and MI325X benefit from the following performance settings; MI355X/MI350X do **not** need them: + +```bash +# MI300X / MI325X only -- improves performance +export HSA_NO_SCRATCH_RECLAIM=1 +export PRIMUS_TURBO_ATTN_V3_ATOMIC_FP32=1 +export NVTE_CK_IS_V3_ATOMIC_FP32=1 +``` + +### Choosing the Docker image + +For **container** and **Slurm** modes (direct mode runs in whatever environment you launched it from), the default image is `rocm/primus:v26.4` (`runner/.primus.yaml`). For reproducing published benchmarks, use the AMD-published tag for your release (the AMD pages under **Authoritative full matrices** above list the most current tag). JAX MaxText has its own separate image family of `rocm/jax-training:maxtext-...`. + +Image is picked in the priority order of `DOCKER_IMAGE` environment variable > `--image` CLI argument > config file. See [Selecting the container image](../01-getting-started/quickstart.md#selecting-the-container-image) for a full explanation, and [Configuration system](configuration-system.md) for configuration loading. + +--- + +## Shared setup for all backends + +These apply across all backends. Set them up before running the recipes below. + +### Hugging Face token (for gated models or real data) + +```bash +export HF_TOKEN= +``` + +`runner/.primus.yaml` forwards `HF_TOKEN` into the container automatically. MaxText configurations might also read `${HF_TOKEN:""}` directly. + +### Mock vs. real data + +- **Mock/synthetic data** (default for most examples): validates the stack without datasets. Megatron and TorchTitan set `mock_data: true`; MaxText sets `dataset_type: "synthetic"`. +- **Real data:** set `mock_data: false` and point `train_data_path` (for Megatron) or the backend's dataset fields at paths visible *inside* your container mounts. + +### Multi-node networking checklist + +The `primus-cli` launcher sets sensible `NCCL_`* defaults, but auto-detection can pick the wrong device on multi-NIC nodes. Before multi-node, confirm and export if needed: + +```bash +export NCCL_IB_HCA= # from `ibv_devices` +export NCCL_SOCKET_IFNAME= # from `ip a` +export GLOO_SOCKET_IFNAME= +export NCCL_IB_GID_INDEX=3 # 3 for RoCE (1 for AMD AINIC) +``` + +For AMD AINIC clusters also set `USING_AINIC=1`, `NCCL_PXN_DISABLE=0`, `NCCL_IB_GID_INDEX=1`. See [Multi-Node Networking](../04-technical-guides/multi-node-networking.md) for the full reference. + +--- + +## Megatron-LM + +**Image:** `rocm/primus`  |  **Configurations:** `examples/megatron/configs//`  |  **Precisions:** BF16, FP8 + +### 1. Launch the container + +```bash +docker pull rocm/primus:v26.4 +docker run -it \ + --device /dev/dri --device /dev/kfd --device /dev/infiniband \ + --network host --ipc host \ + --group-add video --cap-add SYS_PTRACE \ + --security-opt seccomp=unconfined --privileged \ + -v $HOME:$HOME --shm-size 128G \ + --name primus_training_env \ + rocm/primus:v26.4 +``` + +Access the container later with `docker start primus_training_env && docker exec -it primus_training_env bash`. + +### 2. Run pretraining (direct mode, inside the container) + +Pretrain Llama 3.1 8B BF16 on **MI355X / MI350X**: + +```bash +./runner/primus-cli direct \ + --log_file /tmp/primus_llama3.1_8B.log \ + -- train pretrain \ + --config examples/megatron/configs/MI355X/llama3.1_8B-BF16-pretrain.yaml +``` + +Pretrain the same model on **MI300X / MI325X** (add the performance environment variables): + +```bash +export HSA_NO_SCRATCH_RECLAIM=1 +export PRIMUS_TURBO_ATTN_V3_ATOMIC_FP32=1 +export NVTE_CK_IS_V3_ATOMIC_FP32=1 + +./runner/primus-cli direct \ + --log_file /tmp/primus_llama3.1_8B.log \ + -- train pretrain \ + --config examples/megatron/configs/MI300X/llama3.1_8B-BF16-pretrain.yaml +``` + +Switch model or precision by changing the config filename (e.g. `llama3.1_70B-FP8-pretrain.yaml`, `mixtral_8x7B_v0.1-BF16-pretrain.yaml`). The full configuration inventory is the repository's `examples/megatron/configs//` directory. See the parallelism table in [Pretraining](pretraining.md#example-configurations-under-examplesmegatronconfigsmi300x). + +**Model-specific notes:** + +- **Zebra-Llama** (hybrid Mamba+MLA) pretrain presets ship at `examples/megatron/configs//zebra_llama_{1B,3B,8B}-pretrain.yaml` and run via the standard core runtime; Megatron Bridge SFT variants live under `examples/megatron_bridge/configs//`. +- **MoE models** (DeepSeek-V2-Lite, Mixtral) might need extra grouped-GEMM or router flags; [Training with Primus + Megatron-LM](https://rocm.docs.amd.com/en/latest/how-to/rocm-for-ai/training/benchmark-docker/primus-megatron.html) lists the exact flags per model. + +### 3. Multi-node (Slurm mode) + +```bash +./runner/primus-cli slurm srun -N 8 -p -- train pretrain \ + --config examples/megatron/configs/MI300X/llama3.1_8B-FP8-pretrain.yaml \ + --micro_batch_size 4 --global_batch_size 1024 +``` + +Scale batch size with node count and align `tensor_model_parallel_size`, `pipeline_model_parallel_size`, and `expert_model_parallel_size` to your topology. See the [multi-node networking checklist](#multi-node-networking-checklist) above. + +--- + +## TorchTitan (PyTorch) + +**Image:** `rocm/primus`  |  **Configurations:** `examples/torchtitan/configs//`  |  **Precisions:** BF16, FP8 + +Use the same `rocm/primus` container as Megatron (step 1 above). TorchTitan parameters use a dotted namespace (e.g. `--training.local_batch_size`). + +### Run pretraining (direct mode) + +Pretrain Llama 3.1 8B BF16 on **MI355X / MI350X**: + +```bash +./runner/primus-cli direct \ + --log_file /tmp/primus_llama3.1_8B.log \ + -- train pretrain \ + --config examples/torchtitan/configs/MI355X/llama3.1_8B-BF16-pretrain.yaml +``` + +On **MI300X / MI325X**, export the performance environment variables first (see above) and use the `MI300X` config path. + +### Multi-node (Slurm mode) + +```bash +./runner/primus-cli slurm srun -N 4 -- train pretrain \ + --config examples/torchtitan/configs/MI355X/llama3.1_70B-FP8-pretrain.yaml \ + --training.local_batch_size 6 \ + --training.global_batch_size 192 \ + --training.mock_data True +``` + +Available models include Llama 3.1 (8B/70B/405B), Llama 4, DeepSeek V3, and Qwen 3. See the `examples/torchtitan/configs//` directory in the repository. + +--- + +## JAX MaxText + +**Image:** `rocm/jax-training:maxtext-...` (separate family from the other backends)  |  **Configurations:** `examples/maxtext/configs//` + +MaxText uses a different Docker image than Megatron and TorchTitan, and it is **not** the default image pointed to in `runner/.primus.yaml`. In container or Slurm mode, you must point Primus at your MaxText image explicitly. + +### 1. Launch the container + +```bash +docker pull rocm/jax-training:maxtext-v26.4-jax0.9.1-te2.12.0 +docker run -it \ + --device /dev/dri --device /dev/kfd \ + --network host --ipc host \ + --group-add video --cap-add SYS_PTRACE \ + --security-opt seccomp=unconfined --privileged \ + -v $HOME:$HOME -v $HOME/.ssh:/root/.ssh \ + --shm-size 64G \ + --name training_env \ + rocm/jax-training:maxtext-v26.4-jax0.9.1-te2.12.0 +``` + +If you run Primus directly on the host instead of inside the prebuilt Docker image, install the JAX dependencies first by `pip install -r requirements-jax.txt`. + +### 2. Run pretraining + +Direct mode (inside the container)—pretraining Llama 3 8B on **MI355X**: + +```bash +./runner/primus-cli direct \ + -- train pretrain \ + --config examples/maxtext/configs/MI355X/llama3_8B-pretrain.yaml +``` + +Container mode—passing the MaxText image with `--image`: + +```bash +./runner/primus-cli container --image rocm/jax-training:maxtext-v26.4-jax0.9.1-te2.12.0 \ + -- train pretrain \ + --config examples/maxtext/configs/MI355X/llama3_8B-pretrain.yaml +``` + +Slurm mode—supplying the image (and any environment variables) via a config file: + +```bash +./runner/primus-cli --config my_maxtext_config.yaml slurm srun -N 8 \ + -- train pretrain \ + --config examples/maxtext/configs/MI300X/llama3_8B-pretrain.yaml +``` + +MaxText parallelism is set with `ici_*` (intra-node) and `dcn_*` (inter-node) fields—see the [MaxText config table](pretraining.md#maxtext-jax-pretraining) and [MaxText parameters](../03-configuration-reference/maxtext-parameters.md). + +--- + +## Megatron Bridge (post-training) + +Megatron Bridge configurations are under `examples/megatron_bridge/configs//` in the repository and are primarily **SFT and LoRA post-training** recipes (e.g. `qwen3_32b_sft_posttrain.yaml`, `llama31_70b_lora_posttrain.yaml`). Launch with `train posttrain`: + +```bash +./runner/primus-cli direct \ + --log_file /tmp/primus_qwen3_32b_sft.log \ + -- train posttrain \ + --config examples/megatron_bridge/configs/MI355X/qwen3_32b_sft_posttrain.yaml +``` + +See [Post-training](posttraining.md) for the full SFT/LoRA workflow. + +--- + +## Related documentation + +- [Pretraining](pretraining.md): backend concepts, configuration walkthroughs, parallelism vocabulary, full configuration inventories. +- [Post-training](posttraining.md): SFT and LoRA via Megatron Bridge. +- [CLI reference](cli-reference.md): `direct` / `container` / `slurm` modes and flags. +- [Configuration system](configuration-system.md): YAML inheritance, overrides, image/env precedence. +- [Performance tuning](../04-technical-guides/performance-tuning.md): HipBLASLt autotuning, Primus-Turbo, FP8, MoE. diff --git a/docs/tuning_agent.md b/docs/02-user-guide/tuning-agent.md similarity index 89% rename from docs/tuning_agent.md rename to docs/02-user-guide/tuning-agent.md index e346de1d2..08baa04d2 100644 --- a/docs/tuning_agent.md +++ b/docs/02-user-guide/tuning-agent.md @@ -1,18 +1,18 @@ -# Tuning Agent +# Tuning agent The **Tuning Agent** is an LLM-driven search for a near-optimal Primus -**training configuration** — the full parallelism strategy *plus* the coupled +**training configuration**—the full parallelism strategy *plus* the coupled batching, pipeline-schedule, memory, MoE-communication, and precision knobs — on a target GPU cluster, **without running the workload at scale**. It drives the [Primus Projection](./projection.md) tool as an evaluation oracle. Projection -provides two estimates — **memory** and **performance** — each of which runs +provides two estimates—**memory** and **performance**—each of which runs **benchmark-anchored by default** (measuring what fits on a sub-node run and scaling the rest analytically) with a fully analytical **no-GPU `simulate`** fallback. The agent returns the configuration that maximizes `tokens/s/GPU` subject to a per-GPU memory safety margin. See [Knobs Searched](#knobs-searched) for the full set of levers it tunes. -- **Package**: [`primus/agents/tuning_agent/`](../primus/agents/tuning_agent/) +- **Package**: [`primus/agents/tuning_agent/`](../../primus/agents/tuning_agent/) - **Entry point**: `python -m primus.agents.tuning_agent` This document is both the **user/operator guide** (installation, configuration, @@ -22,36 +22,36 @@ features) for the agent. --- -## Table of Contents +## Table of contents -1. [Why a Tuning Agent](#why-a-tuning-agent) -2. [How It Works](#how-it-works) -3. [Knobs Searched](#knobs-searched) +1. [Why a tuning agent](#why-a-tuning-agent) +2. [How it works](#how-it-works) +3. [Knobs searched](#knobs-searched) 4. [Installation](#installation) -5. [LLM Setup](#llm-setup) +5. [LLM setup](#llm-setup) 6. [Quickstart](#quickstart) -7. [Execution Modes](#execution-modes) -8. [CLI Reference](#cli-reference) -9. [Target-Cluster YAML](#target-cluster-yaml) -10. [The Search Loop](#the-search-loop) -11. [Evaluator and Projection Modes](#evaluator-and-projection-modes) -12. [Output Artefacts](#output-artefacts) -13. [Worked Example](#worked-example) +7. [Execution modes](#execution-modes) +8. [CLI reference](#cli-reference) +9. [Target-cluster YAML](#target-cluster-yaml) +10. [The search loop](#the-search-loop) +11. [Evaluator and projection modes](#evaluator-and-projection-modes) +12. [Output artefacts](#output-artefacts) +13. [Worked example](#worked-example) 14. [Troubleshooting](#troubleshooting) 15. [Limitations](#limitations) -16. [Design Notes & Future Features](#design-notes--future-features) +16. [Design notes and future features](#design-notes-and-future-features) --- -## Why a Tuning Agent +## Why a tuning agent Choosing a training configuration for a large training (or inference) workload is a combinatorial problem. The configuration is the joint choice of the parallelism dimensions: -- **Data Parallel (DP)** — derived from world size and the other axes, +- **Data Parallel (DP)**—derived from world size and the other axes, - **Tensor Parallel (TP)**, -- **Expert Parallel (EP)** — for MoE models, +- **Expert Parallel (EP)**—for MoE models, - **Context Parallel (CP)**, - **Pipeline Parallel (PP)**, with virtual pipeline (**VPP**) and the **pipeline schedule** (1F1B / interleaved / zero-bubble / ZBV-\* / @@ -62,7 +62,7 @@ dimensions translate into in-flight work and memory pressure: global batch size (**GBS**), micro batch size (**MBS**), activation recomputation (`recompute_granularity`, `recompute_num_layers`), the overlap flags (`overlap_grad_reduce`, `overlap_param_gather`), and a set of higher-impact -levers — FP8 precision, MoE DeepEP / sync-free communication, fused +levers—FP8 precision, MoE DeepEP / sync-free communication, fused cross-entropy, and optimizer-state sharding (distributed optimizer / FSDP2). The full set is enumerated in [Knobs Searched](#knobs-searched). @@ -77,7 +77,7 @@ legal configuration within a user-specified budget. --- -## How It Works +## How it works ``` ┌────────────────────────────────────────────────────────────────────────┐ @@ -108,7 +108,7 @@ recompute for MBS, whether CP helps a given MoE shape). --- -## Knobs Searched +## Knobs searched The agent sweeps far more than the five parallelism dimensions. Its trial configuration (`TrialConfig` in `legality.py`) carries the full set of knobs @@ -117,7 +117,7 @@ agent leaves it unset) or **overridden for a trial** and translated into the corresponding `projection` flags by the evaluator. Each is legality-checked in code before it ever reaches the projection tool. -### Parallelism & batching +### Parallelism and batching | Knob | Legal values | What it controls | |------|--------------|------------------| @@ -143,11 +143,11 @@ code before it ever reaches the projection tool. |------|--------------|------------------| | `recompute_granularity` | `none` / `selective` / `full` | Activation recomputation strategy | | `recompute_num_layers` | int (≤ layers per VPP stage) | Layers recomputed per stage under `full` | -| `cross_entropy_loss_fusion` | `true` / `false` / inherit | Fused cross-entropy — large-vocab memory + compute win | +| `cross_entropy_loss_fusion` | `true` / `false` / inherit | Fused cross-entropy—large-vocab memory + compute win | | `use_distributed_optimizer` | `true` / `false` / inherit | ZeRO-1 optimizer-state sharding across DP | | `use_torch_fsdp2` | `true` / `false` / inherit | FSDP2 sharding (mutually exclusive with `use_distributed_optimizer`) | -### MoE communication — *MoE only, high impact* +### MoE communication—*MoE only, high impact* | Knob | Legal values | What it controls | |------|--------------|------------------| @@ -155,11 +155,11 @@ code before it ever reaches the projection tool. | `sync_free_stage` | `0` / `1` / `2` / `3` | Sync-free MoE pipelining; stage ≥ 2 auto-enables DeepEP | | `target_ep_size` | positive int / inherit | EP override used for All-to-All modeling | -### Precision — *high impact* +### Precision—*high impact* | Knob | Legal values | What it controls | |------|--------------|------------------| -| `fp8` | `none` / `hybrid` (also `e4m3`, `delayed`) | FP8 on linear layers — roughly 2× compute on GEMMs | +| `fp8` | `none` / `hybrid` (also `e4m3`, `delayed`) | FP8 on linear layers—roughly 2× compute on GEMMs | ### Coupling rules enforced in code @@ -206,10 +206,10 @@ Origami) are only needed by the evaluator paths you actually use: --- -## LLM Setup +## LLM setup The agent uses [DSPy](https://dspy.ai), which routes LLM calls through -[LiteLLM](https://docs.litellm.ai/docs/providers) internally — **no separate +[LiteLLM](https://docs.litellm.ai/docs/providers) internally—**no separate proxy process is required**. Set credentials for whichever provider you use: ```bash @@ -266,7 +266,7 @@ python -m primus.agents.tuning_agent \ --- -## Execution Modes +## Execution modes The agent has two orthogonal mode switches: **what the evaluator does** (`--mode`) and **whether the LLM stage runs** (`--seed-only` / `--agent-only`). @@ -293,7 +293,7 @@ The agent has two orthogonal mode switches: **what the evaluator does** --- -## CLI Reference +## CLI reference ```bash python -m primus.agents.tuning_agent \ @@ -322,15 +322,15 @@ python -m primus.agents.tuning_agent \ --- -## Target-Cluster YAML +## Target-cluster YAML A thin wrapper around the existing Primus `hardware_config` convention, so no new networking format has to be invented; topology, bandwidths, and latencies are consumed by the analytical communication model (see -[`projection.md` → Communication Modeling](./projection.md#communication-modeling)). +[`projection.md` → Assumptions (performance projection)](./projection.md#assumptions-performance-projection)). A complete example ships at -[`examples/agents/tuning_agent/target_cluster_mi355x_4nodes.yaml`](../examples/agents/tuning_agent/target_cluster_mi355x_4nodes.yaml): +[`examples/agents/tuning_agent/target_cluster_mi355x_4nodes.yaml`](../../examples/agents/tuning_agent/target_cluster_mi355x_4nodes.yaml): ```yaml target_cluster: @@ -396,7 +396,7 @@ agent: --- -## The Search Loop +## The search loop 1. **Resolve the workload.** Load the workload YAML, follow `modules.pre_trainer.model` into `primus/configs/models/megatron/.yaml`, @@ -437,7 +437,7 @@ agent: --- -## Evaluator and Projection Modes +## Evaluator and projection modes The evaluator wraps the Primus Projection CLI behind a uniform interface, so the agent does not need to know which mode produced a number: @@ -463,21 +463,21 @@ pre-filter to reject infeasible configs before paying for a performance call, then `simulate` (or `benchmark` for promising candidates when a GPU is available). The tool belt exposed to the LLM mirrors this: -- `evaluate_memory_only(config_json)` — cheap pre-filter -- `evaluate_simulate(config_json)` — primary scoring path -- `evaluate_with_benchmark(config_json)` — only if `has_gpu: true` +- `evaluate_memory_only(config_json)`—cheap pre-filter +- `evaluate_simulate(config_json)`—primary scoring path +- `evaluate_with_benchmark(config_json)`—only if `has_gpu: true` - `get_history`, `get_best`, `get_legal_axes`, `get_architecture`, `get_cluster`, `get_budget_status` - `note_to_scratchpad`, `read_scratchpad` -- `query_llm(prompt, system?)` — one-shot "LLM-inside-LLM" consultation +- `query_llm(prompt, system?)`—one-shot "LLM-inside-LLM" consultation -For the underlying projection math — memory components, the simulate vs. +For the underlying projection math—memory components, the simulate vs. benchmark trade-off, and the benchmark-based memory projection the agent uses -for OOM-accurate feasibility — see [`projection.md`](./projection.md). +for OOM-accurate feasibility—see [`projection.md`](./projection.md). --- -## Output Artefacts +## Output artefacts Everything lands in `--out-dir`: @@ -509,7 +509,7 @@ The run also prints the best configuration and ready-to-paste exports: --- -## Worked Example +## Worked example Search for the best Mixtral 8×22B configuration on a 4-node MI355X pod, with a single idle 8-GPU node available for benchmarking: @@ -566,17 +566,17 @@ These are honest caveats, not future features: `memory_safety_margin` compensates conservatively; the benchmark-based memory path (see `projection.md`) closes most of this gap. 3. **Search-space explosion** with all axes on. The agent mitigates with an - impact-ordered deterministic seed plan — high-leverage levers first + impact-ordered deterministic seed plan—high-leverage levers first (recompute, MoE DeepEP / sync-free, FP8, then schedule and VPP/MBS neighbors), with the broad TP×PP×EP×CP grid evaluated last and capped by - `--seed-budget` — so the LLM starts from an informed incumbent and spends + `--seed-budget`—so the LLM starts from an informed incumbent and spends its budget on polish. 4. **Cluster-description lossiness**: averaged bandwidth/latency cannot capture contention or per-rail asymmetry. --- -## Design Notes & Future Features +## Design notes and future features > This section captures the design rationale and the paper-ready problem > statement, plus the list of features deliberately deferred from v1. @@ -584,8 +584,8 @@ These are honest caveats, not future features: ### Problem statement (paper-ready) We address the problem of **automatically selecting a near-optimal -training configuration — the parallelism strategy plus the coupled batching, -schedule, memory, MoE-communication, and precision knobs — for a large-scale +training configuration—the parallelism strategy plus the coupled batching, +schedule, memory, MoE-communication, and precision knobs—for a large-scale distributed training workload on a target GPU cluster, without executing the workload at scale**. The configuration space is combinatorial: for each axis only a small set of values @@ -608,7 +608,7 @@ We formulate the search as **LLM-as-policy over a hybrid analytical / benchmark-driven evaluator**. The evaluator is the Primus Projection tool, which provides three calls of increasing fidelity and cost: (i) a **memory projection** that runs either analytically (no GPU) or, by default, -benchmark-anchored — measuring the real per-rank peak on a sub-node run and +benchmark-anchored—measuring the real per-rank peak on a sub-node run and extrapolating it for an OOM-accurate estimate; (ii) a fully analytical **performance projection** built on the Origami GEMM model and an SDPA simulator; and (iii) a **hybrid benchmark** that measures per-layer compute on @@ -622,7 +622,7 @@ to a configurable per-GPU memory safety margin. The contribution is a configuration-search methodology that exploits an LLM's ability to reason over architectural priors (topk dominance of MoE activations, the impact of MQA on attention activation, the inter-node/intra-node boundary -for All-to-All) to direct an analytical oracle — replacing exhaustive sweeps on +for All-to-All) to direct an analytical oracle—replacing exhaustive sweeps on real hardware with a small number of informed analytical evaluations. The system runs either entirely on a CPU-only host (simulate backend only) or in a mixed mode where a small number of real-hardware benchmark runs calibrate the @@ -640,7 +640,7 @@ analytical predictions. 4. Agent search over **all** parallelism and coupled axes (TP, PP, EP, CP, MBS, GBS, VPP, pipeline schedule, recompute, overlap flags) plus the higher-impact levers (FP8, MoE DeepEP / sync-free, fused cross-entropy, - distributed-optimizer / FSDP2) — restricted by per-architecture legality. + distributed-optimizer / FSDP2)—restricted by per-architecture legality. See [Knobs Searched](#knobs-searched) for the complete list. 5. Two evaluator paths: a **no-GPU path** (`projection memory --memory-mode simulate` + `projection performance --profiling-mode simulate`), always @@ -657,60 +657,60 @@ analytical predictions. These are recorded so they are not lost; they are deliberately left out to keep the agent small. -- **F1. Multi-objective Pareto** — replace the scalar `tokens/s/GPU` objective +- **F1. Multi-objective Pareto**—replace the scalar `tokens/s/GPU` objective with a Pareto frontier over (throughput, MFU, memory headroom, projected $/token, projected energy/token). -- **F2. Online cluster-spec retrieval** — pull the cluster description from an +- **F2. Online cluster-spec retrieval**—pull the cluster description from an internal registry or known-archs catalog (MI300X / MI325X / MI355X reference pods) and fall back to user overrides; optionally infer topology from a small DCGM / ROCm-SMI dump. -- **F3. Persistent memory / configuration cache** — cache +- **F3. Persistent memory / configuration cache**—cache `(model_signature, cluster_signature) → best_known_configs` across runs; invalidate when ROCm / hipBLASLt / framework versions change. -- **F4. Agent-proposed scale-downs and microbenchmarks** — let the agent +- **F4. Agent-proposed scale-downs and microbenchmarks**—let the agent *propose* reduced-model proxies and targeted microbenchmarks to reduce the uncertainty of its current top-k (reusing the `moe_proxy_single_node.yaml` pattern). -- **F5. Telemetry plug-ins (rocprofiler / TraceLens / Magpie)** — after a +- **F5. Telemetry plug-ins (rocprofiler / TraceLens / Magpie)**—after a benchmark run, optionally extract per-kernel time, GEMM efficiency, A2A bytes, NIC utilization to calibrate the analytical models and explain underperformance back to the agent. Exposed as a `SKILL.md`-described plug-in. References: [TraceLens](https://github.com/AMD-AGI/TraceLens-internal), [Magpie](https://github.com/AMD-AGI/Magpie). -- **F6. Calibration learning** — under `--profiling-mode both`, record +- **F6. Calibration learning**—under `--profiling-mode both`, record per-(model, arch, dim) residuals between simulate and benchmark, fit a small correction model, and report a confidence band on subsequent simulate runs. -- **F7. Robustness / sensitivity report** — for the winning config, sweep ±1 +- **F7. Robustness / sensitivity report**—for the winning config, sweep ±1 step on each axis and report whether the optimum is a sharp peak or a broad basin (cheap; only `simulate` calls). -- **F8. Cross-axis priors as DSPy modules** — a library of tunable "rules of +- **F8. Cross-axis priors as DSPy modules**—a library of tunable "rules of thumb" that DSPy's optimizer can refine over time using the trial logs. -- **F9. Sub-agent "test-proposer"** — delegate targeted experiments (e.g. a +- **F9. Sub-agent "test-proposer"**—delegate targeted experiments (e.g. a 2-layer scale-down forward+backward microbenchmark, or a stand-alone A2A probe at the proposed EP × hidden_size × topk), profiling the *test* with explicit synchronisation rather than the sandbox. A `run_proposed_experiment(plan, code)` tool can be added to `tools.py` without restructuring the loop. -- **F10. Sub-LLM expert router** — extend the existing `query_llm` tool into a +- **F10. Sub-LLM expert router**—extend the existing `query_llm` tool into a *named expert* router (`query_llm(expert='moe', …)`). ### Known design holes -1. **Simulator-vs-reality gap** — see Limitations above; in no-GPU mode the +1. **Simulator-vs-reality gap**—see Limitations above; in no-GPU mode the agent reports a confidence caveat. -2. **Memory-projection blind spots** — A2A buffers, allocator fragmentation, +2. **Memory-projection blind spots**—A2A buffers, allocator fragmentation, and comm scratch are not modeled analytically; the benchmark-based memory projection closes most of this gap by anchoring on a measured peak. -3. **Search-space explosion** — mitigated by an impact-ordered deterministic +3. **Search-space explosion**—mitigated by an impact-ordered deterministic seed plan (high-leverage levers first, broad parallelism grid last) plus LLM-guided search from the seed incumbent. -4. **Cluster-description lossiness** — averaged bandwidth/latency cannot capture +4. **Cluster-description lossiness**—averaged bandwidth/latency cannot capture contention or per-rail asymmetry. --- -## Related Documentation +## Related documentation -- [Projection](./projection.md) — memory + performance projection internals, +- [Projection](./projection.md)—memory + performance projection internals, including the benchmark-based memory projection the agent relies on. -- [Tuning Agent package README](../primus/agents/tuning_agent/README.md) — +- [Tuning Agent package README](../../primus/agents/tuning_agent/README.md) — quickstart reference inside the source tree. diff --git a/docs/03-configuration-reference/README.md b/docs/03-configuration-reference/README.md new file mode 100644 index 000000000..487d6e526 --- /dev/null +++ b/docs/03-configuration-reference/README.md @@ -0,0 +1,13 @@ +# Configuration reference + +Parameter references for Primus presets, backend-facing keys, and commonly used environment variables. + +- [Megatron parameters](megatron-parameters.md): Megatron-LM backend YAML parameters and Primus overrides +- [TorchTitan parameters](torchtitan-parameters.md): Primus TorchTitan preset keys and common JobConfig fields +- [MaxText parameters](maxtext-parameters.md): Primus MaxText overlay defaults and common fields +- [Megatron Bridge parameters](megatron-bridge-parameters.md): Megatron Bridge recipe, SFT, and pretraining fields surfaced through Primus +- [Environment variables](environment-variables.md): practical reference for commonly encountered environment variables + +--- + +[← Documentation home](../README.md) diff --git a/docs/03-configuration-reference/environment-variables.md b/docs/03-configuration-reference/environment-variables.md new file mode 100644 index 000000000..61aca7f1b --- /dev/null +++ b/docs/03-configuration-reference/environment-variables.md @@ -0,0 +1,239 @@ +# Environment variables reference + +This document catalogs the main environment variables you might encounter when running Primus on AMD GPUs: distributed launchers, Primus runners and CLI, YAML substitution, libraries (NCCL/RCCL, ROCm, PyTorch, JAX), and optional integrations (Hugging Face, WandB, MLflow). It is a practical reference, not a complete list of every variable accepted by upstream libraries. + +**Legend** + +- **Required**: Must be set for the stated workflow; otherwise the job fails or mis-ranks. +- **Optional**: Has a safe default or is only needed for specific features. +- **Set by**: Typical source (launcher, `runner/helpers/envs/*.sh`, user shell, container host). +- **Used in**: Representative Primus paths; many variables are also read by NVIDIA NCCL, AMD RCCL, PyTorch, or JAX without Primus wrapping them. + +--- + +## 1. PyTorch distributed + +Set by `torchrun`, Slurm launchers, or `runner/primus-cli-direct.sh` / `runner/primus-cli-slurm-entry.sh`. Consumed by PyTorch distributed, RCCL, and Primus helpers. + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `MASTER_ADDR` | `localhost` (direct / `base_env.sh`) | User, Slurm entry (`primus-cli-slurm-entry.sh`), or validation fallback (`runner/lib/validation.sh`) | `primus/pretrain.py`, `primus/core/base_module.py`, `primus/core/utils/env.py`, `primus/tools/preflight/network/network_probe.py`, PyTorch rendezvous | Rendezvous hostname or IP for process group initialization. **Required** for multi-node if not using Slurm auto-detection. | +| `MASTER_PORT` | `1234` (direct), `29500` in some Python defaults | Config / CLI / user | Same as `MASTER_ADDR`; `validation.sh` enforces 1024–65535 | TCP port for the store backing `torch.distributed`. | +| `RANK` | `0` if unset in helpers | `torchrun` | `primus/tools/utils.py`, `primus/tools/preflight/global_vars.py`, projection and profiler code | Global rank index. | +| `WORLD_SIZE` | `1` | `torchrun` | Preflight, projection, `primus/core/base_module.py` | Total number of processes. | +| `LOCAL_RANK` | `0` | `torchrun` | `primus/core/base_module.py`, GPU selection in benchmarks and trainers | GPU index on this node. | +| `LOCAL_WORLD_SIZE` | `1` (Python) / `8` in benchmarks default | `torchrun` | `primus/tools/preflight/*.py`, `strided_allgather_bench.py` | Processes (GPUs) per node. | +| `NODE_RANK` | `0` | `primus-cli-direct` / `primus-cli-slurm-entry.sh` | `primus/pretrain.py`, logging in `runner/lib/common.sh` | Zero-based node index in multi-node jobs. | +| `NNODES` | `1` | Direct config (`runner/.primus.yaml`), `primus-cli-slurm-entry.sh` | `primus/pretrain.py`, `primus/core/projection/training_config.py` | Number of nodes in the job. | +| `GPUS_PER_NODE` | `8` | `runner/.primus.yaml` direct section, `primus-cli-slurm-entry.sh`, `validation.sh` | `primus/core/projection/module_profilers/*.py`, training config helpers | GPUs per node for world-size math and binding. | + +--- + +## 2. Primus core + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `PRIMUS_PATCHES` | `""` / `"all"` | User | `primus/core/patches/patch_runner.py` | `"all"` or empty enables all patches; `"none"` disables; comma list enables subset. | +| `PRIMUS_LOG_LEVEL` | `INFO` | User; debug paths in `runner/primus-cli-*.sh` set `DEBUG` | `runner/lib/common.sh` | Log verbosity: `DEBUG`, `INFO`, `WARN`, `ERROR`. | +| `PRIMUS_LOG_TIMESTAMP` | `1` | User | `runner/lib/common.sh` | `1` prefixes logs with timestamps; `0` disables. | +| `PRIMUS_LOG_COLOR` | `1` (auto-off if not a TTY) | User; tests may set `0` | `runner/lib/common.sh` | ANSI colors in runner logs. | +| `PRIMUS_DEBUG` | `0` | User | `runner/helpers/envs/primus-env.sh` | `1` enables `set -x` in the env loader for shell tracing. | +| `PRIMUS_SKIP_VALIDATION` | `0` | User / tests | `runner/helpers/envs/primus-env.sh` | `1` skips `validate_distributed_params` (not recommended). | +| `PRIMUS_EXPECT_IB` | (unset) | User | `primus/tools/preflight/network/network_standard.py` | When `1`, preflight treats InfiniBand as expected for validation. | +| `PRIMUS_CLUSTER` | `amd-aig-poolside` (CLI default) | User | `primus/tools/benchmark/rccl_bench_args.py` | Cluster label for RCCL benchmark tooling. | +| `PRIMUS_GPU_ARCH` | (auto / `"mi300x"` in simulators) | User / CLI | `primus/core/projection/simulation_backends/origami_backend.py`, `sdpa_simulator.py`, `projection.py` CLI | GPU architecture string for performance projection. | +| `PRIMUS_GPU_CLOCK_MHZ` | (unset) | User | Same as `PRIMUS_GPU_ARCH` | Optional clock override for projection. | +| `PRIMUS_GPU_DEVICE` | `0` | User | `origami_backend.py` | GPU index for hardware detection in projection. | +| `PRIMUS_GEMM_BACKEND` | (unset) | User | `primus/core/projection/simulation_backends/factory.py` | Selects GEMM simulation backend by name. | +| `PRIMUS_PREFLIGHT_MIN_FREE_MEM_GB` | `1` | User | `primus/tools/preflight/gpu/utils.py` | Minimum free GPU memory (GB) for preflight checks. | +| `PRIMUS_PREFLIGHT_MIN_TFLOPS` | `10.0` | User | `primus/tools/preflight/gpu/utils.py` | Minimum TFLOPS threshold for preflight GEMM checks. | +| `PRIMUS_TURBO_AUTO_TUNE` | (unset) | User / tests | `tests/trainer/test_megatron_trainer.py` (integration) | Enables Turbo auto-tuning in supported Turbo/Megatron test flows; not referenced in core `primus/` Python outside tests. **Optional**. | +| `PRIMUS_TURBO_MOE_DISPATCH_COMBINE_BACKEND` | `TURBO` | User; hooks may set `DEEP_EP` | `primus/backends/megatron/patches/args/rocm_arg_validation.py`, `examples/run_pretrain.sh`, `runner/helpers/hooks/05_using_uep.sh` | MoE dispatch/combine backend selector. | + +--- + +## 3. Primus YAML substitution + +Parsed by `primus/core/config/yaml_loader.py` for patterns `${VAR}` (required) and `${VAR:default}` (optional). Typical experiment YAMLs under `examples/` use these for sweep-friendly overrides. + +| Variable | Typical default in YAML | Where set | Where used | Description | +|----------|-------------------------|-----------|------------|-------------| +| `PRIMUS_TEAM` | `"amd"` | User | Resolved before module merge in experiment YAML | Work group / team segment in paths. | +| `PRIMUS_USER` | `"root"` | User | Experiment YAML | User name segment. | +| `PRIMUS_EXP_NAME` | per-example | User | Experiment YAML | Experiment folder name. | +| `PRIMUS_WORKSPACE` | `"./output"` | User | Experiment YAML | Root workspace for artifacts. | +| `PRIMUS_TP` | `1` | User | Megatron example YAMLs | `tensor_model_parallel_size` override. | +| `PRIMUS_PP` | `1` | User | Megatron example YAMLs | `pipeline_model_parallel_size` override. | +| `PRIMUS_EP` | `1` | User | Megatron example YAMLs | `expert_model_parallel_size` override. | +| `PRIMUS_SEQ_LENGTH` | per-model | User | Megatron example YAMLs | Sequence length override. | +| `PRIMUS_MAX_POSITION_EMBEDDINGS` | `4096` or `131072` | User | `examples/megatron/**/*.yaml`, tests | Position embedding cap override. | +| `PRIMUS_GLOBAL_BATCH_SIZE` | per-model | User | Megatron example YAMLs | Global batch override. | +| `PRIMUS_NUM_LAYERS` | per-model | User | Tests and MoE examples | Transformer layer count override. | +| `PRIMUS_MOE_LAYER_FREQ` | MoE patterns | User | MoE examples / tests | MoE layer frequency pattern. | +| `PRIMUS_TOKENIZED_DATA_PATH` | `null` | User | Megatron examples | Path to tokenized training data. | +| `PRIMUS_MODEL` | per-stack | User | Megatron examples | Model preset stem (e.g. `llama3_8B`). | +| `PRIMUS_VPP` | `null` | User | `tests/trainer/test_megatron_trainer.yaml` | Virtual pipeline stages override. | + +--- + +## 4. NCCL / RCCL + +Primus seeds many of these in `runner/helpers/envs/base_env.sh`. RCCL honors NCCL-compatible variables on AMD GPUs. See [NCCL environment](https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/env.html) and [RCCL environment](https://rocm.docs.amd.com/projects/rccl/en/develop/api-reference/env-variables.html). + +| Variable | Default (Primus base) | Where set | Where used | Description | +|----------|------------------------|-----------|------------|-------------| +| `NCCL_DEBUG` | unset | User / `base_env.sh` empty default | Preflight reports, RCCL runtime | Log verbosity: `NONE`, `WARN`, `INFO`, `TRACE`, etc. **Optional** unless debugging comms. | +| `NCCL_SOCKET_IFNAME` | derived from `IP_INTERFACE` | `base_env.sh` | `primus/tools/preflight/network/*.py`, GPU topology helpers | Socket NIC for host networking. | +| `GLOO_SOCKET_IFNAME` | same as NCCL if unset | `base_env.sh` | Preflight | Gloo TCP backend interface. | +| `NCCL_IB_HCA` | auto via `get_nccl_ib_hca.sh` if empty | `base_env.sh`, container passthrough | Preflight, multi-node tuning | InfiniBand HCAs to use. | +| `NCCL_IB_GID_INDEX` | `3` | `base_env.sh` | RCCL | GID index for IB/RoCE; many sites use `1` for RoCE v2 (override as needed). | +| `NCCL_IB_TC` | (unset) | User | RCCL | InfiniBand traffic class. | +| `NCCL_IB_FIFO_TC` | (unset) | User | RCCL | InfiniBand FIFO traffic class. | +| `NCCL_IB_ROCE_VERSION_NUM` | (unset) | User | RCCL | RoCE version selection. | +| `NCCL_PXN_DISABLE` | `1` | `base_env.sh` | RCCL | Disable PXN (PCIe cross-NIC); set `0` to enable. | +| `NCCL_P2P_NET_CHUNKSIZE` | `524288` | `base_env.sh` | RCCL | P2P network chunk size tuning. | +| `NCCL_PROTO` | (unset) | User | RCCL | Protocol selection (e.g. `Simple`, `LL`, `LL128`). | +| `NCCL_CROSS_NIC` | `0` | `base_env.sh` | RCCL | Cross-NIC communication policy. | +| `NCCL_IB_RETRY_CNT` | (unset) | User | RCCL | IB retry count. | +| `NCCL_IB_TIMEOUT` | (unset) | User | RCCL | IB timeout. | +| `NCCL_NET_GDR_LEVEL` | (unset) | User | Preflight summaries | GPUDirect RDMA level. | +| `NCCL_IB_DISABLE` | `0` | User / env | Preflight | Disable IB; use sockets only. | +| `NCCL_DMABUF_ENABLE` | (unset) | User | RCCL | DMA-BUF registration path. | +| `NCCL_IGNORE_CPU_AFFINITY` | (unset) | User | RCCL | Ignore CPU affinity hints. | +| `NCCL_IB_QPS_PER_CONNECTION` | (unset) | User | RCCL | IB QPs per connection. | +| `NCCL_MAX_P2P_CHANNELS` | (unset) | User | RCCL | Cap P2P channels. | +| `NCCL_GDR_FLUSH_DISABLE` | (unset) | User | RCCL | Disable GDR flush. | +| `NCCL_IB_USE_INLINE` | (unset) | User | RCCL | Inline IB sends. | +| `NCCL_NET_PLUGIN` | (unset) | User | RCCL | Alternate network plugin (e.g. `librccl-anp.so`). | +| `RCCL_MSCCL_ENABLE` | `0` | `base_env.sh` | RCCL | Enable MSCCL algorithms. | +| `RCCL_MSCCLPP_THRESHOLD` | `1GiB` default | `base_env.sh` | RCCL | MSCCL++ message-size threshold. | +| `RCCL_GDR_FLUSH_GPU_MEM_NO_RELAXED_ORDERING` | `0` in hooks | `runner/helpers/hooks/03_enable_ainic.sh` | RCCL | Stricter GDR flush memory ordering; relevant for some NIC/GPU combos. | +| `TORCH_NCCL_USE_TENSOR_REGISTER_ALLOCATOR_HOOK` | `0` | `base_env.sh` | PyTorch + RCCL | Tensor allocator hook for NCCL registration. | +| `TORCH_NCCL_HIGH_PRIORITY` | `1` | `base_env.sh` | PyTorch | High-priority NCCL streams. | + +--- + +## 5. ROCm / HSA / HIP + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `HSA_ENABLE_SDMA` | `1` | `base_env.sh` | ROCm runtime | Enable SDMA engines for copies. | +| `HSA_NO_SCRATCH_RECLAIM` | `1` | `base_env.sh`, container passthrough | ROCm runtime; documented for MoE stability | `1` keeps scratch allocated (often used for MoE stability). See [ROCR environment](https://rocm.docs.amd.com/projects/ROCR-Runtime/en/docs-7.1.1/environment_variables.html). | +| `HIP_VISIBLE_DEVICES` | `0..GPUS_PER_NODE-1` | `base_env.sh` | ROCm device visibility | Restricts which GPU indices ROCm exposes. | +| `ROCBLAS_DEFAULT_ATOMICS_MODE` | (unset) | User | `primus/backends/megatron/patches/args/rocm_arg_validation.py` | Read for deterministic / accuracy-sensitive GEMM behavior. | + +--- + +## 6. CUDA / PyTorch + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `CUDA_DEVICE_MAX_CONNECTIONS` | `1` | `base_env.sh`; Megatron patches may adjust | `primus/backends/megatron/patches/env_patches.py`, Megatron patches | Limits concurrent CUDA connections; often `1` for TP/PP overlap. | +| `TORCH_COMPILE_DISABLE` | `0` | User | `primus/backends/megatron/patches/args/rocm_arg_validation.py` | Disable `torch.compile` when `1`. | + +--- + +## 7. Transformer engine + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `NVTE_ROCM_ENABLE_MXFP8` | `1` | `base_env.sh` | Transformer Engine on ROCm | Enable MXFP8 paths. | +| `NVTE_CK_USES_BWD_V3` | `1` | `base_env.sh`, container passthrough | TE / CK | Use CK backward v3 kernels. | +| `NVTE_CK_IS_V3_ATOMIC_FP32` | (unset; examples print `0`) | User / `examples/run_pretrain.sh`, container passthrough | TE / CK | Atomic FP32 mode for CK v3 backward. | +| `PATCH_TE_FLASH_ATTN` | `0` | `base_env.sh`, container passthrough | `runner/helpers/hooks/01_patch_te_flash_attn_max_version.sh` | Trigger TE flash-attn patch hook when `1`. | + +--- + +## 8. Caches and authentication + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `HF_HOME` | `${DATA_PATH}/huggingface` | `base_env.sh`, `primus/core/utils/env_setup.py`, `primus/pretrain.py` | Hugging Face libraries | Cache for models and datasets. | +| `HF_TOKEN` | (unset) | User, container passthrough | Hugging Face Hub | Auth for gated models. **Required** for private/gated assets. | +| `TORCH_HOME` | under workspace | `primus/core/utils/env_setup.py` | PyTorch Hub | Torch Hub cache root. | +| `TRANSFORMERS_CACHE` | aligned with HF layout | `primus/core/utils/env_setup.py` | `transformers` | Model cache for Transformers. | +| `WANDB_API_KEY` | (unset) | User, container passthrough | Weights & Biases client, Megatron trainer checks | API key for logging. **Required** for Weights & Biases when enabled. | +| `WANDB_PROJECT` | (unset) | User / TorchTitan patch | `primus/backends/torchtitan/patches/wandb_patches.py` | Project name. | +| `WANDB_RUN_NAME` | (unset) | User / patches | Same | Run display name. | +| `WANDB_TEAM` | (unset) | User | TorchTitan metrics (entity) | WandB team/entity. | +| `DATABRICKS_HOST` | (unset) | User | `mlflow` client (via `primus/backends/megatron/training/global_vars.py` MLflow setup) | Required for Databricks-hosted MLflow when MLflow logging is enabled. | +| `DATABRICKS_TOKEN` | (unset) | User | Databricks APIs | Auth token paired with host. | +| `MLFLOW_TRACKING_URI` | (unset) | User | `mlflow` (via Megatron integrations) | MLflow tracking server URI. **Optional** unless using MLflow. | +| `MLFLOW_REGISTRY_URI` | (unset) | User | MLflow | Model registry endpoint. | +| `NLTK_DATA` | (unset) | User | `runner/helpers/hooks/train/pretrain/megatron/preprocess_data.py`, Megatron-LM tools | Punkt and other tokenizer data for preprocessing. | +| `TOKENIZED_DATA_PATH` | per-hook default | User | `runner/helpers/hooks/train/pretrain/megatron/prepare.py` | Pre-tokenized dataset location for Megatron data prep hooks. | + +--- + +## 9. hipBLASLt tuning + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `PRIMUS_HIPBLASLT_TUNING` | `0` | User | `examples/run_pretrain.sh` | **Master switch** for the HipBLASLt tuning flow (`1` enables). Must be set before `PRIMUS_HIPBLASLT_TUNING_STAGE` takes effect, and is mutually exclusive with deterministic mode (`PRIMUS_DETERMINISTIC=1`). | +| `PRIMUS_HIPBLASLT_TUNING_STAGE` | `0` | User | `examples/run_pretrain.sh` | Stages `0` off, `1` dump shapes, `2` offline tune, `3` apply tuned kernels. | +| `HIPBLASLT_TUNING_OVERRIDE_FILE` | (unset) | User / tuning scripts | `examples/run_pretrain.sh` | Path to tuned-kernel override file for stage `3`. | +| `TE_HIPBLASLT_TUNING_RUN_COUNT` | varies | User | `examples/run_pretrain.sh` | Number of benchmark runs per shape during TE hipBLASLt tuning. | +| `TE_HIPBLASLT_TUNING_ALGO_COUNT` | varies | User | `examples/run_pretrain.sh` | Transformer Engine hipBLASLt search breadth. | +| `TE_HIPBLASLT_TUNING_ALGO_FILE` | (unset) | User | TE + HipBLASLt | Algorithm file for TE tuning flows. | +| `TE_HIPBLASLT_TUNING` | (unset) | User | `examples/run_pretrain.sh` | When set, interacts with deterministic mode and tuning stages (disable conflicting modes per script comments). | +| `HIPBLASLT_LOG_LEVEL` | (unset) | User | HipBLASLt | Library log level. | +| `HIPBLASLT_LOG_MASK` | (unset) | User | HipBLASLt | Bitmask for log categories. | + +--- + +## 10. Build and rebuild + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `REBUILD_PRIMUS_TURBO` | `0` | User, container passthrough | `runner/helpers/hooks/00_rebuild_primus_turbo.sh` | `1` rebuilds Primus-Turbo on startup. | +| `REBUILD_BNXT` | `0` | User, container passthrough | `runner/helpers/hooks/02_rebuild_bnxt.sh` | `1` rebuilds BNXT driver artifacts when packaged. | +| `USING_AINIC` | (unset) | User | `runner/helpers/hooks/03_enable_ainic.sh` | `1` enables AINIC-oriented networking hooks. | +| `MAX_JOBS` | (unset) | User / tooling | `tools/daily/safe_wrapper.py` | Parallel compile jobs for pip builds. | +| `BACKEND_PATH` | (unset) | User | `primus/pretrain.py`, `primus/core/backend/backend_adapter.py` | Override checkout path for third-party backends (Megatron, TorchTitan, MaxText). | + +--- + +## 11. Container passthrough + +`runner/.primus.yaml` lists names forwarded from the host into training containers (`container.options.env`). Primus does not assign values here; it only allowlists keys for `--env` forwarding. + +Forwarded keys: + +`MASTER_ADDR`, `MASTER_PORT`, `NNODES`, `NODE_RANK`, `GPUS_PER_NODE`, `DOCKER_IMAGE`, `HF_TOKEN`, `WANDB_API_KEY`, `ENABLE_NUMA_BINDING`, `REBUILD_PRIMUS_TURBO`, `USING_AINIC`, `PATCH_TE_FLASH_ATTN`, `REBUILD_BNXT`, `HSA_NO_SCRATCH_RECLAIM`, `NVTE_CK_USES_BWD_V3`, `GPU_MAX_HW_QUEUES`, `HSA_KERNARG_POOL_SIZE`, `PRIMUS_TURBO_DEEPEP_TIMEOUT`, `NCCL_IB_HCA`, `NCCL_SOCKET_IFNAME`, `GLOO_SOCKET_IFNAME`, `NCCL_IB_GID_INDEX`, `PRIMUS_TURBO_ATTN_V3_ATOMIC_FP32`, `NVTE_CK_IS_V3_ATOMIC_FP32`, `PATH_TO_BNXT_TAR_PACKAGE`, `ANP_HOME_DIR`, `RCCL_HOME_DIR`, `MPI_HOME_DIR`, `DUMP_HLO`, `DUMP_HLO_DIR`, `PRIMUS_DETERMINISTIC`, `PRIMUS_HIPBLASLT_TUNING`, `PRIMUS_HIPBLASLT_TUNING_STAGE`, `TE_HIPBLASLT_TUNING_RUN_COUNT`, `TE_HIPBLASLT_TUNING_ALGO_COUNT`, `HIPBLASLT_LOG_MASK`, `HIPBLASLT_LOG_FILE`, `HIPBLASLT_LOG_LEVEL`, `HIPBLASLT_TUNING_OVERRIDE_FILE` + +--- + +## 12. Slurm + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `SLURM_NNODES` / `SLURM_JOB_NUM_NODES` | job-dependent | Slurm | `primus-cli-slurm-entry.sh` (`NNODES` export), preflight probes | Node count for the allocation. | +| `SLURM_NODEID` | job-dependent | Slurm | Mapped to `NODE_RANK` in `primus-cli-slurm-entry.sh` | Node index. | +| `SLURM_PROCID` | job-dependent | Slurm | Fallback for `NODE_RANK` when `SLURM_NODEID` is unset | Process ID within the Slurm step (entry script). | +| `SLURM_JOB_ID` | job-dependent | Slurm | `primus/tools/preflight/host/host_probe.py` | Job identifier string. | + +--- + +## 13. Debug and pipeline + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `DUMP_PP_DIR` | `output/pp_data` | User | `primus/backends/megatron/megatron_pretrain_trainer.py`, `primus/backends/megatron/patches/pp_dump_data_patches.py` | Directory for pipeline-parallel debug dumps. | +| `DEBUG_SIMULATOR` | `0` | User | `primus/core/projection/performance_projection/simulator.py` | `1` enables verbose projection simulator logging. | +| `RECORD_OFFLOAD_MEMORY_INFO` | `0` | User | `primus/core/pipeline_parallel/handler/offload_handler.py` | Record offload memory stats when `1`. | +| `RECORD_OFFLOAD_MEMORY_INFO_DIR` | `output` | User | `primus/core/pipeline_parallel/scheduler/scheduler.py` | Output directory for offload memory logs. | +| `USE_PINNED_OFFLOAD` | `0` | User | `offload_handler.py` | Use pinned host memory for offload buffers when `1`. | + +--- + +## 14. JAX / XLA (MaxText) + +Primus MaxText hooks print recommended values in `runner/helpers/hooks/train/pretrain/maxtext/prepare.py`; MaxText and JAX read them directly. + +| Variable | Default | Where set | Where used | Description | +|----------|---------|-----------|------------|-------------| +| `XLA_PYTHON_CLIENT_MEM_FRACTION` | e.g. `.97` in prepare hook | User / hook output | JAX / XLA allocator | Fraction of GPU memory pre-allocated for JAX. | +| `DUMP_HLO_DIR` | `${PRIMUS_PATH}/output/xla_dump_hlo` (example) | User | XLA via `XLA_FLAGS` composition | Directory for HLO dumps when enabled. | +| `DUMP_HLO` | `0` | User | Prepare hook → XLA flags | Gate HLO dumping (`1` enables in hook samples). | + +**Note:** MaxText also propagates many knobs through `XLA_FLAGS` and `LIBTPU_INIT_ARGS` upstream; see MaxText sources for the full list. diff --git a/docs/03-configuration-reference/maxtext-parameters.md b/docs/03-configuration-reference/maxtext-parameters.md new file mode 100644 index 000000000..17f03bdbd --- /dev/null +++ b/docs/03-configuration-reference/maxtext-parameters.md @@ -0,0 +1,146 @@ +# MaxText backend configuration reference + +Primus routes experiment YAML into the [MaxText](https://maxtext.readthedocs.io/) stack (JAX / XLA). Configuration is a **flat map of keys** (no nested `training.`* trees like TorchTitan): Primus merges module and model presets, writes a temporary YAML, and MaxText’s `pyconfig.initialize` loads it on top of upstream defaults. + +The Primus overlay keeps `base_config: "base.yml"` so MaxText loads its own [`configs/base.yml`](https://github.com/AI-Hypercomputer/maxtext/blob/main/src/maxtext/configs/base.yml) at runtime. This page lists **Primus-defined defaults and commonly overridden Primus fields**. For the full upstream parameter set (hundreds of keys), see the [MaxText documentation](https://maxtext.readthedocs.io/) and upstream `base.yml`. + +--- + +## How parameters flow + +1. YAML presets under `primus/configs/modules/maxtext/` and `primus/configs/models/maxtext/` are merged with CLI overrides. +2. `MaxTextAdapter.convert_config` passes the merged namespace through `MaxTextConfigBuilder` (currently a thin pass-through). +3. `export_params_to_yaml` writes a flat YAML file; MaxText ignores unknown Primus-private keys via pydantic filtering. +4. Unknown keys from upstream still resolve through environment overrides inside MaxText (`pyconfig`), not shown here. + +--- + +## 1. Base module parameters + +Shared with all Primus modules via `module_base.yaml` and trainer extensions. + + +| Parameter | Default (Primus) | Description | +| ------------------- | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | +| `trainable` | `true` in `trainer_base.yaml` (overrides `module_base`’s `false`) | When `true`, the module participates in training orchestration. | +| `sink_level` | `null` | Structured logging sink level for the module (if the logging stack is configured to use it). | +| `file_sink_level` | `DEBUG` | File sink verbosity. | +| `stderr_sink_level` | `INFO` | Stderr sink verbosity. | + + +--- + +## 2. Training + +From `pre_trainer.yaml` (extends `trainer_base.yaml`). + + +| Parameter | Default | Description | +| ------------- | ------------ | ------------------------------------------------------------- | +| `base_config` | `"base.yml"` | Upstream MaxText base file loaded by `pyconfig._load_config`. | +| `hardware` | `"gpu"` | Hardware target string consumed by MaxText. | +| `steps` | `1000` | Global optimizer steps for the run. | +| `log_period` | `100` | Steps between log emissions. | + + +--- + +## 3. Data + + +| Parameter | Default | Description | +| ---------------- | -------------- | -------------------------------------------------------------------- | +| `dataset_type` | `"hf"` | Dataset backend selector (Hugging Face in the default path). | +| `hf_path` | `"allenai/c4"` | Hugging Face dataset repo or identifier. | +| `hf_data_dir` | `"en"` | Subdirectory / config slice within the HF dataset. | +| `hf_train_files` | `""` | Optional explicit train file list (format per MaxText HF loader). | +| `packing` | `true` | Sequence packing for efficiency when supported by the data pipeline. | + + +--- + +## 4. Checkpointing + +These are Primus overlay defaults. MaxText also loads upstream `base.yml` at runtime through `base_config: "base.yml"`, where upstream checkpoint defaults might differ. When debugging effective behavior, distinguish the Primus YAML written by the adapter from the upstream MaxText defaults loaded afterward. + + +| Parameter | Default | Description | +| ---------------------- | ------- | ------------------------------------------------------------------ | +| `enable_checkpointing` | `false` | See Training section. | +| `async_checkpointing` | `false` | When `enable_checkpointing` is true, use async checkpoint workers. | + + +--- + +## 5. Profiling + + +| Parameter | Default | Description | +| --------------------------------- | ---------- | --------------------------------------- | +| `profiler` | `"xplane"` | Profiler backend (e.g. XPlane for JAX). | +| `skip_first_n_steps_for_profiler` | `3` | Warmup steps excluded from capture. | +| `profiler_steps` | `1` | Number of steps to profile once active. | + + +--- + +## 6. Memory and recomputation + + +| Parameter | Default | Description | +| ------------------------------- | -------- | ---------------------------------------------------------------------------------- | +| `remat_policy` | `'full'` | Activation rematerialization policy (`none`, `minimal`, `full`, etc.—see MaxText). | +| `optimizer_memory_host_offload` | `false` | Offload optimizer state to host memory when supported. | +| `scan_layers` | `true` | Use scanned layer implementation where applicable. | +| `param_scan_axis` | `1` | Axis for parameter scanning / partitioning layout. | + + +--- + +## 7. Precision and quantization + + +| Parameter | Default | Description | +| ------------------------- | ----------------- | --------------------------------------------------------------------- | +| `dtype` | `"bfloat16"` | Default compute dtype for many ops. | +| `quantization` | `""` | Quantization mode string (empty = none; set per MaxText AQT recipes). | +| `quantize_kvcache` | `false` | Quantize KV cache tensors. | +| `kv_quant_axis` | `"heads_and_dkv"` | KV quantization axis naming for kernels. | +| `kv_quant_dtype` | `"int8"` | Storage dtype for KV cache when quantization is on. | +| `weight_dtype` | `bfloat16` | Weight storage / compute dtype for non-quantized paths. | +| `checkpoint_is_quantized` | `false` | Set `true` when loading an AQT-quantized checkpoint. | +| `logits_dot_in_fp32` | `false` | Compute logits matmul in `float32` for numerical stability. | + + +--- + +## 8. Model + +From `model_base.yaml` and per-model files such as `llama3_8B.yaml`. + + +| Parameter | Default | Description | +| ----------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------- | +| `model_name` | `"default"` in `model_base`; e.g. `"llama3-8b"` in `llama3_8B.yaml` | Selects MaxText’s bundled model YAML when present. | +| `override_model_config` | `true` | When `true`, CLI / kwargs override values from the loaded model config. | +| `attention` | `"cudnn_flash_te"` | Attention implementation (Primus default favors TE flash on AMD GPUs). | +| `use_iota_embed` | `true` | Use iota-based embedding for performance on accelerator backends. | +| `tokenizer_path` | e.g. `"meta-llama/Meta-Llama-3-8B"` | Hugging Face tokenizer id or local path. | + + +--- + +## 9. Advanced + + +| Parameter | Default | Description | +| --------- | ------- | --------------------------------------------------------------------- | +| `shardy` | `false` | Enable Shardy-related integration in MaxText when building shardings. | + + +--- + +## Related reading + +- [MaxText documentation](https://maxtext.readthedocs.io/)—full parameter reference and recipes. +- Primus implementation: `primus/backends/maxtext/argument_builder.py`, `maxtext_pretrain_trainer.py`, `maxtext_adapter.py`. diff --git a/docs/03-configuration-reference/megatron-bridge-parameters.md b/docs/03-configuration-reference/megatron-bridge-parameters.md new file mode 100644 index 000000000..156b82a5b --- /dev/null +++ b/docs/03-configuration-reference/megatron-bridge-parameters.md @@ -0,0 +1,167 @@ +# Megatron Bridge backend configuration reference + +Megatron Bridge integrates [Megatron-Core](https://github.com/NVIDIA/Megatron-LM) training with Hugging Face–centric workflows. In Primus, the **`megatron_bridge`** framework is used for post-training with module preset `sft_trainer.yaml`, and the repository also ships a pretraining preset at `primus/configs/modules/megatron_bridge/pretrain_trainer.yaml`. + +## Recipe system + +Megatron Bridge resolves training defaults through a **recipe** and **flavor**: + +- `recipe` is a Python module path under `megatron.bridge.recipes` (e.g. `qwen.qwen3`). +- `flavor` is the function name inside that module (e.g. `qwen3_8b_finetune_config`) that returns a `ConfigContainer`. + +At runtime, `load_recipe_config` in `primus/backends/megatron_bridge/config_utils.py`: + +1. Imports `megatron.bridge.recipes.` and calls `(**filtered_backend_args)` to build the baseline `ConfigContainer`. +2. **Deep-merges** Primus `backend_args` (from YAML + CLI) into that dataclass via `_merge_dict_to_dataclass`, so user overrides sit on top of recipe defaults. + +You normally specify `recipe`, `flavor`, `hf_path`, and `dataset` in the model YAML; training hyperparameters and parallelism go in module overrides or experiment module overrides (`modules.post_trainer.overrides` for SFT/post-training, `modules.pre_trainer.overrides` for pretraining examples). + +--- + +## 1. Base module parameters + +From `primus/configs/modules/megatron_bridge/sft_trainer.yaml` (extends `module_base.yaml`). Pretraining examples use `pretrain_trainer.yaml` instead. + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `trainable` | `true` | Module participates in the training graph. | +| `sink_level` | `null` | Inherited from `module_base.yaml`; structured logging sink level. | +| `file_sink_level` | `DEBUG` | File sink verbosity. | +| `stderr_sink_level` | `INFO` | Stderr sink verbosity. | + +--- + +## 2. Training + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `stage` | `"sft"` | Backend stage selector. Primus dispatches post-training via `primus train posttrain` and loads the Megatron Bridge posttrain trainer when this module is used under `post_trainer`. | +| `trainable` | `true` | See Base module parameters. | + +**CLI note:** The user-facing suite is **`posttrain`** (`primus train posttrain --config ...`). The YAML `stage` field selects the Megatron Bridge trainer implementation (`sft`), not the CLI suite name. + +For Bridge pretraining, use the normal pretraining suite (`primus train pretrain --config ...`) with experiments that reference `modules.pre_trainer.config: pretrain_trainer.yaml`. + +--- + +## 3. Fine-tuning method (PEFT) + +Primus examples set these under `modules.post_trainer.overrides` (see `examples/megatron_bridge/configs/`). + +| Parameter | Example | Description | +|-----------|---------|-------------| +| `peft` | `"none"`, `"lora"` | Parameter-efficient fine-tuning mode. | +| `peft_dim` | `16` | LoRA rank (example: `llama31_70b_lora_posttrain.yaml`). | +| `peft_alpha` | `32` | LoRA scaling alpha (same example). | +| `packed_sequence` | `false` | Pack multiple short sequences per microbatch when supported. | + +Additional keys such as `pretrained_checkpoint`, `use_distributed_optimizer`, or `cross_entropy_loss_fusion` appear in larger examples and are merged into the recipe `ConfigContainer` when the dataclass exposes matching fields. + +--- + +## 4. Parallelism + +Typical overrides from Megatron Bridge examples: + +| Parameter | Example | Description | +|-----------|---------|-------------| +| `tensor_model_parallel_size` | `1`, `2`, `8` | Tensor parallelism degree. | +| `pipeline_model_parallel_size` | `1` | Pipeline parallelism degree. | +| `virtual_pipeline_model_parallel_size` | `null` | Virtual pipeline stages per rank when PP > 1. | +| `context_parallel_size` | `1` | Context parallelism degree. | +| `sequence_parallel` | `false` | Sequence parallelism within TP groups. | +| `use_megatron_fsdp` | `false` | Optional Megatron FSDP path. | + +--- + +## 5. Training hyperparameters + +| Parameter | Example | Description | +|-----------|---------|-------------| +| `train_iters` | `200`, `1000` | Total training iterations. | +| `global_batch_size` | `8`, `128` | Global batch across data-parallel groups. | +| `micro_batch_size` | `1`, `2` | Per-GPU microbatch before gradient accumulation. | +| `seq_length` | `2048`, `8192` | Training sequence length. | +| `eval_interval` | `30` | Steps between evaluations. | +| `save_interval` | `50` | Steps between checkpoint saves. | + +--- + +## 6. Learning rate + +| Parameter | Example | Description | +|-----------|---------|-------------| +| `finetune_lr` | `1.0e-4`, `5.0e-6` | Peak learning rate for fine-tuning. | +| `min_lr` | `0.0` | Floor learning rate after decay. | +| `lr_warmup_iters` | `50` | Linear warmup length in iterations. | +| `lr_decay_iters` | `null` | Optional decay span; `null` defers to recipe defaults. | + +--- + +## 7. Precision + +| Parameter | Example | Description | +|-----------|---------|-------------| +| `precision_config` | `bf16_mixed`, `fp16_mixed`, `fp32` | Mixed-precision recipe for Megatron Bridge. | +| `comm_overlap_config` | `null` | Optional communication/compute overlap policy object. | +| `pipeline_dtype` | `null` | Dtype for pipeline stages when PP is enabled. | + +--- + +## 8. Memory optimization + +| Parameter | Example | Description | +|-----------|---------|-------------| +| `recompute_granularity` | `full` | Activation recomputation granularity. | +| `recompute_method` | `uniform` | How recomputation is scheduled across layers. | +| `recompute_num_layers` | `1` | Number of layers per recompute group (workload-dependent). | + +--- + +## 9. Primus-Turbo + +From `sft_trainer.yaml` (defaults shown). + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `enable_primus_turbo` | `true` | Master flag for Primus-Turbo optimized kernels and paths. | +| `use_turbo_attention` | `false` | Turbo attention implementation. | +| `use_turbo_parallel_linear` | `false` | Turbo parallel linear layers. | +| `use_turbo_grouped_gemm` | `false` | Turbo grouped GEMM flag for MoE paths (the preset ships this key). The former `use_turbo_grouped_mlp` alias has been removed. | +| `moe_use_fused_router_with_aux_score` | `false` | Fused MoE router with auxiliary loss handling. | +| `enable_turbo_attention_float8` | `false` | FP8 path inside Turbo attention. | +| `use_turbo_deepep` | `false` | DeepEP-style expert-parallel integration. | +| `turbo_deepep_num_cu` | `32` | Compute-unit count hint for DeepEP. | +| `turbo_deepep_use_comm_stream` | `false` | Use dedicated communication streams. | +| `turbo_sync_free_moe_stage` | `0` | Sync-free MoE scheduling stage. | +| `use_turbo_fused_act_with_probs` | `false` | Fuse activation with probability tensors where applicable. | +| `use_turbo_rms_norm` | `false` | Turbo RMSNorm path. | + +**Environment:** `PRIMUS_TURBO_MOE_DISPATCH_COMBINE_BACKEND` (default `TURBO`) is read in `primus/backends/megatron/patches/args/rocm_arg_validation.py` to select MoE dispatch/combine behavior when Turbo MoE is active. + +--- + +## 10. Model and dataset + +Model YAML files (`qwen3_8b.yaml`, `qwen3_32b.yaml`, `llama31_70b.yaml`) supply: + +| Parameter | Example | Description | +|-----------|---------|-------------| +| `recipe` | `qwen.qwen3`, `llama.llama3` | Recipe module under `megatron.bridge.recipes`. | +| `flavor` | `qwen3_8b_finetune_config`, `llama31_70b_finetune_config` | Flavor function producing the baseline `ConfigContainer`. | +| `hf_path` | `Qwen/Qwen3-8B`, `meta-llama/Meta-Llama-3.1-70B` | Hugging Face model id for weights/tokenizer flows. | +| `dataset` | nested | Example: `dataset_name: "rajpurkar/squad"` for SQuAD-style fine-tuning. | + +**Logging (optional overrides in examples):** `wandb_project`, `wandb_entity`, `wandb_exp_name` might be set under `overrides` for experiment tracking when Weights & Biases is configured. + +--- + +## Argument merge mechanics + +`MegatronBridgeArgBuilder` (`primus/backends/megatron_bridge/argument_builder.py`) performs a **deep merge** of CLI and YAML into a single dict/namespace before `load_recipe_config` runs. Nested dicts (for example dataset or optimizer sections) combine recursively; explicit `None` in the merged structure can clear fields depending on merge rules in `_merge_dict_to_dataclass`. + +--- + +## Example layouts + +Under `examples/megatron_bridge/configs/`, per-GPU directories (for example `MI300X/`, `MI355X/`) contain full experiment YAMLs that set `work_group`, `user_name`, `exp_name`, `workspace`, and Megatron Bridge modules. Post-training examples use `modules.post_trainer` with `config: sft_trainer.yaml`; MI300X pretraining examples use `modules.pre_trainer` with `config: pretrain_trainer.yaml`. Both patterns set `framework: megatron_bridge`, `model: .yaml`, and an `overrides` block for parallelism, LR, precision, and related options. diff --git a/docs/03-configuration-reference/megatron-parameters.md b/docs/03-configuration-reference/megatron-parameters.md new file mode 100644 index 000000000..97e0bfc04 --- /dev/null +++ b/docs/03-configuration-reference/megatron-parameters.md @@ -0,0 +1,857 @@ +# Megatron backend configuration reference + +This page lists the flat configuration keys exposed by Primus when `framework: megatron`. Unless a section says otherwise, values are the defaults from `primus/configs/modules/megatron/trainer_base.yaml` and related model presets. The effective pretraining preset is `pre_trainer.yaml`, which extends `trainer_base.yaml` and overrides several high-impact training defaults. + +**Where parameters live.** Set overrides under `modules.pre_trainer.overrides:` in your experiment YAML. Model architecture keys usually come from `models..overrides:` (or your chosen model preset), but the same names map to Megatron’s argparse namespace either way. + +**Presets.** + +- Module presets: `primus/configs/modules/megatron/` (the main pretraining bundle is `pre_trainer.yaml`, which extends `trainer_base.yaml` and Primus Megatron add-ons). +- Model presets: `primus/configs/models/megatron/` (for example `language_model.yaml`). + +**Mapping to Megatron-LM.** Keys are passed through **1:1** to Megatron’s training arguments (same names as `argparse` / `Namespace`). Primus builds that namespace with `MegatronArgBuilder`. + +**Upstream reference.** Full flag semantics and newer options are defined in Megatron-LM: [`megatron/training/arguments.py`](https://github.com/NVIDIA/Megatron-LM/blob/main/megatron/training/arguments.py). + +### Example (experiment YAML) + +```yaml +framework: megatron + +modules: + pre_trainer: + overrides: + global_batch_size: 256 + train_iters: 50000 + tensor_model_parallel_size: 2 + +models: + pre_train: + overrides: + hidden_size: 2048 + num_layers: 32 +``` + +--- + +## 1. Base module parameters + +*Source: `primus/configs/modules/module_base.yaml` (merged into Megatron presets; `trainer_base.yaml` sets `trainable: true`).* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `trainable` | `true` | When `true`, this module participates in training workflows. (`module_base.yaml` alone defaults to `false`; Megatron `trainer_base.yaml` overrides to `true`.) | +| `sink_level` | `null` | Log level for the structured sink (Primus module plumbing); `null` uses framework default. | +| `file_sink_level` | `DEBUG` | Minimum level for file-backed logging. | +| `stderr_sink_level` | `INFO` | Minimum level for stderr logging. | + +--- + +## 2. Training and batching + +*Source: `primus/configs/modules/megatron/trainer_base.yaml`; effective `pre_trainer.yaml` overrides are noted where they differ.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `yaml_cfg` | `null` | Reserved; not supported as a Megatron override in this preset. | +| `spec` | `null` | Optional trainer spec hook (unused in defaults). | +| `micro_batch_size` | `2` | Samples per microbatch per data-parallel rank (per forward/backward step before gradient accumulation). | +| `batch_size` | `null` | Deprecated; use `micro_batch_size` / `global_batch_size`. | +| `global_batch_size` | `128` (`16` in `pre_trainer.yaml`) | Total batch size across the data-parallel world (before or after splitting, per Megatron semantics). | +| `rampup_batch_size` | `null` | Optional batch-size ramp schedule string / config. | +| `decrease_batch_size_if_needed` | `false` | Allow shrinking batch if memory is insufficient. | +| `check_for_nan_in_loss_and_grad` | `true` | Abort on NaNs in loss or gradients. | +| `check_for_spiky_loss` | `false` | Detect abnormal loss spikes. | +| `check_for_large_grads` | `false` | Detect abnormally large gradients. | +| `make_vocab_size_divisible_by` | `128` | Pads vocabulary size for efficient kernels / partitioning. | +| `exit_signal_handler` | `false` | Install handlers for graceful shutdown signals. | +| `exit_duration_in_mins` | `null` | Stop training after this many minutes. | +| `exit_interval` | `null` | Exit after this many iterations (if set). | +| `onnx_safe` | `null` | ONNX export compatibility tweaks. | +| `bert_binary_head` | `true` | Use BERT binary classification head when applicable. | +| `use_flash_attn` | `false` (`true` in `pre_trainer.yaml`) | Prefer FlashAttention kernels when available. | +| `seed` | `1234` | RNG seed for reproducibility. | +| `data_parallel_random_init` | `false` | Random init that varies across data-parallel ranks. | +| `init_method_xavier_uniform` | `false` | Use Xavier uniform for some weights. | +| `test_mode` | `false` | Lightweight test path (fewer steps / checks). | +| `train_iters` | `null` (`1000` in `pre_trainer.yaml`) | Total training iterations (mutually exclusive with sample-based stopping in typical setups). | +| `train_samples` | `null` | Total training samples (when using sample-based training). | +| `eval_iters` | `32` (`0` in `pre_trainer.yaml`) | Validation iterations per eval. | +| `eval_interval` | `2000` (`1000` in `pre_trainer.yaml`) | Run validation every this many iterations. | +| `full_validation` | `false` | Run a full pass over validation data. | +| `multiple_validation_sets` | `false` | Multiple validation datasets / passes. | +| `skip_train` | `false` | Only run eval / test, no training updates. | +| `train_sync_interval` | `null` | Periodic distributed sync barrier for debugging. | +| `adlr_autoresume` | `false` | ADLR autoresume integration. | +| `adlr_autoresume_interval` | `1000` | Autoresume checkpoint interval. | +| `manual_gc` | `false` | Force Python GC on a schedule. | +| `manual_gc_interval` | `1` | GC every N steps when `manual_gc` is enabled. | +| `manual_gc_eval` | `false` | Run manual GC during evaluation. | +| `mask_type` | `random` | Masking strategy for MLM / similar objectives. | +| `mask_factor` | `1.0` | Masking strength multiplier. | +| `iter_per_epoch` | `1250` | Iterations interpreted as one “epoch” for logging. | + +--- + +## 3. Mixed precision + +*Source: `trainer_base.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `fp16` | `false` | Enable FP16 mixed precision training. | +| `bf16` | `true` | Enable BF16 mixed precision training. | +| `grad_reduce_in_bf16` | `false` | All-reduce gradients in BF16 (saves bandwidth). | +| `calculate_per_token_loss` | `false` | Normalize loss per token instead of per sample. | +| `loss_scale` | `null` | Static loss scale for FP16; `null` uses dynamic scaling. | +| `initial_loss_scale` | `4294967296` | Initial dynamic loss scale. | +| `min_loss_scale` | `1.0` | Floor for dynamic loss scale. | +| `loss_scale_window` | `1000` | Window for dynamic loss scaling updates. | +| `hysteresis` | `2` | Hysteresis steps for loss-scale decreases. | +| `accumulate_allreduce_grads_in_fp32` | `false` | Accumulate and reduce gradients in FP32. | +| `fp16_lm_cross_entropy` | `false` | Compute LM cross-entropy in FP16. | +| `fp8` | `null` | FP8 recipe selection (`e4m3`, `hybrid`, etc.); `null` disables. | +| `fp8_margin` | `0` | FP8 scaling margin. | +| `fp8_recipe` | `delayed` | FP8 recipe variant (e.g. delayed scaling). | +| `fp8_interval` | `1` | Deprecated FP8 interval (kept for compatibility). | +| `fp8_amax_history_len` | `1024` | History length for FP8 amax statistics. | +| `fp8_amax_compute_algo` | `"max"` | How to combine amax history (`max`, etc.). | +| `fp8_wgrad` | `true` | Run weight gradients in FP8 where supported. | +| `fp8_param_gather` | `false` | FP8 parameter gather for distributed optimizer paths. | +| `te_rng_tracker` | `false` | Transformer Engine RNG tracker for FP8. | +| `inference_rng_tracker` | `false` | Separate RNG tracker for inference FP8. | +| `fp4` | `null` | FP4 mode; `null` disables. | +| `fp4_recipe` | `nvfp4` | FP4 recipe name. | +| `fp4_param` | `false` | Store parameters in FP4. | +| `first_last_layers_bf16` | `false` | Keep first/last layers in BF16 for stability. | +| `num_layers_at_start_in_bf16` | `1` | Count of early layers forced to BF16 when enabled. | +| `num_layers_at_end_in_bf16` | `1` | Count of final layers forced to BF16 when enabled. | +| `no_fp8_weight_transpose_cache` | `false` | *Primus:* disable FP8 weight transpose cache (see `primus_megatron_module.yaml`). | + +--- + +## 4. Optimizer and learning rate + +*Source: `trainer_base.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `optimizer` | `adam` | Optimizer family (`adam`, `sgd`, etc.). | +| `lr` | `2.5e-4` (`2.0e-05` in `pre_trainer.yaml`) | Peak learning rate. | +| `lr_decay_style` | `cosine` | LR decay schedule (`cosine`, `linear`, `constant`, WSD, etc.). | +| `lr_decay_iters` | `null` | Decay duration in iterations. | +| `lr_decay_samples` | `null` | Decay duration in samples. | +| `lr_warmup_fraction` | `null` | Warmup as a fraction of total train steps. | +| `lr_warmup_iters` | `0` (`40` in `pre_trainer.yaml`) | Linear warmup steps. | +| `lr_warmup_samples` | `0` | Warmup in samples. | +| `lr_warmup_init` | `0.0` | LR at the start of warmup. | +| `min_lr` | `2.5e-5` (`0.0` in `pre_trainer.yaml`) | Minimum LR after decay. | +| `lr_wsd_decay_style` | `exponential` | Weight-decay schedule style for WSD when used. | +| `lr_wsd_decay_samples` | `null` | WSD decay window in samples. | +| `lr_wsd_decay_iters` | `null` | WSD decay window in iterations. | +| `head_lr_mult` | `1.0` | LR multiplier for attention/head modules when supported. | +| `weight_decay` | `0.01` (`0.0` in `pre_trainer.yaml`) | AdamW / L2-style weight decay. | +| `start_weight_decay` | `null` | Starting weight decay for schedules. | +| `end_weight_decay` | `null` | Ending weight decay for schedules. | +| `weight_decay_incr_style` | `constant` | How weight decay changes between start/end. | +| `clip_grad` | `1.0` | Global gradient norm clip. | +| `adam_beta1` | `0.9` | Adam first moment decay. | +| `adam_beta2` | `0.95` (`0.999` in `pre_trainer.yaml`) | Adam second moment decay. | +| `adam_eps` | `1.0e-08` | Adam epsilon. | +| `sgd_momentum` | `0.9` | SGD momentum when `optimizer` is SGD. | +| `override_opt_param_scheduler` | `false` (`true` in `pre_trainer.yaml`) | Override optimizer parameter groups’ schedulers. | +| `use_checkpoint_opt_param_scheduler` | `false` | Load optimizer scheduler state strictly from checkpoint. | +| `warmup` | `null` | Alternate warmup specification (legacy / schedule hooks). | +| `decoupled_lr` | `null` | Decoupled LR for certain param groups. | +| `decoupled_min_lr` | `null` | Minimum for decoupled LR. | +| `muon_extra_scale_factor` | `1.0` | Muon optimizer scaling. | +| `muon_scale_mode` | `"spectral"` | Muon scaling mode. | +| `muon_fp32_matmul_prec` | `"medium"` | Muon matmul precision hint. | +| `muon_num_ns_steps` | `5` | Muon Newton–Schulz iterations. | +| `muon_tp_mode` | `"blockwise"` | Muon tensor-parallel mode. | +| `muon_use_nesterov` | `false` | Muon Nesterov momentum. | +| `muon_split_qkv` | `true` | Split QKV for Muon. | +| `muon_momentum` | `0.95` | Muon momentum. | +| `muon_weight_decay` | `0.01` | Muon-specific decay. | +| `muon_weight_decay_method` | `"decoupled"` | How Muon applies decay. | +| `optimizer_cpu_offload` | `false` | Offload optimizer state to CPU. | +| `optimizer_offload_fraction` | `1.0` | Fraction of optimizer state offloaded. | +| `use_torch_optimizer_for_cpu_offload` | `false` | Use PyTorch optimizer for offload path. | +| `overlap_cpu_optimizer_d2h_h2d` | `false` | Overlap CPU optimizer device transfers. | +| `pin_cpu_grads` | `true` | Pin memory for CPU gradients. | +| `pin_cpu_params` | `true` | Pin memory for CPU params in offload. | +| `use_precision_aware_optimizer` | `false` | Use precision-aware optimizer (main grads/params in lower precision). | +| `main_grads_dtype` | `fp32` | Dtype for main gradients (`fp32`, `bf16`). | +| `main_params_dtype` | `fp32` | Dtype for master params. | +| `exp_avg_dtype` | `fp32` | Optimizer first moment dtype (`fp32`, `fp16`, `fp8`). | +| `exp_avg_sq_dtype` | `fp32` | Optimizer second moment dtype. | + +--- + +## 5. Parallelism and distribution + +*Sources: `trainer_base.yaml` (distributed runtime) and `primus/configs/models/megatron/language_model.yaml` (model-parallel sizes and TP communication).* + +### 5.1 Data / distributed runtime (trainer) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `overlap_p2p_comm` | `true` | Overlap pipeline P2P with compute. | +| `distributed_backend` | `nccl` | Process-group backend (`nccl`, `gloo`, …). | +| `distributed_timeout_minutes` | `10` (`60` in `pre_trainer.yaml`) | Collective timeout. | +| `defer_embedding_wgrad_compute` | `false` | Defer embedding weight gradients. | +| `wgrad_deferral_limit` | `0` | Max deferred embedding wgrad steps. | +| `align_grad_reduce` | `true` | Align gradient reductions for efficiency. | +| `ddp_num_buckets` | `null` | Number of DDP buckets. | +| `ddp_bucket_size` | `null` | DDP bucket size in elements. | +| `ddp_pad_buckets_for_high_nccl_busbw` | `false` | Pad buckets for NCCL bus bandwidth. | +| `ddp_average_in_collective` | `false` | Average inside collective vs outside. | +| `overlap_grad_reduce` | `false` | Overlap gradient all-reduce with backward. | +| `overlap_param_gather` | `false` | Overlap param all-gather (distributed optimizer). | +| `overlap_param_gather_with_optimizer_step` | `false` | Overlap param gather with optimizer step. | +| `align_param_gather` | `true` | Align param gather for distributed optimizer. | +| `scatter_gather_tensors_in_pipeline` | `true` | Scatter/gather tensors across PP ranks. | +| `use_ring_exchange_p2p` | `false` | Ring-exchange P2P for PP. | +| `local_rank` | `null` | Local rank override (normally from launcher). | +| `lazy_mpu_init` | `null` | Defer Megatron parallel state init. | +| `account_for_embedding_in_pipeline_split` | `false` | Account for embedding in PP partition. | +| `account_for_loss_in_pipeline_split` | `false` | Account for loss partition in PP. | +| `empty_unused_memory_level` | `0` | Aggressiveness of `torch.cuda.empty_cache`. | +| `standalone_embedding_stage` | `false` | Dedicated PP stage for embeddings. | +| `use_distributed_optimizer` | `false` (`true` in `pre_trainer.yaml`) | Shard optimizer state across data parallel. | +| `use_sharp` | `false` | Use SHARP for collectives when available. | +| `sharp_enabled_group` | `null` | Which group SHARP applies to (`dp`, `dp_replica`). | +| `use_custom_fsdp` | `false` | Custom FSDP integration path. | +| `use_megatron_fsdp` | `false` | Megatron FSDP path. | +| `init_model_with_meta_device` | `false` | Build model on `meta` device first. | +| `data_parallel_sharding_strategy` | `no_shard` | FSDP / ZeRO style sharding (`no_shard`, `optim`, …). | +| `gradient_reduce_div_fusion` | `true` | Fuse division into reduce-scatter. | +| `suggested_communication_unit_size` | `400000000` | Suggested communication chunk size. | +| `keep_fp8_transpose_cache_when_using_custom_fsdp` | `false` | Keep FP8 transpose cache with custom FSDP. | +| `num_distributed_optimizer_instances` | `1` | Sharded optimizer instances per rank group. | +| `use_torch_fsdp2` | `false` | Use PyTorch FSDP2 integration. | +| `nccl_communicator_config_path` | `null` | JSON config for NCCL communicators. | +| `use_tp_pp_dp_mapping` | `false` | Custom TP/PP/DP process mapping. | +| `replication` | `false` | Data replication mode for certain schedules. | +| `replication_jump` | `null` | Stride between replicated ranks. | +| `replication_factor` | `null` | Replication factor. | +| `deterministic_mode` | `false` | Prefer deterministic algorithms (slower). | +| `check_weight_hash_across_dp_replicas_interval` | `null` | Periodically hash weights across DP replicas for debugging. | +| `overlap_moe_expert_parallel_comm` | `false` | Overlap MoE expert-parallel communication. | +| `decoder_pipeline_manual_split_list` | `null` | *Primus:* manual PP split points for decoder (list of ints). | +| `patch_moe_overlap` | `false` | *Primus:* patch MoE compute/comm overlap. | + +### 5.2 Model parallelism (model preset) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `model_parallel_size` | `null` | Legacy combined MP size override. | +| `tensor_model_parallel_size` | `1` | Tensor parallelism degree (intra-layer split). | +| `encoder_tensor_model_parallel_size` | `0` | Encoder TP size when encoder/decoder differ. | +| `pipeline_model_parallel_size` | `1` | Pipeline parallelism stages. | +| `pipeline_model_parallel_layout` | `null` | Optional explicit PP layout string. | +| `pipeline_model_parallel_comm_backend` | `null` | `nccl` or `ucc` for PP collectives. | +| `encoder_pipeline_model_parallel_size` | `0` | Encoder PP stages (encoder–decoder models). | +| `pipeline_model_parallel_split_rank` | `null` | Rank where encoder/decoder split. | +| `decoder_first_pipeline_num_layers` | `null` | Layers on first decoder PP stage. | +| `decoder_last_pipeline_num_layers` | `null` | Layers on last decoder PP stage. | +| `virtual_pipeline_model_parallel_size` | `null` | Virtual PP (interleaved) depth. | +| `num_layers_per_virtual_pipeline_stage` | `null` | Layers per virtual stage. | +| `num_virtual_stages_per_pipeline_rank` | `null` | Virtual stages per physical PP rank. | +| `microbatch_group_size_per_vp_stage` | `null` | Microbatch grouping for interleaved PP. | +| `sequence_parallel` | `true` | Sequence parallelism when TP > 1. | +| `context_parallel_size` | `1` | Context (sequence) parallelism degree. | +| `cp_comm_type` | `p2p` | Context-parallel comm pattern (`p2p`, `a2a`, `allgather`, `a2a+p2p`). | +| `hierarchical_context_parallel_sizes` | `null` | Hierarchical CP group sizes. | +| `expert_model_parallel_size` | `1` | Expert parallelism for MoE. | +| `expert_tensor_parallel_size` | `null` | Expert tensor-parallel degree. | +| `high_priority_stream_groups` | `[]` | Named groups that get high-priority CUDA streams. | + +### 5.3 Tensor-parallel communication overlap (model) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `async_tensor_model_parallel_allreduce` | `true` | Async TP all-reduces for column-parallel layers. | +| `tp_comm_overlap` | `false` | Enable TP communication overlap planner. | +| `tp_comm_overlap_cfg` | `null` | Extra JSON / path for overlap configuration. | +| `tp_comm_overlap_ag` | `true` | Overlap all-gather in TP backward. | +| `tp_comm_overlap_rs` | `true` | Overlap reduce-scatter in TP backward. | +| `tp_comm_overlap_rs_dgrad` | `false` | Overlap RS for data-grad path. | +| `tp_comm_split_ag` | `true` | Split all-gather for overlap. | +| `tp_comm_split_rs` | `true` | Split reduce-scatter for overlap. | +| `tp_comm_bulk_wgrad` | `true` | Bulk weight-gradient path for TP comm. | +| `tp_comm_bulk_dgrad` | `true` | Bulk data-gradient path for TP comm. | +| `barrier_with_L1_time` | `true` | Barrier using L1 timing hooks for TP comm profiling. | +| `tp_comm_bootstrap_backend` | `nccl` | Backend used to bootstrap TP communicators. | + +--- + +## 6. Checkpointing + +*Source: `trainer_base.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `save` | `null` | Path prefix / pattern for checkpoints to write. | +| `save_interval` | `20000` (`1000` in `pre_trainer.yaml`) | Save every N iterations. | +| `save_retain_interval` | `null` | Retain checkpoints at this interval. | +| `no_save_optim` | `null` | Skip optimizer state in checkpoints when truthy. | +| `no_save_rng` | `null` | Skip RNG state in checkpoints when truthy. | +| `load` | `null` | Checkpoint path to load. | +| `load_main_params_from_ckpt` | `false` | Load only main parameters. | +| `no_load_optim` | `null` | Skip loading optimizer state. | +| `no_load_rng` | `null` | Skip loading RNG state. | +| `finetune` | `false` (`true` in `pre_trainer.yaml`) | Finetune mode (do not require full optimizer match). | +| `use_checkpoint_args` | `false` | When `true`, restore training args from checkpoint metadata. | +| `use_mp_args_from_checkpoint_args` | `false` | Restore model-parallel args from checkpoint. | +| `use_tokenizer_model_from_checkpoint_args` | `true` | Restore tokenizer path from checkpoint args. | +| `exit_on_missing_checkpoint` | `true` | Fail if `load` is set but checkpoint is missing. | +| `non_persistent_save_interval` | `null` | Ephemeral checkpoint interval. | +| `non_persistent_ckpt_type` | `null` | `global`, `local`, `in_memory`, or `null`. | +| `non_persistent_global_ckpt_dir` | `null` | Directory for non-persistent global checkpoints. | +| `non_persistent_local_ckpt_dir` | `null` | Directory for non-persistent local checkpoints. | +| `non_persistent_local_ckpt_algo` | `"fully_parallel"` | `fully_parallel` or `atomic`. | +| `pretrained_checkpoint` | `null` | Load weights from a pretrained checkpoint path. | +| `ckpt_step` | `null` | Specific step to load within a distributed checkpoint. | +| `use_dist_ckpt_deprecated` | `false` | Use deprecated distributed checkpoint format. | +| `use_persistent_ckpt_worker` | `false` | Background worker for checkpoint IO. | +| `auto_detect_ckpt_format` | `false` | Infer checkpoint format automatically. | +| `dist_ckpt_format_deprecated` | `null` | Legacy format hint. | +| `ckpt_format` | `torch_dist` | `torch`, `torch_dist`, or `zarr`. | +| `ckpt_convert_format` | `null` | Target format for one-shot conversion. | +| `ckpt_convert_save` | `null` | Output path for conversion. | +| `ckpt_convert_update_legacy_dist_opt_format` | `false` | Update legacy distributed-optimizer layout when converting. | +| `ckpt_fully_parallel_save_deprecated` | `false` | Deprecated fully-parallel save toggle. | +| `ckpt_fully_parallel_save` | `true` | Save shards in parallel across ranks. | +| `async_save` | `null` | Async checkpoint save (`null` = framework default). | +| `ckpt_fully_parallel_load` | `false` | Load shards in parallel. | +| `ckpt_assume_constant_structure` | `false` | Assume identical layer structure across ranks. | +| `dist_ckpt_strictness` | `assume_ok_unexpected` | How to handle unexpected keys in distributed ckpt. | +| `dist_ckpt_save_pre_mcore_014` | `null` | Compatibility flag for older Megatron-Core checkpoints. | +| `dist_ckpt_optim_fully_reshardable` | `null` | Optimizer state fully reshardable layout. | +| `auto_continue_train` | `false` | *Primus:* resume from latest checkpoint in the save directory when enabled. | +| `disable_last_saving` | `false` | *Primus:* skip writing the final checkpoint at shutdown. | + +--- + +## 7. Data + +*Source: `trainer_base.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `data_path` | `null` | Single blended dataset path / list. | +| `data_sharding` | `true` | Shard data across ranks. | +| `split` | `"99,1,0"` (`null` in `pre_trainer.yaml`) | Train/valid/test split ratios as comma string. | +| `train_data_path` | `null` | Training data blend. | +| `valid_data_path` | `null` | Validation data blend. | +| `test_data_path` | `null` | Test data blend. | +| `data_args_path` | `null` | External JSON/YAML of dataset arguments. | +| `per_split_data_args_path` | `null` | Per-split dataset args file. | +| `data_cache_path` | `null` | On-disk cache for indexed datasets. | +| `mock_data` | `false` | Use synthetic data (no real files). | +| `merge_file` | `null` | Merge file for blended datasets. | +| `seq_length` | `4096` (`1024` in `pre_trainer.yaml`) | Training sequence length. | +| `encoder_seq_length` | `null` | Encoder sequence length (encoder–decoder). | +| `decoder_seq_length` | `null` | Decoder sequence length. | +| `retriever_seq_length` | `256` | Sequence length for retriever models. | +| `sample_rate` | `1.0` | Sampling rate for dataset blending. | +| `mask_prob` | `0.15` | MLM mask probability. | +| `short_seq_prob` | `0.1` | Probability of shorter sequences in BERT-style data. | +| `num_workers` | `8` | DataLoader worker processes per rank. | +| `reset_position_ids` | `false` | Reset position IDs at document boundaries. | +| `reset_attention_mask` | `false` | Reset attention mask at boundaries. | +| `eod_mask_loss` | `false` | Mask loss at end-of-document tokens. | +| `dataloader_type` | `null` (`cyclic` in `pre_trainer.yaml`) | Dataloader implementation (`single`, `cyclic`, `external`, …). | +| `mmap_bin_files` | `true` | Memory-map `.bin` index files when supported. | +| `create_attention_mask_in_dataloader` | `true` | Build attention masks in the dataloader. | +| `num_dataset_builder_threads` | `1` | Threads to build dataset indices. | + +--- + +## 8. Recomputation (activation checkpointing) + +*Sources: `trainer_base.yaml` and `primus_megatron_module.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `recompute_activations` | `false` | Enable activation recomputation globally. | +| `recompute_granularity` | `null` | `full` or `selective` checkpointing. | +| `recompute_method` | `null` | `uniform` or `block` selective recomputation. | +| `recompute_num_layers` | `null` | Layers to recompute per block / schedule. | +| `recompute_layer_ids` | `null` | *Primus:* explicit **global** layer indices to recompute (`0 … num_layers-1`). | +| `distribute_saved_activations` | `false` | Distribute saved activations across TP/PP for memory balance. | +| `checkpoint_activations` | `false` | Deprecated alias for activation checkpointing. | +| `moe_layer_recompute` | `false` | Recompute MoE layer activations (model preset). | + +--- + +## 9. Logging and profiling + +*Sources: `trainer_base.yaml` and `primus_megatron_module.yaml`.* + +### 9.1 Logging + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `log_avg_skip_iterations` | `2` | Skip first N iterations for throughput averaging. | +| `log_avg_reset_interval` | `10` | Reset moving averages periodically. | +| `log_params_norm` | `false` | Log L2 norms of parameters. | +| `log_num_zeros_in_grad` | `false` | Log fraction of zero gradients. | +| `log_throughput` | `false` (`true` in `pre_trainer.yaml`) | Log tokens/sec and timing. | +| `log_progress` | `false` | Verbose progress logging. | +| `timing_log_level` | `0` | Verbosity for timing logs. | +| `timing_log_option` | `minmax` | Aggregate style for timing (`minmax`, `all`, …). | +| `tensorboard_log_interval` | `1` | Steps between TensorBoard scalars. | +| `tensorboard_queue_size` | `1000` | TensorBoard event queue size. | +| `log_timers_to_tensorboard` | `false` (`true` in `pre_trainer.yaml`) | Write timer stats to TensorBoard. | +| `log_batch_size_to_tensorboard` | `false` (`true` in `pre_trainer.yaml`) | Log batch size. | +| `log_learning_rate_to_tensorboard` | `true` | Log LR. | +| `log_validation_ppl_to_tensorboard` | `false` | Log validation perplexity. | +| `log_memory_to_tensorboard` | `false` | Log memory usage. | +| `log_world_size_to_tensorboard` | `false` | Log distributed world size. | +| `log_loss_scale_to_tensorboard` | `true` | Log FP16/FP8 loss scale. | +| `wandb_project` | `null` | Weights & Biases project name. | +| `wandb_exp_name` | `null` | W&B run name. | +| `wandb_save_dir` | `null` | W&B local directory. | +| `wandb_entity` | `null` | W&B entity / team. | +| `enable_one_logger` | `true` | Enable NVIDIA OneLogger integration. | +| `one_logger_project` | `megatron-lm` | OneLogger project string. | +| `one_logger_run_name` | `null` | OneLogger run name. | +| `log_interval` | `100` (`1` in `pre_trainer.yaml`) | Console log interval in iterations. | +| `tensorboard_dir` | `null` | TensorBoard output directory. | +| `logging_level` | `null` | Python logging level override. | +| `config_logger_dir` | `""` | Directory for dumped config logs. | +| `one_logger_async` | `false` | Async OneLogger flushing. | +| `app_tag_run_name` | `null` | Application tag for telemetry. | +| `app_tag_run_version` | `0.0.0` | Application tag version. | +| `disable_tensorboard` | `true` | *Primus:* disable TensorBoard integration in Primus-wrapped runs. | +| `disable_wandb` | `true` | *Primus:* disable W&B. | +| `disable_mlflow` | `true` | *Primus:* disable MLflow. | +| `mlflow_run_name` | `null` | *Primus:* MLflow run name. | +| `mlflow_experiment_name` | `null` | *Primus:* MLflow experiment name. | +| `use_rocm_mem_info` | `false` | *Primus:* collect ROCm memory info via `rocm-smi` every step when `true`. | +| `use_rocm_mem_info_iters` | `[1, 2]` | *Primus:* iterations at which to log memory if `use_rocm_mem_info` is `false`. | + +### 9.2 Profiling + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `profile` | `false` | Enable lightweight Nsight / CUDA profiling hooks. | +| `use_pytorch_profiler` | `false` | Enable `torch.profiler` regions. | +| `profile_ranks` | `[0]` | Ranks to profile. | +| `profile_step_start` | `10` | First step to profile. | +| `profile_step_end` | `12` | Last step to profile. | +| `iterations_to_skip` | `null` | Skip listed iterations in profiling. | +| `result_rejected_tracker_filename` | `null` | Log rejected samples to this file. | +| `enable_gloo_process_groups` | `true` | Create auxiliary Gloo groups for CPU-side ops. | +| `record_memory_history` | `false` | Record CUDA memory history (debug). | +| `memory_snapshot_path` | `snapshot.pickle` | Path for memory snapshot dumps. | +| `disable_profiler_activity_cpu` | `false` | *Primus:* omit CPU activities from profiler traces. | +| `torch_profiler_record_shapes` | `true` | *Primus:* record tensor shapes in PyTorch profiler. | +| `torch_profiler_with_stack` | `true` | *Primus:* capture Python stacks in profiler. | +| `torch_profiler_use_gzip` | `false` | *Primus:* gzip profiler outputs. | + +--- + +## 10. Model architecture + +*Sources: `primus/configs/models/megatron/language_model.yaml` and `primus/configs/models/megatron/primus_megatron_model.yaml`.* + +### 10.1 Core architecture + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `use_legacy_models` | `false` | Use legacy Megatron model code paths. | +| `deprecated_use_mcore_models` | `false` | Deprecated flag for Megatron-Core models; prefer current `transformer_impl` + stack. | +| `model_type` | `gpt` | `gpt` or `mamba` family. | +| `num_layers` | `24` | Transformer layers (decoder or unified stack). | +| `encoder_num_layers` | `null` | Encoder depth (encoder–decoder). | +| `decoder_num_layers` | `null` | Decoder depth. | +| `hidden_size` | `1024` | Hidden / model width. | +| `num_attention_heads` | `16` | Attention heads. | +| `attention_backend` | `auto` | Attention kernel backend selection. | +| `group_query_attention` | `false` | Enable grouped-query attention (GQA). | +| `qk_layernorm` | `false` | LayerNorm on Q/K projections. | +| `qk_l2_norm` | `false` | L2-normalize Q/K vectors. | +| `num_query_groups` | `null` | Number of query groups for GQA; `null` means MHA. | +| `add_position_embedding` | `false` | Add absolute position embeddings (non-RoPE stacks). | +| `position_embedding_type` | `learned_absolute` | Position embedding style. | +| `max_position_embeddings` | `null` | Maximum sequence positions (context length cap). | +| `original_max_position_embeddings` | `null` | Original pretrained length for interpolation / scaling. | +| `untie_embeddings_and_output_weights` | `true` | Separate input embedding and LM head weights. | +| `ffn_hidden_size` | `null` | FFN hidden size; `null` often defaults via `hidden_size` heuristics. | +| `kv_channels` | `null` | Per-head KV channels override. | +| `hidden_dropout` | `0.1` | Dropout on residual / hidden states. | +| `attention_dropout` | `0.1` | Attention dropout. | +| `fp32_residual_connection` | `false` | Accumulate residuals in FP32. | +| `apply_residual_connection_post_layernorm` | `false` | Apply residual after (vs before) norm where supported. | +| `add_bias_linear` | `false` | Biases in linear / column-parallel layers. | +| `add_qkv_bias` | `false` | Biases in QKV projections. | +| `swiglu` | `true` | SwiGLU activation in FFN. | +| `quick_geglu` | `false` | Faster GeGLU path. | +| `openai_gelu` | `false` | OpenAI GELU variant. | +| `squared_relu` | `false` | Squared ReLU activation. | +| `rotary_base` | `10000` | RoPE base frequency. | +| `rotary_percent` | `1.0` | Fraction of head dim spanned by RoPE. | +| `rotary_interleaved` | `false` | Interleaved RoPE layout. | +| `rotary_seq_len_interpolation_factor` | `null` | Positional interpolation factor for long contexts. | +| `use_rotary_position_embeddings` | `null` | Force RoPE on/off; `null` follows model type. | +| `use_rope_scaling` | `false` | Enable LLaMA-style rope scaling. | +| `rope_scaling_factor` | `8.0` | Scaling factor for extended contexts (LLaMA-3 style). | +| `transformer_impl` | `transformer_engine` | Backend library (`transformer_engine`, `local`, …). | +| `rope_type` | `null` | `rope` or `yarn` style extensions. | +| `norm_epsilon` | `1.0e-05` | LayerNorm / RMSNorm epsilon. | +| `normalization` | `"LayerNorm"` | Norm type (`LayerNorm`, `RMSNorm` with TE, …). | +| `apply_layernorm_1p` | `false` | LayerNorm with +1 offset trick. | +| `clone_scatter_output_in_embedding` | `true` | Clone embedding scatter for autograd safety. | +| `perform_initialization` | `true` | Run weight initialization. | +| `use_cpu_initialization` | `null` | Initialize on CPU then move to GPU. | +| `use_te_activation_func` | `false` | Use Transformer Engine activation kernels. | +| `gradient_accumulation_fusion` | `true` | Fuse gradient accumulation kernels. | +| `delay_wgrad_compute` | `false` | Delay weight-gradient computation for scheduling. | + +### 10.2 Tokenizer and vocabulary + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `tokenizer_type` | `null` | Tokenizer class name (`GPT2BPETokenizer`, `HuggingFaceTokenizer`, …). | +| `tokenizer_model` | `null` | Path to tokenizer model / vocabulary file. | +| `vocab_size` | `null` | Vocabulary size (often inferred from tokenizer). | +| `vocab_file` | `null` | Vocabulary file path for BPE/WP tokenizers. | +| `vocab_extra_ids` | `0` | Extra reserved token slots. | +| `tiktoken_pattern` | `null` | Regex pattern for tiktoken. | +| `tiktoken_num_special_tokens` | `1000` | Special token count for tiktoken setup. | +| `tiktoken_special_tokens` | `null` | Serialized special tokens for tiktoken. | +| `legacy_tokenizer` | `false` | Legacy tokenizer behavior. | +| `trust_remote_code` | `false` | `trust_remote_code` for Hugging Face tokenizers. | + +### 10.3 Initialization and attention numerics + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `init_method_std` | `0.02` | Standard deviation for weight init. | +| `apply_query_key_layer_scaling` | `false` | Scale Q/K by layer index (deprecated GPT-3 trick). | +| `attention_softmax_in_fp32` | `false` | Force softmax in FP32. | + +### 10.4 Kernel fusion flags + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `bias_gelu_fusion` | `true` | Fuse bias + GELU. | +| `cross_entropy_loss_fusion` | `false` | Fused cross-entropy + softmax. | +| `cross_entropy_fusion_impl` | `"native"` | `native` or `te` fused CE. | +| `bias_swiglu_fusion` | `true` | Fuse bias + SwiGLU. | +| `masked_softmax_fusion` | `true` | Fused masked softmax. | +| `no_persist_layer_norm` | `false` | Non-persistent LayerNorm mode in TE. | +| `bias_dropout_fusion` | `true` | Fuse bias + dropout. | +| `apply_rope_fusion` | `true` | Fused RoPE kernels. | + +### 10.5 Multi-latent attention (MLA) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `multi_latent_attention` | `false` | Enable MLA blocks instead of standard MHA. | +| `q_lora_rank` | `null` | Low-rank query projection rank. | +| `kv_lora_rank` | `32` | Low-rank KV compression rank. | +| `qk_head_dim` | `128` | Q/K head dimension for MLA. | +| `qk_pos_emb_head_dim` | `64` | Positional head dimension for MLA. | +| `v_head_dim` | `128` | Value head dimension for MLA. | +| `rotary_scaling_factor` | `1.0` | RoPE scaling inside MLA (distinct from `rope_scaling_factor` above). | +| `mscale` | `1.0` | Yarn / scaling m-factor. | +| `mscale_all_dim` | `1.0` | Yarn scaling on all dims. | + +### 10.6 Mixture-of-experts (MoE) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `num_experts` | `null` | Experts per MoE layer; `null` means dense model. | +| `moe_layer_freq` | `1` | Every Nth layer is MoE (1 = every layer). | +| `moe_ffn_hidden_size` | `null` | Expert FFN hidden size. | +| `moe_shared_expert_overlap` | `false` | Shared expert overlaps routing. | +| `moe_shared_expert_intermediate_size` | `null` | Shared expert FFN size. | +| `moe_grouped_gemm` | `false` | Grouped GEMM for experts. | +| `moe_router_load_balancing_type` | `"aux_loss"` | Router balancing (`aux_loss`, `seq_aux_loss`, `sinkhorn`, `none`). | +| `moe_router_dtype` | `null` | Router activation dtype (`fp32`, `fp64`). | +| `moe_router_score_function` | `softmax` | `softmax` or `sigmoid` routing scores. | +| `moe_router_topk` | `2` | Experts to select per token. | +| `moe_router_pre_softmax` | `false` | Apply softmax before top-k. | +| `moe_router_num_groups` | `null` | Group-limited routing: number of expert groups. | +| `moe_router_group_topk` | `null` | Groups to pick before top-k inside groups. | +| `moe_router_topk_scaling_factor` | `null` | Scaling for routing logits. | +| `moe_router_enable_expert_bias` | `false` | Learnable per-expert bias. | +| `moe_router_bias_update_rate` | `1.0e-03` | Update rate for expert bias. | +| `moe_use_legacy_grouped_gemm` | `false` | Legacy grouped GEMM path. | +| `moe_aux_loss_coeff` | `0.0` | Auxiliary load-balancing loss weight. | +| `moe_z_loss_coeff` | `null` | Router z-loss coefficient. | +| `moe_input_jitter_eps` | `null` | Input jitter for router stability. | +| `moe_token_dispatcher_type` | `allgather` | Token dispatch algorithm (`allgather`, `alltoall`, `flex`, `alltoall_seq`). | +| `moe_enable_deepep` | `false` | DeepEP-style expert parallelism. | +| `moe_per_layer_logging` | `false` | Per-layer MoE statistics logging. | +| `moe_expert_capacity_factor` | `null` | Capacity factor for token dropping / padding. | +| `moe_pad_expert_input_to_capacity` | `false` | Pad expert batches to capacity. | +| `moe_token_drop_policy` | `probs` | Token dropping policy when over capacity. | +| `moe_extended_tp` | `false` | Extended tensor-parallel for experts. | +| `moe_use_upcycling` | `false` | Expert upcycling initialization. | +| `moe_permute_fusion` | `false` | Fuse token permutation for MoE. | +| `disable_primus_topk_router` | `false` | *Primus:* disable Primus top-k router patch. | +| `moe_router_force_load_balancing` | `false` | *Primus:* force load-balanced routing. | +| `use_deprecated_20241209_moe_layer` | `false` | *Primus:* legacy MoE layer implementation. | +| `moe_router_force_load_balancing_type` | `even` | *Primus:* Control the force load balancing type for the MoE router. Choices: even, uniform. | + + +### 10.7 Logit softcapping (Primus / Grok-style) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `final_logit_softcapping` | `null` | Softcap value for final logits; `null` disables. | +| `attn_logit_softcapping` | `null` | Softcap for attention logits. | +| `router_logit_softcapping` | `null` | Softcap for MoE router logits. | + +--- + +## 11. Primus extensions + +### 11.1 Build and compile + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `disable_compile_dependencies` | `true` | *Primus:* avoid compiling dependency stacks in the trainer wrapper. | + +### 11.2 Primus-Turbo (`primus_turbo.yaml`) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `enable_primus_turbo` | `false` | Master switch for Primus-Turbo integrations. Many sub-features require this plus specific kernels. | +| `use_turbo_attention` | `false` | Turbo attention implementation. | +| `use_sink_attention` | `false` | GPT-OSS-style learned sink attention. | +| `sink_sliding_window` | `0` | Sliding-window size for sink attention (GPT-OSS uses `128`). | +| `sink_window_even_layers_only` | `true` | Apply the sliding window only to even layers (GPT-OSS pattern). | +| `use_turbo_gemm` | `false` | Active Turbo GEMM flag for Dense paths. | +| `use_turbo_parallel_linear` | *(removed)* | Removed—use `use_turbo_gemm`. Passing this key now raises an assertion error (`use_turbo_parallel_linear has been removed; please use use_turbo_gemm instead`). | +| `use_turbo_grouped_gemm` | `false` | Active Turbo grouped GEMM flag for MoE paths. | +| `use_turbo_grouped_mlp` | *(removed)* | Removed—use `use_turbo_grouped_gemm`. Passing this key now raises an assertion error (`use_turbo_grouped_mlp has been removed; please use use_turbo_grouped_gemm instead`). | +| `moe_use_fused_router_with_aux_score` | `false` | Fused MoE router with auxiliary scores. | +| `enable_turbo_attention_float8` | `false` | FP8 path inside Turbo attention (spacing in YAML is normalized to this key). | +| `use_turbo_deepep` | `false` | Turbo DeepEP expert communication. | +| `turbo_deepep_num_cu` | `32` | DeepEP compute units / channels. | +| `turbo_deepep_use_comm_stream` | `false` | Use a dedicated communication stream for DeepEP. | +| `turbo_sync_free_moe_stage` | `0` | Stage selector for sync-free MoE. | +| `use_turbo_fused_act_with_probs` | `false` | Fuse activation + probability tensors to remove redundant work. | +| `use_turbo_rms_norm` | `false` | Turbo RMSNorm kernels. | + +### 11.3 Zero-bubble pipeline (`zero_bubble.yaml`) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `patch_zero_bubble` | `false` | Install Primus zero-bubble PP patches when `true`. | +| `debug_scheduler_table` | `false` | Print PP scheduler tables (also in `primus_pipeline.yaml`; last merge wins—defaults match). | +| `enable_zb_runtime` | `true` | Unified runtime for zero-bubble and related schedules. | +| `pre_communication_optimization` | `false` | Issue a tiny comm before real comm to tune overlap. | +| `zero_bubble_pipeline_timers_start_iter` | `100` | Start iter for auto-scheduler timers. | +| `zero_bubble_pipeline_timers_end_iter` | `110` | End iter for auto-scheduler timers. | +| `zero_bubble_max_pending_backward` | `auto` | Max pending backward ops (ZB1p vs ZB2p style); `auto` adapts. | +| `zero_bubble_adaptive_memory_limit_percentile` | `85` | GPU memory percentile cap for adaptive ZB. | +| `enable_optimizer_post_validation` | `false` | Post-optimizer validation step (needs FSDP path). | +| `enable_exactly_numeric_match` | `true` | Require bitwise match in post validation when enabled. | +| `enable_zero_bubble` | `true` | Enable zero-bubble schedule features in the ZB runtime. | +| `zero_bubble_v_schedule` | `false` | Zero-bubble “V” schedule without extra memory vs some baselines. | +| `zero_bubble_v_schedule_mem_setup` | `half` | Memory setup variant: `half`, `min`, or `zb`. | +| `enable_1f1b_v` | `false` | 1F1B-V schedule variant. | +| `allow_padding_num_layers` | `true` | Allow PP layer padding for divisibility. | +| `profile_memory_iter` | `-1` | Iteration to profile memory (`-1` disables). | +| `interleave_group_size` | `0` | Interleaved PP group size. | +| `offload_chunk_num` | `0` | Activation offload chunk count. | +| `offload_time` | `1.0` | Time budget for offload (scheduler hint). | +| `auto_offload_time` | `true` | Auto-tune offload timing. | +| `offload_overlap_sr` | `true` | Overlap save/resume in offload path. | +| `num_seq_splits` | `1` | Splits along sequence dimension for ZB. | +| `cpu_offload` | `false` | CPU offload of activations in ZB path. | + +### 11.4 Primus pipeline (`primus_pipeline.yaml`) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `patch_primus_pipeline` | `false` | Enable Primus pipeline scheduling patches. | +| `pp_algorithm` | `"1f1b-interleaved"` | Schedule name (`1f1b`, `1f1b-interleaved`, `zero-bubble`, `zero-bubble-heuristic`, `zbv-formatted`, `v-half`, `v-min`). | +| `communication_method` | `"async_p2p"` | `async_p2p` or `batch_p2p` PP transfers. | +| `offload` | `false` | Generic PP activation offload toggle in Primus pipeline. | +| `offload_ops` | `""` | Comma-separated offload targets (`attn` today; other ops listed in-file are not supported yet). | +| `pp_max_mem` | `null` | `zero-bubble-heuristic` only: max activation memory per stage (`null` = unlimited). | +| `pp_cost_f` | `null` | `zero-bubble-heuristic` only: forward cost per stage (scalar or list; `null` = default 1000). | +| `pp_cost_b` | `null` | `zero-bubble-heuristic` only: backward cost per stage (scalar or list; `null` = default 1000). | +| `pp_cost_w` | `null` | `zero-bubble-heuristic` only: weight-grad cost per stage (scalar or list; `null` = default 1000). | + +`pp_warmup` and `dump_pp_data` are *Primus* helpers defined in `primus_megatron_module.yaml` (not `primus_pipeline.yaml`): + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `pp_warmup` | `false` | *Primus:* warm-up PP stages to reduce first-iteration latency. | +| `dump_pp_data` | `false` | *Primus:* dump PP tensors for debugging. | + +--- + +## 12. Reinforcement learning and GRPO-related settings + +*Source: `trainer_base.yaml`. Names follow Megatron’s `grpo_*` / `rl_*` prefixes (there is no `rl_grpo` single flag in these presets).* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `perform_rl_step` | `false` | Run RL / preference optimization steps (GRPO / LangRL integration). | +| `rl_prompts_per_eval` | `32` | Prompts per RL evaluation pass. | +| `grpo_prompts_per_step` | `32` | GRPO prompts sampled per training step. | +| `grpo_group_size` | `2` | Samples per prompt group for GRPO. | +| `grpo_iterations` | `2` | Inner GRPO iterations. | +| `grpo_clamp_eps_lower` | `0.01` | PPO-style lower clip epsilon. | +| `grpo_clamp_eps_upper` | `0.01` | Upper clip epsilon. | +| `grpo_kl_beta` | `0.001` | KL penalty weight toward reference policy. | +| `grpo_entropy_term_weight` | `0.0` | Entropy bonus weight. | +| `grpo_filter_groups_with_same_reward` | `false` | Drop groups with identical rewards. | +| `grpo_default_temperature` | `1.0` | Default softmax temperature for rollouts. | +| `grpo_default_top_p` | `0` | Top-p sampling (`0` often means disabled / greedy—see Megatron RL docs). | +| `langrl_inference_server_type` | `inplace_megatron` | LangRL inference backend. | +| `langrl_inference_server_conversation_template` | `null` | Conversation template path / name. | +| `langrl_env_config` | `null` | Environment / task YAML for LangRL. | +| `rl_offload_optimizer_during_inference` | `false` | Offload optimizer to CPU during rollout inference. | +| `rl_offload_kv_cache_during_training` | `false` | Offload KV cache while training forward runs. | +| `rl_remove_kv_cache_during_training` | `false` | Drop KV cache between RL phases to save memory. | +| `rl_reset_cuda_graphs` | `false` | Reset CUDA graphs when switching RL phases. | +| `rl_partial_rollouts` | `false` | Partial sequence rollouts. | +| `rl_inference_logprobs_is_correction` | `false` | Interpret inference logprobs as IS correction term. | +| `rl_importance_sampling_truncation_coef` | `null` | Truncate importance ratios at this value. | +| `rl_calculate_intra_group_similarity` | `false` | Log similarity within GRPO groups. | + +--- + +## 13. Additional specialized parameters + +*Source: `trainer_base.yaml` (remaining domains).* + +### 13.1 Vision pretraining + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `vision_pretraining` | `false` | Enable vision backbone pretraining. | +| `vision_pretraining_type` | `classify` | Objective (`classify`, etc.). | +| `vision_backbone_type` | `vit` | Vision backbone family. | +| `swin_backbone_type` | `tiny` | Swin variant size. | +| `num_classes` | `1000` | Classification classes. | +| `img_h` | `224` | Image height. | +| `img_w` | `224` | Image width. | +| `num_channels` | `3` | Input channels. | +| `patch_dim` | `16` | ViT patch size. | +| `classes_fraction` | `1.0` | Fraction of classes used. | +| `data_per_class_fraction` | `1.0` | Fraction of data per class. | + +### 13.2 RETRO + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `retro_project_dir` | `null` | RETRO project directory with indices. | +| `retro_add_retriever` | `false` | Add frozen retriever tower. | +| `retro_cyclic_train_iters` | `null` | Cyclic iterator length. | +| `retro_encoder_layers` | `2` | Retriever encoder layers. | +| `retro_encoder_hidden_dropout` | `0.1` | Retriever dropout. | +| `retro_encoder_attention_dropout` | `0.1` | Retriever attention dropout. | +| `retro_num_neighbors` | `2` | Neighbors per query chunk. | +| `retro_num_retrieved_chunks` | `2` | Chunks concatenated per neighbor set. | +| `retro_attention_gate` | `1` | Gating between retrieval and LM. | +| `retro_verify_neighbor_count` | `true` | Assert neighbor counts for debugging. | + +### 13.3 DINO self-supervised + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `dino_local_img_size` | `96` | Local crop size. | +| `dino_local_crops_number` | `10` | Number of local crops. | +| `dino_head_hidden_size` | `2048` | Projection head width. | +| `dino_bottleneck_size` | `256` | Bottleneck dimension. | +| `dino_freeze_last_layer` | `1` | Freeze last layer epochs. | +| `dino_norm_last_layer` | `false` | Normalize last layer weights. | +| `dino_warmup_teacher_temp` | `0.04` | Teacher temperature warmup start. | +| `dino_teacher_temp` | `0.07` | Teacher temperature. | +| `dino_warmup_teacher_temp_epochs` | `30` | Epochs to warm teacher temperature. | + +### 13.4 Biencoder / ICT / retriever utilities + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `ict_head_size` | `null` | ICT projection head width. | +| `biencoder_projection_dim` | `0` | Biencoder shared projection dimension. | +| `biencoder_shared_query_context_model` | `false` | Share query/context encoders. | +| `ict_load` | `null` | ICT checkpoint path. | +| `bert_load` | `null` | BERT encoder checkpoint for biencoder. | +| `titles_data_path` | `null` | Titles file for ICT datasets. | +| `query_in_block_prob` | `0.1` | Probability of in-block queries. | +| `use_one_sent_docs` | `false` | Single-sentence pseudo documents. | +| `evidence_data_path` | `null` | Evidence passages for open-domain QA. | +| `retriever_report_topk_accuracies` | `[]` | k values for top-k accuracy logging. | +| `retriever_score_scaling` | `false` | Scale retriever scores. | +| `block_data_path` | `null` | Block JSON data for retrieval. | +| `embedding_path` | `null` | Precomputed embeddings path. | +| `indexer_batch_size` | `128` | Batch size when building ANN index. | +| `indexer_log_interval` | `1000` | Indexer progress log interval. | + +### 13.5 Straggler detection + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `log_straggler` | `false` | Log straggler diagnostics. | +| `disable_straggler_on_startup` | `false` | Skip straggler detection at startup. | +| `straggler_ctrlr_port` | `65535` | Controller port for straggler service. | +| `straggler_minmax_count` | `1` | Min/max samples for straggler stats. | + +### 13.6 Inference-oriented options + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `inference_batch_times_seqlen_threshold` | `-1` | Heuristic threshold tying batch and sequence length. | +| `inference_dynamic_batching` | `false` | Dynamic batching for inference server. | +| `inference_dynamic_batching_buffer_size_gb` | `40.0` | GPU buffer budget (GB). | +| `inference_dynamic_batching_buffer_guaranteed_fraction` | `0.2` | Minimum reserved fraction of buffer. | +| `inference_dynamic_batching_buffer_overflow_factor` | `null` | Overflow growth factor. | +| `inference_dynamic_batching_max_requests_override` | `null` | Hard cap on concurrent requests. | +| `inference_dynamic_batching_max_tokens_override` | `null` | Hard cap on tokens in flight. | +| `max_tokens_to_oom` | `12000` | Token limit guard before OOM abort. | +| `output_bert_embeddings` | `false` | Return BERT pooled embeddings. | +| `bert_embedder_type` | `megatron` | `megatron` or `huggingface` embedder. | +| `flash_decode` | `false` | Flash decode kernels for incremental generation. | +| `enable_cuda_graph` | `false` | Capture CUDA graphs for inference. | +| `cuda_graph_warmup_steps` | `3` | Warm-up steps before capturing graphs. | +| `external_cuda_graph` | `false` | External graph provider hooks. | +| `cuda_graph_scope` | `full` | Graph scope (`full` or `attn`). | +| `inference_max_requests` | `8` | Max concurrent requests. | +| `inference_max_seq_length` | `2560` | Max prefill + decode tokens per request. | + +### 13.7 Fault tolerance package and tooling + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `enable_ft_package` | `false` | NVIDIA fault-tolerance package hooks. | +| `calc_ft_timeouts` | `false` | Auto-calculate FT timeouts. | +| `run_workload_inspector_server` | `false` | Run workload inspector sidecar. | + +### 13.8 Heterogeneous layers and process resilience + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `heterogeneous_layers_config_path` | `null` | JSON describing variable layer widths/types per layer. | +| `heterogeneous_layers_config_encoded_json` | `null` | Inline base64/JSON blob for heterogeneous layers. | +| `inprocess_restart` | `false` | In-process restart for fault recovery experiments. | + +### 13.9 Experimental and rerun controls + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `enable_experimental` | `false` | Gate experimental Megatron features. | +| `error_injection_rate` | `0` | Fraction of iterations with injected errors (testing). | +| `error_injection_type` | `transient_error` | `correct_result`, `transient_error`, or `persistent_error`. | +| `rerun_mode` | `disabled` | `disabled`, `validate_results`, or `report_stats` for rerun harness. | + +--- + +### Related documentation + +- Megatron-LM argument definitions: [`megatron/training/arguments.py`](https://github.com/NVIDIA/Megatron-LM/blob/main/megatron/training/arguments.py) +- Primus Megatron presets: `primus/configs/modules/megatron/` +- Primus Megatron model presets: `primus/configs/models/megatron/` diff --git a/docs/03-configuration-reference/torchtitan-parameters.md b/docs/03-configuration-reference/torchtitan-parameters.md new file mode 100644 index 000000000..c838fa84a --- /dev/null +++ b/docs/03-configuration-reference/torchtitan-parameters.md @@ -0,0 +1,370 @@ +# TorchTitan backend configuration reference + +This page lists Primus preset keys and common TorchTitan `JobConfig` fields used when `framework: torchtitan`. Defaults are taken from the TorchTitan module preset (`pre_trainer.yaml`), its `extends` chain (`module_base.yaml`, `quantize.yaml`), and the example model preset `llama3_8B.yaml`. It is not a complete upstream TorchTitan `JobConfig` reference. + +**Where parameters live.** Provide overrides under `modules.pre_trainer.overrides:` in your experiment YAML. TorchTitan’s `JobConfig` is hierarchical: use **dot notation** for flat overrides, or nest YAML objects under `overrides`—both are equivalent when merged. + +**Example (flat dot paths):** + +```yaml +framework: torchtitan + +modules: + pre_trainer: + overrides: + training.steps: 20000 + training.global_batch_size: 512 + optimizer.lr: 0.00015 + parallelism.tensor_parallel_degree: 2 +``` + +**Example (nested YAML):** + +```yaml +modules: + pre_trainer: + overrides: + training: + steps: 20000 + global_batch_size: 512 + optimizer: + lr: 0.00015 + parallelism: + tensor_parallel_degree: 2 +``` + +**Presets.** + +- Module presets: `primus/configs/modules/torchtitan/` (main entry: `pre_trainer.yaml`). +- Model presets: `primus/configs/models/torchtitan/` (example: `llama3_8B.yaml`). + +**Mapping to TorchTitan.** Keys are translated into TorchTitan’s `JobConfig` via `TorchTitanJobConfigBuilder` (same nested structure as upstream TorchTitan). + +**Upstream reference.** TorchTitan repository and documentation: [https://github.com/pytorch/torchtitan](https://github.com/pytorch/torchtitan) (vendored as the `third_party/torchtitan` submodule). + +--- + +## 1. Base module parameters + +*Source: `primus/configs/modules/module_base.yaml` (merged before TorchTitan-specific keys; `pre_trainer.yaml` does not override `trainable`).* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `trainable` | `false` | Whether this module is active in training orchestration. (TorchTitan preset inherits `false` from `module_base.yaml`.) | +| `sink_level` | `null` | Structured logging sink level; `null` uses defaults. | +| `file_sink_level` | `DEBUG` | Minimum level for file logging. | +| `stderr_sink_level` | `INFO` | Minimum level for stderr logging. | + +--- + +## 2. Training (`training.*`) + +*Source: `primus/configs/modules/torchtitan/pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `training.mock_data` | `true` | Primus preset extension: use synthetic data instead of reading `dataset_path`. | +| `training.dataset` | `c4` | Dataset name key for TorchTitan dataset loaders. | +| `training.dataset_path` | `null` | Filesystem or remote path to dataset assets. | +| `training.enable_cpu_offload` | `false` | Offload optimizer or activations to CPU when supported. | +| `training.gc_debug` | `false` | Extra garbage-collection diagnostics. | +| `training.gc_freq` | `50` | Run Python GC every N steps when enabled. | +| `training.global_batch_size` | `-1` | Global batch size across all ranks (`-1` often means “auto” / unset in TorchTitan). | +| `training.local_batch_size` | `8` | Per-rank microbatch size before gradient accumulation. | +| `training.max_norm` | `1.0` | Gradient clipping max norm (global). | +| `training.mixed_precision_param` | `bfloat16` | Parameter dtype for mixed precision (`bfloat16`, `float16`, etc.). | +| `training.mixed_precision_reduce` | `float32` | Dtype for reduction / gradient accumulation. | +| `training.seq_len` | `2048` | Sequence length per sample. | +| `training.steps` | `10000` | Total optimizer steps. | + +--- + +## 3. Optimizer (`optimizer.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `optimizer.name` | `AdamW` | Optimizer class (`AdamW`, `Adam`, …). | +| `optimizer.lr` | `0.0008` | Base learning rate. | +| `optimizer.beta1` | `0.9` | First moment decay. | +| `optimizer.beta2` | `0.95` | Second moment decay. | +| `optimizer.eps` | `1.0e-08` | Numerical stability term. | +| `optimizer.weight_decay` | `0.1` | Weight decay coefficient. | +| `optimizer.implementation` | `fused` | Kernel implementation (`fused`, `foreach`, …). | +| `optimizer.early_step_in_backward` | `false` | Experimental: step optimizer during backward when supported. | + +--- + +## 4. Learning rate scheduler (`lr_scheduler.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `lr_scheduler.decay_ratio` | `null` | Fraction of training at the end used for decay; `null` uses framework default. | +| `lr_scheduler.decay_type` | `linear` | LR decay curve (`linear`, `cosine`, etc.). | +| `lr_scheduler.min_lr_factor` | `0.0` | LR floor as a fraction of base LR after decay. | +| `lr_scheduler.warmup_steps` | `200` | Linear warmup steps before decay. | + +--- + +## 5. Parallelism (`parallelism.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `parallelism.tensor_parallel_degree` | `1` | Tensor parallelism (intra-layer) degree. | +| `parallelism.pipeline_parallel_degree` | `1` | Pipeline parallelism stages. | +| `parallelism.pipeline_parallel_microbatch_size` | `1` | Microbatches per pipeline round. | +| `parallelism.pipeline_parallel_schedule` | `1F1B` | Pipeline schedule name (`1F1B`, `GPipe`, …). | +| `parallelism.pipeline_parallel_schedule_csv` | `''` | Optional CSV schedule definition. | +| `parallelism.pipeline_parallel_layers_per_stage` | `null` | Layers per stage when auto-balanced. | +| `parallelism.pipeline_parallel_first_stage_less_layers` | `1` | Fewer layers on first PP stage (for imbalance). | +| `parallelism.pipeline_parallel_last_stage_less_layers` | `1` | Fewer layers on last PP stage. | +| `parallelism.data_parallel_shard_degree` | `-1` | FSDP / shard degree (`-1` = auto). | +| `parallelism.data_parallel_replicate_degree` | `1` | Replicated data-parallel groups. | +| `parallelism.expert_parallel_degree` | `1` | Expert parallelism for MoE models. | +| `parallelism.expert_tensor_parallel_degree` | `1` | Tensor parallelism inside experts. | +| `parallelism.context_parallel_degree` | `1` | Context (sequence) parallelism degree. | +| `parallelism.context_parallel_rotate_method` | `allgather` | Communication pattern for context parallel. | +| `parallelism.disable_loss_parallel` | `false` | Disable loss parallel layout when TP is used. | +| `parallelism.enable_async_tensor_parallel` | `false` | Overlap TP collectives with compute. | +| `parallelism.fsdp_reshard_after_forward` | `default` | FSDP reshard policy (`default`, `always`, `never`). | +| `parallelism.module_fqns_per_model_part` | `null` | Map of pipeline stage → module FQNs for multi-part models. | + +--- + +## 6. Checkpoint (`checkpoint.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `checkpoint.enable` | `false` | Master switch for checkpointing. | +| `checkpoint.folder` | `checkpoint` | Output directory for checkpoints. | +| `checkpoint.interval` | `500` | Save every N steps. | +| `checkpoint.initial_load_path` | `null` | Path to load from at startup. | +| `checkpoint.initial_load_model_only` | `true` | Load weights only (skip optimizer/scheduler). | +| `checkpoint.initial_load_in_hf` | `false` | Load initial weights from Hugging Face format. | +| `checkpoint.last_save_model_only` | `true` | Final save stores weights only. | +| `checkpoint.last_save_in_hf` | `false` | Export final weights in Hugging Face format. | +| `checkpoint.export_dtype` | `float32` | Dtype for exported checkpoints. | +| `checkpoint.async_mode` | `disabled` | Async checkpoint (`disabled`, `async`, …). | +| `checkpoint.keep_latest_k` | `10` | Retain only the newest k checkpoints. | +| `checkpoint.load_step` | `-1` | Step index to load (`-1` = latest). | +| `checkpoint.exclude_from_loading` | `[]` | FQNs or keys to skip when loading. | +| `checkpoint.enable_first_step_checkpoint` | `false` | Save checkpoint at step 0 for debugging. | +| `checkpoint.create_seed_checkpoint` | `false` | Save a seed checkpoint before training starts. | + +--- + +## 7. Activation checkpoint (`activation_checkpoint.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `activation_checkpoint.mode` | `none` | Activation checkpointing mode (`none`, `selective`, `full`). | +| `activation_checkpoint.selective_ac_option` | `"2"` | Selective AC policy string (TorchTitan-specific). | +| `activation_checkpoint.per_op_sac_force_recompute_mm_shapes_by_fqns` | `["moe.router.gate"]` | FQNs that always recompute matmuls in selective AC. | +| `activation_checkpoint.early_stop` | `false` | Stop AC early in certain subgraphs. | + +--- + +## 8. Metrics and profiling + +### 8.1 Metrics (`metrics.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `metrics.disable_color_printing` | `false` | Disable ANSI colors in logs. | +| `metrics.enable_tensorboard` | `false` | Write TensorBoard scalars. | +| `metrics.enable_wandb` | `false` | Log to Weights & Biases. | +| `metrics.log_freq` | `10` | Steps between metric logs. | +| `metrics.save_for_all_ranks` | `false` | Save metric files per rank (not just rank 0). | +| `metrics.save_tb_folder` | `tb` | TensorBoard subdirectory / name. | + +### 8.2 Profiling (`profiling.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `profiling.enable_profiling` | `false` | Enable PyTorch profiler traces. | +| `profiling.enable_memory_snapshot` | `false` | Capture CUDA memory snapshots. | +| `profiling.profile_freq` | `10` | Steps between profiler activations. | +| `profiling.save_traces_folder` | `profile_traces` | Directory for profiler traces. | +| `profiling.save_memory_snapshot_folder` | `memory_snapshot` | Directory for memory snapshots. | + +--- + +## 9. Quantization (`quantize.*`) + +*Source: `primus/configs/modules/torchtitan/quantize.yaml` (merged into the module preset).* + +### 9.1 Linear FP8 (`quantize.linear.float8.*`) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `quantize.linear.float8.enable_fsdp_float8_all_gather` | `false` | FP8 all-gather for FSDP sharded params (recommended for tensorwise scaling). | +| `quantize.linear.float8.precompute_float8_dynamic_scale_for_fsdp` | `false` | Precompute dynamic scales for FSDP FP8. | +| `quantize.linear.float8.recipe_name` | `null` | Recipe (`tensorwise`, `rowwise`, `rowwise_with_gw_hp`); `null` disables. | +| `quantize.linear.float8.filter_fqns` | `[]` | Module FQNs to skip for FP8 training. | +| `quantize.linear.float8.emulate` | `false` | Emulate FP8 in FP32 (no FP8 HW); not compatible with `torch.compile`. | + +### 9.2 Linear MX (`quantize.linear.mx.*`) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `quantize.linear.mx.mxfp8_dim1_cast_kernel_choice` | `"triton"` | Kernel backend for MXFP8 dim-1 cast (`triton`, `cuda`, `torch`). | +| `quantize.linear.mx.recipe_name` | `"mxfp8_cublas"` | MX recipe name (see torchao `mx_formats`). | +| `quantize.linear.mx.filter_fqns` | `["output"]` | FQNs to skip; output layer skipped by default. | + +### 9.3 Grouped GEMM FP8 (`quantize.grouped_mm.float8.*`) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `quantize.grouped_mm.float8.fqns` | `[]` | MoE layer FQNs for FP8 grouped GEMM (prototype; may require torchao nightly). | + +### 9.4 Grouped GEMM MX (`quantize.grouped_mm.mx.*`) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `quantize.grouped_mm.mx.recipe_name` | `"mxfp8"` | MX recipe for grouped GEMMs. | +| `quantize.grouped_mm.mx.fqns` | `[]` | MoE module FQNs for MXFP8 grouped GEMM (prototype). | + +--- + +## 10. Compile (`compile.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `compile.enable` | `true` | Enable `torch.compile` on selected subsystems. | +| `compile.components` | `["model", "loss"]` | Which components to compile. | + +--- + +## 11. Communication and fault tolerance + +### 11.1 Communicator (`comm.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `comm.init_timeout_seconds` | `300` | Timeout for initial process-group setup. | +| `comm.train_timeout_seconds` | `100` | Timeout for training collectives. | +| `comm.trace_buf_size` | `20000` | Flight recorder buffer size for NCCL traces. | +| `comm.save_traces_folder` | `comm_traces` | Where to dump communication traces. | + +### 11.2 Fault tolerance (`fault_tolerance.*`) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `fault_tolerance.enable` | `false` | Enable fault-tolerant training hooks. | +| `fault_tolerance.process_group` | `gloo` | Backend for control-plane process group. | +| `fault_tolerance.process_group_timeout_ms` | `10000` | Control-plane timeout. | +| `fault_tolerance.replica_id` | `0` | Replica index in elastic setups. | +| `fault_tolerance.group_size` | `0` | Group size (0 = unset / default). | +| `fault_tolerance.min_replica_size` | `1` | Minimum replicas to continue. | +| `fault_tolerance.semi_sync_method` | `null` | Optional semi-synchronous strategy name. | + +### 11.3 Memory estimation (`memory_estimation.*`) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `memory_estimation.enable` | `false` | Run memory-estimation fake-mode passes. | +| `memory_estimation.disable_fake_mode` | `false` | Disable fake tensor mode inside estimation. | + +### 11.4 Experimental (`experimental.*`) + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `experimental.custom_import` | `""` | Optional Python module import path for custom extensions. | +| `experimental.custom_args_module` | `"primus.backends.torchtitan.primus_turbo_extensions.config_extension"` | Module providing extra `JobConfig` fields for Primus-Turbo. | + +--- + +## 12. Primus-Turbo (`primus_turbo.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `primus_turbo.enable_primus_turbo` | `true` | Master switch for Primus-Turbo integrations in TorchTitan. | +| `primus_turbo.enable_attention_float8` | `false` | FP8 attention path inside Turbo attention. | +| `primus_turbo.use_turbo_attention` | `true` | Use Turbo attention kernels. | +| `primus_turbo.use_classic_attention` | `false` | Fall back to classic attention implementation. | +| `primus_turbo.use_turbo_async_tp` | `true` | Async tensor-parallel communication in Turbo. | +| `primus_turbo.use_turbo_mx_linear` | `true` | MX linear layers via Turbo. | +| `primus_turbo.use_turbo_float8_linear` | `true` | FP8 linear layers via Turbo. | +| `primus_turbo.use_turbo_grouped_mm` | `false` | Turbo grouped GEMM for MoE (off by default). | +| `primus_turbo.use_moe_fp8` | `true` | FP8 paths for MoE experts when applicable. | +| `primus_turbo.enable_embedding_autocast` | `true` | Autocast policy around embeddings for Turbo. | + +--- + +## 13. Model (`model.*` and `job.*`) + +### 13.1 Model preset (`models.*` / `model.*`) + +*Example defaults from `primus/configs/models/torchtitan/llama3_8B.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `model.name` | `"llama3"` | Model family key for TorchTitan recipes. | +| `model.flavor` | `"8B"` | Size / variant within the family. | +| `model.hf_assets_path` | `"meta-llama/Meta-Llama-3-8B"` | Hugging Face Hub repo or local path for weights/tokenizer. | +| `model.converters` | `["primus_turbo"]` | Weight converter pipeline stages applied at load. | + +### 13.2 Job metadata (`job.*`) + +*Source: `llama3_8B.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `job.dump_folder` | `"./outputs"` | Root directory for logs, checkpoints, and exports. | +| `job.description` | `"Llama 3 8B training"` | Human-readable label for run metadata. | + +--- + +## 14. Validation (`validation.*`) + +*Source: `pre_trainer.yaml`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `validation.enable` | `false` | Run periodic validation loops. | +| `validation.dataset` | `c4_validation` | Validation dataset key. | +| `validation.dataset_path` | `null` | Filesystem path to validation data. | +| `validation.local_batch_size` | `8` | Per-rank validation batch size. | +| `validation.seq_len` | `2048` | Validation sequence length. | +| `validation.freq` | `10` | Run validation every N training steps. | +| `validation.steps` | `-1` | Max validation steps (`-1` = full pass / framework default). | + +--- + +## 15. Debug (`debug.*`) + +*Source: `pre_trainer.yaml`. Upstream added this section in TorchTitan v0.2.2; the keys previously lived under `training.*`.* + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `debug.deterministic` | `false` | Prefer deterministic algorithms (often slower). | +| `debug.deterministic_warn_only` | `false` | Warn instead of erroring when an op has no deterministic implementation. | +| `debug.moe_force_load_balance` | `false` | Round-robin tokens across MoE experts so every expert gets the same amount; debugging only. | +| `debug.seed` | `null` | RNG seed; `null` lets the framework choose. | + +--- + +### Related documentation + +- TorchTitan repository and documentation: [https://github.com/pytorch/torchtitan](https://github.com/pytorch/torchtitan) +- Primus TorchTitan presets: `primus/configs/modules/torchtitan/` +- Primus TorchTitan model presets: `primus/configs/models/torchtitan/` diff --git a/docs/04-technical-guides/README.md b/docs/04-technical-guides/README.md new file mode 100644 index 000000000..2f45c99c3 --- /dev/null +++ b/docs/04-technical-guides/README.md @@ -0,0 +1,24 @@ +# Technical guides + +Deep technical topics for advanced users. + +- [Parallelism strategies](parallelism-strategies.md): DP, TP, PP, SP, CP, EP, FSDP explained +- [Parallelism configuration](parallelism-configuration.md): per-backend parallelism setup and batch size relationships +- [Collective operations](collective-operations.md): NCCL/RCCL operations and their role in each parallelism strategy +- [Performance tuning](performance-tuning.md): HipBLASLt, Primus-Turbo, FP8, MoE optimization +- [MoE training deep-dive](moe-training.md): bottlenecks and Primus-Turbo optimizations for Mixture-of-Experts models +- [MegaMoE fused MoE layer](mega-moe.md): FlyDSL-based fused MoE layer for EP-only bf16 training, setup and reproduction +- [Data preparation](data-preparation.md): tokenization, data formats, mock data +- [Checkpoint management](checkpoint-management.md): formats, save/load, distributed checkpointing +- [Multi-node networking](multi-node-networking.md): InfiniBand, RoCE, AINIC configuration +- [Profiling and observability](profiling-and-observability.md): Torch profiler, TraceLens, memory snapshots, projection, pp_vis +- [Logging and experiment tracking](logging-and-experiment-tracking.md): TensorBoard, WandB, MLflow setup per backend +- [Fault tolerance and elastic training](fault-tolerance-and-elastic-training.md): graceful exit, auto-resume, in-process restart, torchft +- [Determinism and reproducibility](determinism-and-reproducibility.md): deterministic mode, seeds, trade-offs +- [Diffusion models](diffusion-models/README.md): Flux diffusion architecture, data pipeline, and FP8 / MXFP4 training +- [Hybrid models](hybrid-models/README.md): Zebra-Llama hybrid recurrent-attention (Mamba/KDA/GDN + MLA) models, FLA-parity recipes, and checkpoint conversion +- [Native SFT and LoRA](native-sft-lora.md): Megatron-native SFT/LoRA runbook (BF16 / FP8 / FP4), no Megatron-Bridge dependency + +--- + +[← Documentation home](../README.md) diff --git a/docs/04-technical-guides/checkpoint-management.md b/docs/04-technical-guides/checkpoint-management.md new file mode 100644 index 000000000..9e6f29cbc --- /dev/null +++ b/docs/04-technical-guides/checkpoint-management.md @@ -0,0 +1,203 @@ +# Checkpoint management + +Checkpoints capture **model state**, **optimizer state**, and **training progress** (iteration or step counters, schedulers, and related metadata). They are essential for **fault tolerance** (resume after failure), **experiment management** (reproducibility and comparison), and **hand-offs** between pretraining, fine-tuning, and conversion workflows. + +Primus is YAML-driven: checkpoint behavior is configured per backend. **Megatron-LM**, **TorchTitan**, and **MaxText** each expose their own checkpoint surfaces; this guide maps the knobs you set in Primus configs. + +**Primary sources in this repository** + +| Area | File | +|------|------| +| Megatron trainer defaults | `primus/configs/modules/megatron/trainer_base.yaml` | +| Primus Megatron extensions | `primus/configs/modules/megatron/primus_megatron_module.yaml` | +| TorchTitan defaults | `primus/configs/modules/torchtitan/pre_trainer.yaml` | +| Megatron checkpoint benchmark | `benchmark/megatron/checkpoint/README.md` | + +--- + +## 1. Overview + +- **What is saved:** Typically model parameters, optimizer state, RNG state, and iteration/step tracking—exact contents depend on flags such as `no_save_optim` / `no_save_rng` (Megatron) or `initial_load_model_only` (TorchTitan). +- **Why it matters:** Long runs on AMD GPU clusters benefit from periodic saves to durable storage; resuming or branching experiments requires consistent paths and formats. +- **Backend-specific systems:** Each training backend integrates its own checkpoint pipeline; Primus wires YAML into those backends without forcing a single universal format across Megatron, TorchTitan, and MaxText. + +--- + +## 2. Megatron checkpoint configuration + +Megatron-related options live on the trainer configuration merged from `trainer_base.yaml` and `primus_megatron_module.yaml`. Defaults below are taken from `trainer_base.yaml` unless noted. + +### Core paths and cadence + +| Parameter | Default (`trainer_base.yaml`) | Description | +|-----------|-------------------------------|-------------| +| `save` | `null` | Directory where new checkpoints are written. | +| `load` | `null` | Directory to load from when **resuming** training. | +| `save_interval` | `20000` | Save every *N* iterations. | +| `finetune` | `false` | When `true`, loads weights but **resets** the iteration counter (typical fine-tune entry). | + +### Format and detection + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `ckpt_format` | `torch_dist` | Checkpoint format: `torch` (legacy single-file style), `torch_dist` (distributed), or `zarr`. | +| `auto_detect_ckpt_format` | `false` | When loading, infer format automatically. | +| `pretrained_checkpoint` | `null` | Path to a **pretrained** checkpoint. | +| `ckpt_step` | `null` | Load a specific step from the pretrained checkpoint when applicable. | + +### Optimizer and RNG inclusion + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `no_save_optim` | `null` | When set truthy, **omit** optimizer state from saves. | +| `no_save_rng` | `null` | When set truthy, **omit** RNG state from saves. | +| `no_load_optim` | `null` | When set truthy, **do not** restore optimizer from checkpoint. | +| `no_load_rng` | `null` | When set truthy, **do not** restore RNG from checkpoint. | + +### Performance and distributed I/O + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `async_save` | `null` | Asynchronous checkpoint saving to reduce time blocking the training loop. | +| `ckpt_fully_parallel_save` | `true` | Parallel save path for distributed checkpoints. | +| `ckpt_fully_parallel_load` | `false` | Parallel load for distributed checkpoints. | +| `ckpt_assume_constant_structure` | `false` | Optimization when model structure is fixed across saves/loads. | +| `non_persistent_save_interval` | `null` | Save to **fast local** storage on a different cadence than persistent saves. | + +Related keys in `trainer_base.yaml` for non-persistent checkpoints include `non_persistent_ckpt_type`, `non_persistent_global_ckpt_dir`, `non_persistent_local_ckpt_dir`, and `non_persistent_local_ckpt_algo` (default `"fully_parallel"`). + +### Format conversion + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `ckpt_convert_format` | `null` | Target format for conversion (`torch`, `torch_dist`, or `zarr`). | +| `ckpt_convert_save` | `null` | Output directory for converted checkpoints. | +| `ckpt_convert_update_legacy_dist_opt_format` | `false` | Update legacy distributed optimizer layout when converting. | + +### Primus extensions + +Defined in `primus/configs/modules/megatron/primus_megatron_module.yaml` and implemented in `primus/backends/megatron/patches/checkpoint_patches.py` and `primus/backends/megatron/patches/args/checkpoint_path_patches.py`. + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `auto_continue_train` | `false` | When `true`, **automatically resume** from the latest checkpoint under `save` (adjusts load/finetune and related flags). | +| `disable_last_saving` | `false` | When `true`, **skip** the final checkpoint at shutdown (useful for benchmarking or when only periodic saves matter). | + +--- + +## 3. TorchTitan checkpoint configuration + +TorchTitan checkpoint options are grouped under `checkpoint` in `primus/configs/modules/torchtitan/pre_trainer.yaml`. + +| Parameter | Default (`pre_trainer.yaml`) | Description | +|-----------|------------------------------|-------------| +| `checkpoint.enable` | `false` | Master switch for checkpointing. | +| `checkpoint.folder` | `checkpoint` | Output directory (relative to run layout unless given as absolute). | +| `checkpoint.interval` | `500` | Save every *N* **steps**. | +| `checkpoint.initial_load_path` | `null` | Path for **initial** load (cold start or migration). | +| `checkpoint.initial_load_model_only` | `true` | Load **weights only**, not optimizer state. | +| `checkpoint.initial_load_in_hf` | `false` | Load initial weights from **Hugging Face** layout. | +| `checkpoint.last_save_model_only` | `true` | On last save, write **model only**. | +| `checkpoint.last_save_in_hf` | `false` | Write final checkpoint in **Hugging Face** format. | +| `checkpoint.export_dtype` | `float32` | Dtype for exported checkpoints. | +| `checkpoint.async_mode` | `disabled` | Asynchronous checkpoint mode. | +| `checkpoint.keep_latest_k` | `10` | Retain only the **K** most recent checkpoints. | +| `checkpoint.load_step` | `-1` | Load a specific step (`-1` typically means latest or default behavior per backend). | +| `checkpoint.exclude_from_loading` | `[]` | Glob or pattern list to **exclude** from restore. | +| `checkpoint.enable_first_step_checkpoint` | `false` | Optional checkpoint at step 0. | +| `checkpoint.create_seed_checkpoint` | `false` | Create a seed checkpoint when enabled. | + +TorchTitan also defines `activation_checkpoint` (activation recomputation) separately from persistent training checkpoints—do not confuse the two sections in `pre_trainer.yaml`. + +--- + +## 4. MaxText checkpoint configuration + +MaxText (JAX) uses configuration keys surfaced in Primus documentation and MaxText configs under `third_party/maxtext`. Primus overlay presets set the defaults shown below, while upstream MaxText `base.yml` is still loaded at runtime via `base_config: "base.yml"` and might define different upstream defaults. Typical training flags: + +| Parameter | Typical default | Description | +|-----------|-----------------|-------------| +| `enable_checkpointing` | `false` | Enable Orbax (or configured) checkpoint saves. | +| `async_checkpointing` | `false` | When checkpointing is enabled, use **async** checkpoint workers. | + +See `docs/03-configuration-reference/maxtext-parameters.md` for the full MaxText parameter table and interaction with training runs. + +--- + +## 5. Checkpoint formats (Megatron) + +| Format | Behavior | Notes | +|--------|----------|--------| +| `torch` | Classic PyTorch save/load; often **one file per rank** in distributed settings. | Simple but less flexible for topology changes. | +| `torch_dist` | **Distributed** checkpoint format with **resharding** support (e.g., changing tensor/pipeline parallel degree between save and load). | **Recommended** for many production flows that might change parallelism. | +| `zarr` | Zarr-backed checkpoint storage. | Useful when the stack and storage backend support it. | + +**Recommendation:** Prefer `torch_dist` for production when you need **flexibility across parallel layouts** and scalable I/O (see `ckpt_fully_parallel_save` / `ckpt_fully_parallel_load` in Megatron config). + +--- + +## 6. Common workflows + +**Resume training** + +- Set `load` to the checkpoint directory produced by a previous run. +- Keep `save` pointed at the directory for **new** checkpoints (often the same tree with a new run id, depending on your layout). +- Ensure `finetune` is `false` when you want to **continue** iteration counts. + +**Fine-tune from a pretrained checkpoint** + +- Set `load` (and optionally `pretrained_checkpoint` / `ckpt_step` as appropriate). +- Set `finetune: true` so iteration counters reset while weights load. + +**Auto-resume (Primus Megatron extension)** + +- Set `auto_continue_train: true` in the Megatron module config. +- Primus searches for the latest checkpoint under `save` and aligns load/optimizer flags; see `primus/backends/megatron/patches/checkpoint_patches.py` for behavior details. + +**Convert checkpoint format** + +- Set `ckpt_convert_format` (for example `torch_dist`) and `ckpt_convert_save` to the output directory. + +**Import Hugging Face weights (TorchTitan)** + +- Set `checkpoint.initial_load_in_hf: true` and `checkpoint.initial_load_path` to the HF model directory. + +--- + +## 7. Benchmarking checkpoints + +The Megatron checkpoint benchmark lives in `benchmark/megatron/checkpoint/`. + +**Entry points** + +- `benchmark/megatron/checkpoint/ckpt_launch.py`—main launcher (requires a Primus YAML config). +- `benchmark/megatron/checkpoint/ckpt_report.py`—reporting utility (can be run separately). + +**Example** (from `benchmark/megatron/checkpoint/README.md`): + +```bash +export DATA_PATH=/PATH/TO/DATA +python3 benchmark/megatron/checkpoint/ckpt_launch.py \ + --yaml-config-path examples/megatron/configs/MI300X/mixtral_8x7B_v0.1-pretrain.yaml \ + --nnodes 1 +``` + +The tool reports save/load times, bandwidth, and configuration echoes (world size, `ckpt_format`, `async_save`, paths, and more). Truncate or clean leftover output directories between runs if permissions or stale outputs cause issues. + +--- + +## 8. Best practices + +- Enable **`async_save`** (Megatron) for large models when supported, to limit training stalls during checkpoint windows. +- Set **`save_interval`** from **economic** criteria: frequent enough to limit lost work, infrequent enough to avoid storage and throughput bottlenecks (Megatron default in `trainer_base.yaml` is `20000`—override per job). +- Use **`non_persistent_save_interval`** with fast **local SSD** for frequent snapshots and a slower interval to **NFS** or object storage for durability. +- **Validate** resume and fine-tune paths on short runs before multi-week jobs; confirm `finetune` and `auto_continue_train` behave as intended. +- For TorchTitan, enable **`checkpoint.enable`** explicitly and set **`checkpoint.keep_latest_k`** to bound disk usage. + +--- + +## Related documentation + +- [Megatron parameters](../03-configuration-reference/megatron-parameters.md) +- [MaxText parameters](../03-configuration-reference/maxtext-parameters.md) +- [Benchmark suite](../02-user-guide/benchmarking.md) diff --git a/docs/04-technical-guides/collective-operations.md b/docs/04-technical-guides/collective-operations.md new file mode 100644 index 000000000..b048ecd98 --- /dev/null +++ b/docs/04-technical-guides/collective-operations.md @@ -0,0 +1,289 @@ +# NCCL/RCCL collective operations guide + +Distributed training spends a large fraction of wall time in **collective communication**: many GPUs must exchange gradients, parameters, or activations in coordinated patterns. On AMD GPUs, **RCCL** (ROCm Collective Communications Library) provides these operations with an API aligned to **NCCL** (NVIDIA Collective Communications Library), so most concepts and environment variables carry over between vendors. + +This guide explains core collectives, how they map to parallelism strategies in Primus (Megatron-LM, TorchTitan), and how to benchmark and troubleshoot communication. + +For Megatron knobs like `overlap_grad_reduce` and TorchTitan parallelism flags, see [Megatron parameters](../03-configuration-reference/megatron-parameters.md). For `NCCL_*` / `RCCL_*` environment variables, see [Environment variables](../03-configuration-reference/environment-variables.md). + +--- + +## 1. Introduction + +### What are collective operations? + +A **collective** is a multi-party communication pattern where **every participant** (or a defined **process group**) follows the same operation: combine tensors, broadcast, scatter pieces, or exchange shards. Unlike a single **Send/Recv** pair, collectives are **synchronized** by construction and are implemented with optimized algorithms (ring, tree, etc.). + +### NCCL vs RCCL + +| | NCCL | RCCL | +|---|------|------| +| Vendor | NVIDIA CUDA | AMD ROCm | +| Role | GPU collective communication | GPU collective communication | +| Typical API surface | C/C++ and bindings used by PyTorch distributed | ROCm stack; PyTorch uses similar backends | + +Application code written for **PyTorch distributed** (e.g. `torch.distributed`) generally selects the backend provided by the stack (**nccl** on NVIDIA, **rccl** on AMD). **Operation names and semantics** (AllReduce, AllGather, …) align so that **framework-level** code and tuning guides are largely **portable**. + +### Process groups + +Not every rank talks to every other rank in every step. **Process groups** define **subsets of ranks** that participate in a collective (e.g. only **tensor-parallel** ranks, only **data-parallel** ranks). Correctness and performance depend on **matching ranks** to the same group for each layer or phase of training. + +--- + +## 2. Core collective operations + +Below, \(n\) is the number of ranks in the process group, and \(S\) is the size of the logical tensor being reduced or moved (per-rank message size in ring formulations). **Complexity** expressions are **standard ring-style** approximations for **amount of data moved per rank** relative to \(S\); real implementations pick algorithms based on message size, topology, and environment. + +--- + +### AllReduce + +**What it does:** Each rank contributes a tensor; the **element-wise reduction** (typically **sum**) is applied across ranks, and the **full result** is **replicated** on every rank. + +``` +Rank 0: [a0] Rank 1: [a1] Rank 2: [a2] + \ | / + \ | / + --> REDUCE(sum) <-- + | + All ranks: [a0+a1+a2] +``` + +**Complexity (ring, per-rank data moved):** about \(\frac{2(n-1)}{n} S\). + +**Where used:** **Data-parallel** gradient synchronization; **tensor-parallel** partial sums; any step that needs **identical** tensors on all ranks after a reduction. + +--- + +### AllGather + +**What it does:** Each rank holds **one shard**; every rank receives the **concatenation** (or stacked layout) of **all shards**. + +``` +Rank 0: [x0] Rank 1: [x1] Rank 2: [x2] + \ | / + \ | / + --> ALL GATHER --> +Each rank: [x0 | x1 | x2] +``` + +**Complexity (ring):** about \(\frac{n-1}{n} S\) **if** each rank contributes \(S/n\); more generally scales with gathering \(n-1\) other shards of comparable size. + +**Where used:** **FSDP / ZeRO-3** parameter gather before forward; **TP** weight or activation assembly depending on layout. + +--- + +### ReduceScatter + +**What it does:** Conceptually **AllReduce** then **split**: each rank ends with **one shard** of the reduced result (each rank’s shard is the reduction over corresponding positions from all ranks’ inputs). + +``` +Inputs per rank: full-sized chunks (partial sums local) + | + Reduce + partition + | +Rank i gets shard i of the fully reduced tensor +``` + +**Complexity (ring):** about \(\frac{n-1}{n} S\) for the common balanced case. + +**Where used:** **FSDP** gradient **sharding** after backward; **sequence parallelism** with TP (activation distribution); distributed optimizer flows that **scatter** reduced pieces. + +--- + +### AllToAll + +**What it does:** Each rank sends a **distinct slice** to every other rank; every rank receives from every rank (matrix transpose of data ownership). + +``` + From rank 0..n-1 + | + +---------+---------+ + | scatter per dest | + v v v +Each rank receives its column/row of the logical matrix +``` + +**Where used:** **Expert parallelism** token **dispatch** and **combine** in MoE; some **sparsity** and **parallel embedding** layouts. + +--- + +### Broadcast + +**What it does:** One **root** rank’s tensor is copied to all other ranks. + +``` +Root: [w] ----copy----> all other ranks: [w] +``` + +**Where used:** **Weight initialization**, **loading checkpoints** to a group, distributing hyperparameters or small metadata. + +--- + +### Reduce + +**What it does:** Like AllReduce, but the **full result** appears on **one root** rank only. + +**Where used:** **Logging** or **metrics** where only rank 0 needs the scalar (e.g. reduced loss on one process). + +--- + +### Send / Recv (point-to-point) + +**What it does:** **One** rank sends a buffer to **one** other rank (possibly bidirectional with two ops). + +``` +Stage i ----Send/Recv----> Stage i+1 +``` + +**Where used:** **Pipeline parallelism** activations in forward, gradients in backward; **ring attention** steps in **context parallelism** (often implemented as a ring of Send/Recv with careful ordering). + +--- + +## 3. Which collectives are used in each parallelism strategy + +| Parallelism | Forward Pass | Backward Pass | Optimizer Step | +|---------------|--------------|----------------|----------------| +| Data Parallel | — | AllReduce (gradients) | — | +| FSDP/ZeRO-3 | AllGather (params) | ReduceScatter (grads) + AllGather (params) | — | +| Tensor Parallel | AllReduce or AllGather+ReduceScatter | AllReduce or AllGather+ReduceScatter | — | +| Sequence Parallel | AllGather (activations) | ReduceScatter (activations) | — | +| Pipeline Parallel | Send/Recv (activations) | Send/Recv (gradients) | — | +| Expert Parallel | AllToAll (token dispatch) | AllToAll (gradient dispatch) | — | +| Context Parallel | Ring Send/Recv (KV chunks) | Ring Send/Recv | — | + +Exact fusion and overlap depend on the backend (Megatron vs TorchTitan) and flags such as async TP or overlapped gradient reduction. + +--- + +## 4. Communication patterns in Megatron-LM + +Primus trains with **Megatron-LM** patches and configurations. Typical patterns: + +| Mode | Pattern | +|------|---------| +| **TP** | Column-parallel and row-parallel **linear** layers use **AllReduce** or **ReduceScatter/AllGather** sequences; with **sequence_parallel**, activations follow the Megatron **scatter/gather** pattern around TP regions. | +| **PP** | **Point-to-point** Send/Recv (or backend equivalents) between **pipeline stages** for activations and backward tensors. | +| **DP** | **AllReduce** for gradients when not using distributed optimizer; with **distributed optimizer**, **ReduceScatter**-style paths for shard-sized gradients. | +| **EP** | **AllToAll** for MoE **routing** (dispatch/combine) when experts are parallelized. | + +### Overlap knobs + +Megatron integrates **communication/compute overlap** options such as: + +- `overlap_grad_reduce`—overlap gradient reduction with computation where supported. +- `overlap_param_gather`—overlap parameter gathering (e.g. with distributed optimizer / FSDP-style paths) with computation. + +See [Megatron parameters](../03-configuration-reference/megatron-parameters.md) for defaults and compatibility with `use_distributed_optimizer`, `use_torch_fsdp2`, and checkpoint formats. + +--- + +## 5. Communication patterns in TorchTitan + +TorchTitan (used as a backend in Primus) relies on **PyTorch** distributed primitives and **DTensor**-style layouts: + +| Area | Pattern | +|------|---------| +| **FSDP / sharding** | **AllGather** / **ReduceScatter** orchestrated by **FSDP2** (`fully_shard` and related APIs) when `data_parallel_shard_degree` and sharding are enabled. | +| **TP** | Tensor parallelism is integrated with **DTensor** and model parallel helpers; schedules may use **collectives** inside module forward/backward. | +| **PP** | **Pipeline schedules** (`parallelism.pipeline_parallel_schedule`, `parallelism.pipeline_parallel_degree`) determine stage boundaries and buffering; communication is managed by the pipeline implementation. | + +### Async tensor parallelism + +Set `parallelism.enable_async_tensor_parallel: true` (where supported) to **overlap** TP communication with computation in eligible layers. + +--- + +## 6. RCCL-specific features and tuning + +The following appear in ROCm / AMD deployments and partner integrations; availability depends on your **driver**, **RCCL build**, and **network** stack. + +| Feature | Notes | +|---------|--------| +| **MSCCL** | Microsoft Collective Communication Library: **custom algorithms** and patterns; might be used when the stack is built and configured for them. | +| **MSCCL++** | User-space collective paths aimed at **lower latency** for specific patterns and hardware. | +| **ANP (AMD Network Plugin)** | Network backend integration (e.g. **AINIC**-oriented paths). Example: `NCCL_NET_PLUGIN` might point to `librccl-anp.so` or similar when installed (see Primus `examples/run_pretrain.sh` patterns). | + +### Environment variables + +Many deployments tune behavior with **NCCL-prefixed** variables (honored by RCCL for compatibility), for example: + +- `NCCL_PROTO`—protocol selection hints. +- `NCCL_P2P_NET_CHUNKSIZE`—chunking for P2P/network paths. +- `NCCL_IB_*`—InfiniBand / RDMA-related settings when applicable. +- `NCCL_SOCKET_IFNAME`—**socket** interface selection for TCP fallback or hybrid setups. + +Document your cluster’s recommended values in [Environment variables](../03-configuration-reference/environment-variables.md). + +--- + +## 7. Benchmarking collectives with Primus + +Primus includes an **RCCL microbenchmark** suite to measure **latency and bandwidth** for common collectives across message sizes. + +### Command + +Invoke through **`primus-cli`** (after `runner/` / container setup per your installation): + +```bash +./primus-cli direct -- benchmark rccl --op all_reduce --min-bytes 1M --max-bytes 128M +``` + +Useful flags (see `primus/tools/benchmark/rccl_bench_args.py`): + +| Flag | Purpose | +|------|---------| +| `--op` | One or more of: `all_reduce`, `broadcast`, `reduce_scatter`, `all_gather`, `alltoall` | +| `--min-bytes`, `--max-bytes` | Sweep range (e.g. `1K`, `1M`, `128M`) | +| `--num-sizes`, `--scale` | Generated sweep (`log2` or `linear`) | +| `--dtype` | `bf16`, `fp16`, `fp32` | +| `--output-file` | Write Markdown/CSV/JSONL report (default `./rccl_report.md`) | +| `--check` | Lightweight correctness checks | + +Example with multiple ops: + +```bash +./primus-cli direct -- benchmark rccl --op all_reduce all_gather reduce_scatter --min-bytes 1M --max-bytes 128M +``` + +### Reading results + +- **Bandwidth** (GB/s or similar): higher is better for large messages; compare against **peak NIC** or **GPU-GPU** limits for your topology. +- **Latency** (µs): dominates for **small** messages; important for **frequent small** collectives (e.g. some TP patterns). + +Use results to spot **unexpected drops** (wrong NIC, congestion, fallback to TCP) before scaling full training. + +--- + +## 8. Troubleshooting communication issues + +| Symptom | Checks | +|---------|--------| +| Hangs / timeouts | Enable **`NCCL_DEBUG=INFO`** (or `TRACE` for deep dives) and inspect which collective stalls. | +| Wrong interface | Set **`NCCL_SOCKET_IFNAME`** to the intended **cluster** interface; verify with `ip link` / admin docs. | +| IB / RDMA not used | Confirm **`NCCL_IB_*`**, HCA names, and permissions; run **preflight** (below). | +| Slow AllReduce | Compare **`benchmark rccl`** to baseline; check **topology** (NVLink vs network), **contention**, **message sizes**. | + +### Preflight: Network validation + +Primus **preflight** can aggregate host/GPU/network info: + +```bash +./primus-cli direct -- preflight --network +``` + +Combine with GPU checks as needed: + +```bash +./primus-cli direct -- preflight --gpu --network +``` + +Use this to confirm **RCCL/NCCL-related environment** snapshots and **connectivity expectations** before long jobs. + +--- + +## Related documentation + +- [Parallelism strategies](./parallelism-strategies.md)—how TP, PP, DP, FSDP, EP, and CP fit together. +- [Megatron parameters](../03-configuration-reference/megatron-parameters.md) +- [Environment variables](../03-configuration-reference/environment-variables.md) diff --git a/docs/04-technical-guides/data-preparation.md b/docs/04-technical-guides/data-preparation.md new file mode 100644 index 000000000..1dc6acbab --- /dev/null +++ b/docs/04-technical-guides/data-preparation.md @@ -0,0 +1,159 @@ +# Data preparation guide + +Primus routes training through **Megatron-LM**, **TorchTitan**, and **MaxText**. Each backend expects its own data format and preprocessing pipeline. This guide summarizes how to prepare data, how to use **mock** data for smoke tests, and which environment variables commonly apply. + +Scripts referenced below live under the Primus repository root, for example: + +- `examples/megatron/preprocess_data.py` +- `examples/megatron/prepare.py` +- `examples/megatron/prepare_bookcorpus_megatron_dataset.py` +- `examples/torchtitan/prepare.py` + +--- + +## 1. Overview + +| Backend | Format | Typical entry | +|---------|--------|----------------| +| Megatron | Indexed `.bin` + `.idx` datasets | `data_path`, `train_data_path`, tokenizer args | +| TorchTitan | Hugging Face datasets + local assets | `training.dataset`, `training.dataset_path`, `model.hf_assets_path` | +| MaxText | TFDS / Hugging Face / Grain / synthetic | `dataset_type`, paths per pipeline | + +All backends support **synthetic or mock** data for configuration and scaling tests without large downloads. + +--- + +## 2. Mock data (testing) + +### Megatron + +Set in the trainer module: + +```yaml +mock_data: true +``` + +Default in `primus/configs/modules/megatron/trainer_base.yaml` is `false`. When `true`, training uses generated data matching configured dimensions so you can validate YAML, parallelism, and throughput without real corpora. + +### TorchTitan + +```yaml +training: + mock_data: true +``` + +Default in `primus/configs/modules/torchtitan/pre_trainer.yaml` is `true` (useful for quick runs; set `false` and supply real datasets for production). + +### MaxText + +Use `dataset_type: synthetic` (or other synthetic paths in MaxText configs). See `third_party/maxtext/src/MaxText/configs/base.yml` and model YAMLs under `third_party/maxtext/src/MaxText/configs/`. + +--- + +## 3. Megatron data pipeline + +### Inputs + +- Raw **JSON** or **JSONL** text (one JSON object per line for JSONL). +- Optional **sentence splitting** via NLTK when `--split-sentences` is used (requires NLTK data; see [Environment variables](#6-environment-variables-for-data)). + +### Preprocessing: `examples/megatron/preprocess_data.py` + +The script tokenizes input and writes **Megatron indexed datasets** (`.bin` + `.idx`). It uses `build_tokenizer` from Primus’s Megatron tokenizer integration and accepts tokenizer flags from `_add_tokenizer_args`. + +**Important arguments** (from the script’s argparse): + +| Argument | Description | +|----------|-------------| +| `--input` | Path to input JSON (required). | +| `--json-keys` | Keys to read (default `text`). | +| `--output-prefix` | Output path **without** suffix; produces `{prefix}_{key}_{document|sentence}.bin` and `.idx`. | +| `--workers` | Number of worker processes (required). | +| `--partitions` | Split input for parallel preprocessing (default `1`). | +| `--split-sentences` | Run NLTK sentence splitting before encode. | +| `--append-eod` | Append end-of-document token. | + +**Example** (mirrors `examples/megatron/prepare.py` for BookCorpus-style flows): + +```bash +python3 examples/megatron/preprocess_data.py \ + --input /path/to/train.json \ + --tokenizer-type HuggingFaceTokenizer \ + --tokenizer-model /path/to/tokenizer \ + --output-prefix /path/to/out/bookcorpus_train \ + --workers "$(nproc)" \ + --split-sentences \ + --partitions 2 +``` + +### Configuring training runs + +| Parameter | Notes | +|-----------|--------| +| `data_path` | Single path or **weighted blend**: `0.5 /path/a 0.5 /path/b` | +| `train_data_path`, `valid_data_path`, `test_data_path` | Separate splits when used | +| `split` | Train/valid/test ratio string, e.g. `"99,1,0"` (default in `trainer_base.yaml`) or `"98,2,0"` for train/valid/test | +| `dataloader_type` | Megatron dataloader type; default in `trainer_base.yaml` is `null` (set explicitly in experiments as needed) | + +### BookCorpus example scripts + +- **`examples/megatron/prepare_bookcorpus_megatron_dataset.py`**—downloads BookCorpus to JSON via Hugging Face `datasets`, optional `--out-dir`. +- **`examples/megatron/prepare.py`**—orchestrates download, train/valid split, and calls `preprocess_data.py` with tokenizer settings from Primus config; respects `TOKENIZED_TRAIN_DATA_PATH` / `TOKENIZED_EVAL_DATA_PATH` for output locations. + +### Tokenizers + +Tokenizer type and model path are set on the model preset (for example `tokenizer_type`, `tokenizer_model` in `primus/configs/models/megatron/language_model.yaml` comments list `Llama2Tokenizer`, `HuggingFaceTokenizer`, etc.). + +--- + +## 4. TorchTitan data pipeline + +TorchTitan uses **Hugging Face datasets** style identifiers and local paths. + +| Key | Default (`pre_trainer.yaml`) | Description | +|-----|------------------------------|-------------| +| `training.dataset` | `c4` | Dataset identifier for TorchTitan loaders. | +| `training.dataset_path` | `null` | Local directory for dataset assets when needed. | + +Tokenizer and model assets are resolved from **`model.hf_assets_path`** (or equivalent in your model preset). The preparation script **`examples/torchtitan/prepare.py`**: + +- Resolves the TorchTitan checkout path. +- Runs `scripts/download_hf_assets.py` inside TorchTitan to fetch tokenizer assets for a given `repo_id`. +- Uses `HF_TOKEN` when the model or dataset is gated. + +--- + +## 5. MaxText data pipeline + +MaxText configuration is defined in upstream YAML (for example `third_party/maxtext/src/MaxText/configs/base.yml`). + +| Parameter | Meaning | +|-----------|---------| +| `dataset_type` | One of `synthetic`, `hf`, `grain`, `tfds` (per `base.yml` comments). | +| `hf_path`, `hf_data_dir`, `hf_train_files` | Hugging Face pipeline inputs when `dataset_type: hf`. | +| `per_device_batch_size` | Batch sizing on each device. | +| `packing` | Sequence packing for efficiency (default `True` in `base.yml`). | + +See MaxText’s data input documentation for Grain and TFDS specifics. + +--- + +## 6. Environment variables for data + +| Variable | Usage | +|----------|--------| +| `TOKENIZED_DATA_PATH` / `PRIMUS_TOKENIZED_DATA_PATH` | Tokenized dataset locations for Megatron hooks and examples (see `docs/03-configuration-reference/environment-variables.md`). | +| `TOKENIZED_TRAIN_DATA_PATH`, `TOKENIZED_EVAL_DATA_PATH` | Override output paths in `examples/megatron/prepare.py`. | +| `DATA_PATH` | General data root used in scripts and CI-style launches. | +| `HF_TOKEN` | **Required** for gated Hugging Face models and some datasets (TorchTitan `prepare.py`, Kubernetes examples in `examples/README.md`). | +| `HF_HOME` | Hugging Face cache directory (used in `examples/megatron/prepare.py`). | +| `NLTK_DATA` | NLTK tokenizer data directory for sentence splitting in `preprocess_data.py` when `NLTK_DATA` is set. | + +--- + +## Summary + +1. Use **mock or synthetic** data to validate configs and performance before investing in large preprocessing jobs. +2. For **Megatron**, convert JSON/JSONL to `.bin`/`.idx` with `preprocess_data.py` and point `data_path` or split paths at the outputs. +3. For **TorchTitan**, set `training.dataset` / `dataset_path` and run **`examples/torchtitan/prepare.py`** to fetch tokenizer assets. +4. For **MaxText**, configure `dataset_type` and `per_device_batch_size` per upstream `base.yml` and model YAMLs. diff --git a/docs/04-technical-guides/determinism-and-reproducibility.md b/docs/04-technical-guides/determinism-and-reproducibility.md new file mode 100644 index 000000000..e81cb59a5 --- /dev/null +++ b/docs/04-technical-guides/determinism-and-reproducibility.md @@ -0,0 +1,120 @@ +# Determinism and reproducibility + +Reproducibility—getting bit-identical (or run-to-run stable) results—matters for debugging divergence, validating optimizations, and regression testing. This guide covers Primus's deterministic mode, the environment variables it sets, the per-backend seed/determinism knobs, and the performance trade-offs. Parameters and behavior are grounded in `examples/run_pretrain.sh`, `primus/configs/modules/megatron/trainer_base.yaml`, and `primus/configs/modules/torchtitan/pre_trainer.yaml`. + +--- + +## 1. What "deterministic" means here + +There are two distinct goals: + +- **Reproducible (seeded)**—same seed + same config + same hardware/software gives the *same trajectory*. Achieved with fixed seeds; cheap. +- **Bitwise-deterministic**—kernels avoid non-deterministic reductions/atomics and tuning so results don't vary between runs. Requires deterministic algorithms and disabling autotuning; **slower**. + +Full determinism also generally requires the **same world size, parallelism layout, and library versions**. Changing TP/PP/DP, GPU count, or ROCm/Megatron versions can change numerics even with everything else fixed. + +--- + +## 2. Primus deterministic mode (`PRIMUS_DETERMINISTIC`) + +Setting `PRIMUS_DETERMINISTIC=1` configures the GPU/communication stack for deterministic behavior. The CLI/runner path applies this through the hook `runner/helpers/hooks/05_deterministic.sh`; the `examples/run_pretrain.sh` script applies an equivalent inline block. The exported variables are: + +```bash +# when PRIMUS_DETERMINISTIC=1 (runner/helpers/hooks/05_deterministic.sh) +export NCCL_ALGO="Ring" # deterministic collective algorithm +export NVTE_ALLOW_NONDETERMINISTIC_ALGO=0 # Transformer Engine: forbid non-deterministic kernels +export ROCBLAS_DEFAULT_ATOMICS_MODE=0 # rocBLAS: disable atomic (non-deterministic) reductions +export TORCH_COMPILE_DISABLE=1 # avoid torch.compile/Triton race conditions +export PRIMUS_TURBO_AUTO_TUNE=0 # disable Primus-Turbo autotuning (stable kernel choice) +``` + +> `PRIMUS_TURBO_AUTO_TUNE` also defaults to `0` in `runner/helpers/envs/base_env.sh`. The inline block in `examples/run_pretrain.sh` sets the first four variables and relies on that default for the fifth. + +Additionally, **HipBLASLt autotuning is disabled** in deterministic mode: tuning only runs when `PRIMUS_DETERMINISTIC != 1` *and* `PRIMUS_HIPBLASLT_TUNING=1` (`examples/run_pretrain.sh`). This prevents run-to-run kernel-selection differences. See [Performance tuning](./performance-tuning.md). + +`PRIMUS_DETERMINISTIC` is on the container passthrough allowlist (`runner/.primus.yaml`), so it reaches the training container. See [Environment variables](../03-configuration-reference/environment-variables.md). + +```bash +export PRIMUS_DETERMINISTIC=1 +./runner/primus-cli direct -- train pretrain \ + --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml +``` + +> The MoE example scripts explicitly set `PRIMUS_DETERMINISTIC=0` because deterministic mode disables the performance kernels/tuning they rely on. + +--- + +## 3. Seeds and deterministic algorithms (Megatron) + +In `primus/configs/modules/megatron/trainer_base.yaml`: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `seed` | `1234` | Master RNG seed (Python/NumPy/Torch, data order, init). | +| `deterministic_mode` | `false` | Force deterministic kernels/algorithms inside Megatron (slower; pairs with `PRIMUS_DETERMINISTIC`). | +| `data_parallel_random_init` | `false` | When `false`, parameters are initialized identically and broadcast across DP ranks; keep `false` for reproducible init. | + +For a fully reproducible Megatron run: set a fixed `seed`, `deterministic_mode: true`, and launch with `PRIMUS_DETERMINISTIC=1`. + +> **Startup assertion.** When `deterministic_mode: true`, Primus validates (`primus/backends/megatron/patches/args/rocm_arg_validation.py`, `validate_args_on_rocm`) that these environment variables are set, and **fails fast** otherwise: `TORCH_COMPILE_DISABLE=1`, `ROCBLAS_DEFAULT_ATOMICS_MODE=0`, `PRIMUS_TURBO_AUTO_TUNE=0`, and `PRIMUS_DETERMINISTIC=1`. Launching with `PRIMUS_DETERMINISTIC=1` (above) sets all of them, so always pair `deterministic_mode: true` with `PRIMUS_DETERMINISTIC=1`. + +--- + +## 4. Seeds and determinism (TorchTitan) + +Under `debug:` in `primus/configs/modules/torchtitan/pre_trainer.yaml` (TorchTitan v0.2.2 moved these keys out of `training:`): + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `seed` | `null` | RNG seed; set an integer for reproducible runs. | +| `deterministic` | `false` | Enable deterministic algorithms (disables some optimized kernels; slower). | +| `deterministic_warn_only` | `false` | With `deterministic: true`, warn instead of erroring when an op has no deterministic implementation. | + +Related: `checkpoint.create_seed_checkpoint` (`false`) creates a deterministic seed checkpoint that all ranks load, ensuring identical initialization across a distributed run. + +Note `compile.enable: true` is the TorchTitan default; for strict determinism prefer launching with `PRIMUS_DETERMINISTIC=1` (which sets `TORCH_COMPILE_DISABLE=1`) or disable compilation. + +--- + +## 5. MaxText + +MaxText determinism is governed by the upstream MaxText seed/data options surfaced through the MaxText config (see [MaxText parameters](../03-configuration-reference/maxtext-parameters.md)). The GPU-stack environment effects of `PRIMUS_DETERMINISTIC` (rocBLAS atomics, deterministic collectives) still apply at the launcher level. + +--- + +## 6. Performance trade-offs + +Determinism is not free: + +| Setting | Cost | +|---------|------| +| `NCCL_ALGO=Ring` | Forgoes faster topology-aware collective algorithms. | +| `ROCBLAS_DEFAULT_ATOMICS_MODE=0` | Disables atomic reductions—slower GEMMs. | +| `NVTE_ALLOW_NONDETERMINISTIC_ALGO=0` | Restricts TE to deterministic (often slower) kernels. | +| `TORCH_COMPILE_DISABLE=1` | No `torch.compile` fusion/codegen speedups. | +| `PRIMUS_TURBO_AUTO_TUNE=0` | No Primus-Turbo kernel autotuning. | +| HipBLASLt tuning disabled | No autotuned GEMM kernels. | +| `deterministic_mode` / `deterministic` | Deterministic algorithm variants are generally slower. | + +**Use deterministic mode for debugging and validation, not production throughput runs.** Once a result is reproduced/diagnosed, disable it to recover performance. + +--- + +## 7. Reproducibility checklist + +1. **Pin the environment**—same container image, ROCm version, and backend (Megatron/TorchTitan) commit. +2. **Fix seeds**—Megatron `seed`; TorchTitan `debug.seed`. +3. **Hold the layout constant**—same world size and TP/PP/DP/EP/CP degrees. +4. **Enable determinism**—`PRIMUS_DETERMINISTIC=1` plus backend `deterministic_mode`/`deterministic`. +5. **Disable autotuning**—automatic in deterministic mode (HipBLASLt tuning off). +6. **Use mock or fixed data ordering**—ensure the data pipeline is seeded; see [Data preparation](./data-preparation.md). +7. **Record everything**—log the full resolved config and env (see [Logging & experiment tracking](./logging-and-experiment-tracking.md)). + +--- + +## Related documentation + +- [Performance tuning](./performance-tuning.md)—HipBLASLt tuning and its interaction with deterministic mode. +- [Environment variables](../03-configuration-reference/environment-variables.md)—`PRIMUS_DETERMINISTIC` and related flags. +- [Data preparation](./data-preparation.md)—deterministic data ordering. +- [Megatron parameters](../03-configuration-reference/megatron-parameters.md) and [TorchTitan parameters](../03-configuration-reference/torchtitan-parameters.md). diff --git a/docs/04-technical-guides/diffusion-models/README.md b/docs/04-technical-guides/diffusion-models/README.md new file mode 100644 index 000000000..26daa6cb3 --- /dev/null +++ b/docs/04-technical-guides/diffusion-models/README.md @@ -0,0 +1,371 @@ +# Diffusion models in Primus - developer and architecture guide + +**Purpose:** Developer-focused documentation for understanding Primus diffusion architecture, design decisions, and implementation details. + +**For training/usage instructions, see:** [examples/megatron/diffusion/README.md](../../../examples/megatron/diffusion/README.md) + +**For test documentation, see:** [tests/unit_tests/backends/megatron/diffusion/](../../../tests/unit_tests/backends/megatron/diffusion/) + +--- + +## Architecture philosophy + +Primus diffusion models are built as **Megatron-Core native implementations**, designed for: +- Production-scale distributed training +- Seamless integration with Megatron parallelism strategies (TP, PP, DP, EP) +- Advanced checkpoint management with heterogeneous layers +- Clean separation of concerns (no framework dependencies like PyTorch Lightning) + +### Key design decisions + +**1. Megatron-Core Integration** +- Models in `core/models/diffusion/` follow Megatron-Core patterns +- Extends `TransformerConfig` for configurations (inherits all Megatron features) +- Uses `TransformerBlock` with heterogeneous layer support +- Compatible with Megatron's distributed checkpointing + +**2. Unified TransformerBlock Architecture** +- Unlike HuggingFace's ModuleLists, uses Megatron's unified TransformerBlock +- Simplifies checkpoint management +- More efficient gradient synchronization +- Note: pipeline parallelism is not supported for diffusion models (`pipeline_model_parallel_size` must be 1) + +**3. No Framework Dependencies** +- Direct PyTorch implementation (no PyTorch Lightning) +- Uses Megatron's distributed primitives directly +- Simpler debugging and profiling +- Better control over distributed training + +**4. Extensibility First** +- Base classes designed for multiple diffusion models (Flux, DiT, MovieGen) +- Clear shared vs model-specific separation +- Hierarchical encoder registry for easy extension + +--- + +## Supported models + +### Flux ✅ production ready +Flow-based diffusion model with MMDiT (Multimodal Diffusion Transformer) architecture. + +- **Architecture**: Dual-stream with joint and single transformer blocks +- **Sizes**: 535M (testing) and 12B (production) +- **Reference**: [Black Forest Labs FLUX.1](https://huggingface.co/black-forest-labs/FLUX.1-dev) +- **Status**: Fully implemented and tested (390 tests) + +### Future models ⏳ planned +- **DiT**: Diffusion Transformer for image generation +- **MovieGen**: Video diffusion models +- **Custom Models**: Extensible framework for new architectures + +--- + +## Project structure + +``` +primus/backends/megatron/ +├── core/models/ +│ ├── common/diffusion_module/ # DiffusionModule (base class with sharded state dict) +│ │ └── diffusion_module.py +│ └── diffusion/ # Model implementations (Megatron-Core style) +│ ├── common/ # Shared components (MMDiT layers, attention) +│ │ ├── config.py # BaseDiffusionConfig (extends TransformerConfig) +│ │ └── layers.py # Shared layers (if any) +│ └── flux/ # Flux-specific code +│ ├── config.py # FluxConfig with factory methods (535M, 12B) +│ ├── model.py # Flux model (extends DiffusionModule) +│ └── layer_spec.py # Flux layer specifications +│ +├── training/diffusion/ # Training utilities +│ ├── noise_utils.py # Noise application (flow matching, DDPM) +│ ├── loss_computation.py # Loss functions (flow matching, epsilon, v-prediction) +│ ├── timestep_sampling.py # Timestep sampling strategies +│ └── schedulers/ +│ ├── base.py # BaseScheduler +│ └── flow_matching.py # FlowMatchEulerDiscreteScheduler +│ +└── data/ + ├── energon/ # Shared Energon infrastructure + └── diffusion/ # Diffusion-specific data + ├── encoders/ # Hierarchical encoder registry + │ ├── image/vae/ # VAE variants (SD VAE, custom VAEs) + │ ├── text/t5/ # T5 variants (XXL, etc.) + │ └── text/clip/ # CLIP variants (L, H, etc.) + ├── preprocessing/ # Data preprocessing utilities + │ ├── download.py # Reusable download utils (retry, MD5, manifests) + │ ├── finalize.py # Energon dataset finalization + │ ├── validate.py # Dataset structure validation + │ └── pipelines/ # Dataset preparation pipelines + │ ├── base.py # DatasetPipeline abstract base class + │ ├── raw.py # Raw image pipeline + │ ├── encoded.py # Pre-encoded pipeline + │ └── ingest.py # StreamingIngestPipeline (MLPerf Arrow->WDS) + └── task_encoders/ # Energon TaskEncoders for diffusion + +primus/configs/models/megatron/diffusion/ +├── flux_535m.yaml # Flux 535M config +├── flux_12b.yaml # Flux 12B config +└── encoders.yaml # Encoder configuration + +tests/unit_tests/backends/megatron/diffusion/ # Comprehensive test suite (390 tests) +├── models/ # Model-level tests +├── layers/ # Layer-level tests +├── unit/ # Unit tests for utilities +├── distributed/ # Distributed training tests +├── functional/ # End-to-end functional tests +└── checkpointing/ # Checkpoint tests + +docs/04-technical-guides/diffusion-models/ # This directory +├── README.md # This file (developer guide) +├── architecture_overview.md # Detailed architecture +├── data_preprocessing.md # Data pipeline guide (includes Flux-specific section) +├── energon_integration.md # Energon patterns +├── flux_architecture.md # Flux deep dive +├── fp8_training.md # FP8 training guide (benchmarks, tuning, troubleshooting) +├── api_reference.md # API documentation +├── adding_new_models.md # Extension guide +└── STRUCTURE.md # Directory tree and organization +``` + +--- + +## Key technical features + +### 1. DiffusionModule base class + +All diffusion models inherit from `DiffusionModule`, which provides: +- Megatron-Core integration (process groups, parallelism) +- Sharded state dict support for distributed checkpointing +- Gradient checkpointing +- Mixed precision support +- Device placement utilities + +**Location:** `primus/backends/megatron/core/models/common/diffusion_module/diffusion_module.py` + +### 2. BaseDiffusionConfig + +Configuration class extending `TransformerConfig`: +- Inherits all Megatron-Core configuration (TP, PP, sequence_parallel, etc.) +- Adds diffusion-specific parameters (channels, patch_size, etc.) +- Factory methods for common presets + +**Location:** `primus/backends/megatron/core/models/diffusion/common/config.py` + +### 3. Hierarchical encoder registry + +Organized by modality → type → variant: +``` +encoders/ +├── image/vae/ +│ ├── sd_vae.py # Standard SD VAE +│ └── (future: custom VAEs) +├── text/t5/ +│ ├── t5_xxl.py # T5-XXL encoder +│ └── (future: T5 variants) +└── text/clip/ + ├── clip_l.py # CLIP-L encoder + └── (future: CLIP-H, etc.) +``` + +Benefits: +- Easy to add new encoder variants (5+ planned per modality) +- Config-driven selection via `encoders.yaml` +- Lazy loading (encoders loaded only when needed) +- Shared base classes for common functionality + +### 4. Training utilities structure + +**Noise Application** (`noise_utils.py`): +- `apply_flow_matching_noise()`: For flow matching models (Flux) +- `apply_ddpm_noise()`: For DDPM-based models +- Support for different noise schedules + +**Loss Computation** (`loss_computation.py`): +- `compute_flow_matching_loss()`: For flow matching +- `compute_epsilon_loss()`: For epsilon prediction (DDPM) +- `compute_v_prediction_loss()`: For v-prediction +- Unified interface for different loss types + +**Timestep Sampling** (`timestep_sampling.py`): +- `LogitNormalSampler`: Logit-normal distribution +- `UniformSampler`: Uniform distribution +- `ModeSampler`: Mode-focused sampling +- Base class for custom samplers + +### 5. Shared Energon infrastructure + +Located in `data/energon/` for reusability across models: +- Shared data loading utilities +- Common preprocessing functions +- WebDataset integration +- Model-specific TaskEncoders in `data/diffusion/task_encoders/` + +### 6. Precalculated data support + +**Performance**: 5-10x faster training than on-the-fly encoding + +**Supported encodings**: +- `preencoded` -- Primus-encoded PyTorch `.pth` format (VAE latents + text embeddings) +- `preencoded_numpy` -- MLPerf NumPy uint16 format (bfloat16 tensors as `.bytes` entries) + +**Workflow**: +1. Precompute VAE latents and text embeddings offline +2. Store in WebDataset/Energon format +3. Load directly during training (no encoder overhead) + +**Benefits**: +- Faster training iteration +- Consistent encoder versions across runs +- Lower GPU memory (no encoders loaded during training) +- Better reproducibility + +### 7. MLPerf streaming ingest pipeline + +**Location:** `data/diffusion/preprocessing/pipelines/ingest.py` + +The `StreamingIngestPipeline` downloads Apache Arrow IPC files from MLCommons R2 storage and converts them directly into Energon WebDataset tar shards in a single streaming pass. This avoids storing the full ~6 TB raw Arrow dataset on disk. + +**Architecture**: Producer-consumer with concurrent download and sequential conversion: +- **Producer thread**: Acquires a semaphore permit, submits downloads to a `ThreadPoolExecutor`, passes completed futures to a drain thread +- **Drain thread**: Processes futures in submission order and feeds the prefetch queue +- **Consumer (main thread)**: Converts Arrow data to tar shards, deletes temporary files, releases semaphore permits + +**Key properties**: +- Bounded disk usage: `threading.Semaphore(prefetch_depth)` limits Arrow files on disk +- Deterministic shard ordering preserved via in-order future draining +- Retry with exponential backoff for HTTP 429/503 and MD5 mismatches (`download.py`) +- Skip-and-log: individual failures are recorded in `failed_files.json` +- Resume: re-running skips shards that already exist on disk + +**Related modules**: +- `download.py`: `download_with_backoff()`, `fetch_manifest()`, `parse_md5_manifest()` +- `pipelines/base.py`: `DatasetPipeline` ABC (shared by `raw.py`, `encoded.py`, `ingest.py`) +- `finalize.py`: Energon dataset finalization (`.nv-meta/dataset.yaml` + `energon prepare`) +- `validate.py`: Post-finalization structural validation + +--- + +## Implementation status + +### Core infrastructure ✅ +- ✅ Directory structure with 25+ directories +- ✅ Base classes (DiffusionModule, BaseDiffusionConfig, BaseScheduler) +- ✅ DiffusionModule with Megatron-Core integration +- ✅ FluxConfig with factory methods (flux_535m, flux_12b) +- ✅ FlowMatchEulerDiscreteScheduler +- ✅ Configuration system (YAML files) +- ✅ Testing framework (390 tests) +- ✅ Comprehensive documentation + +### Flux model implementation ✅ +- ✅ Flux model architecture (dual-stream MMDiT) +- ✅ MMDiT layers and attention (joint + single blocks) +- ✅ Embeddings (3D RoPE, timestep, vector) +- ✅ Hierarchical encoder registry +- ✅ Data pipeline and TaskEncoders +- ✅ Training utilities (noise, loss, sampling) +- ✅ Checkpoint conversion (HF <-> Megatron) + +--- + +## Documentation map + +### Core guides + +📖 **[Architecture Overview](architecture_overview.md)** +High-level design, directory structure, and architectural decisions. + +📖 **[Directory Structure](STRUCTURE.md)** +Complete directory tree and file organization. + +📖 **[Data Preprocessing Guide](data_preprocessing.md)** +How to prepare datasets, precalculate latents, and use Energon. + +📖 **[Energon Integration](energon_integration.md)** +Megatron-Energon patterns and TaskEncoder implementation. + +📖 **[Adding New Models](adding_new_models.md)** +Step-by-step guide for implementing new diffusion models. + +### Advanced documentation + +📖 **[Flux Architecture Deep Dive](flux_architecture.md)** +Mathematical formulation, detailed component descriptions, and performance optimizations. + +📖 **[API Reference](api_reference.md)** +Complete API documentation with function signatures and usage examples. + +📖 **[FP8 Training Guide](fp8_training.md)** +FP8 precision training on AMD MI300X: configuration, benchmarks, tuning recipes, and troubleshooting. + +### Related documentation + +📖 **[Training Guide](../../../examples/megatron/diffusion/README.md)** +User-facing guide for training Flux models (quick start, configurations, troubleshooting). + +📖 **[Test Directory](../../../tests/unit_tests/backends/megatron/diffusion/)** +Test suite for diffusion models. + +--- + +## Testing architecture + +**Test Organization** (following Megatron-LM patterns): +- One comprehensive file per model (`test_flux_model.py`) +- Unit tests for utilities (`unit/test_utils.py`, etc.) +- Distributed tests in separate directory (`distributed/`) +- Functional tests for workflows (`functional/`) + +**Test Status**: ✅ 390 tests passing + +See [tests/unit_tests/backends/megatron/diffusion/](../../../tests/unit_tests/backends/megatron/diffusion/) for details. + +--- + +## Hardware requirements + +### Flux 535M (testing) +- **Training**: 1x MI300X 192GB (compatible with H100/A100) +- **Inference**: 1x MI300X 192GB +- **Batch Size**: 1-8 per GPU + +### Flux 12B (production) +- **Training**: 8x MI300X 192GB (recommended) or 4x MI300X 192GB with TP=2 +- **Inference**: 1x MI300X 192GB +- **Batch Size**: 1-2 per GPU for training, 1-4 for inference + +--- + +## Contributing + +See the main guide: [Adding New Models](adding_new_models.md) + +**To contribute:** +1. Follow the established directory structure +2. Extend base classes (DiffusionModule, BaseDiffusionConfig) +3. Add comprehensive tests in `tests/unit_tests/backends/megatron/diffusion/` +4. Update documentation (architecture guide + API reference) +5. Submit PR with clear description + +--- + +## License + +- **Primus Code**: AMD Copyright 2025, Apache License 2.0 +- **Flux Encoders**: + - FLUX.1 [dev]: Non-commercial license + - FLUX.1 [schnell]: Apache 2.0 (commercial use allowed) + - Individual components (T5, CLIP, VAE): Check respective licenses + +--- + +## Resources + +- **Megatron-Core**: [nvidia/Megatron-LM](https://github.com/NVIDIA/Megatron-LM) - Core framework +- **Flux Model**: [black-forest-labs/FLUX.1-dev](https://huggingface.co/black-forest-labs/FLUX.1-dev) +- **Flow Matching**: Rectified flow and flow matching papers +- **NeMo**: [nvidia/NeMo](https://github.com/NVIDIA/NeMo) - Alternative diffusion implementation + +--- + +**Last Updated**: January 2026 diff --git a/docs/04-technical-guides/diffusion-models/STRUCTURE.md b/docs/04-technical-guides/diffusion-models/STRUCTURE.md new file mode 100644 index 000000000..9bed81156 --- /dev/null +++ b/docs/04-technical-guides/diffusion-models/STRUCTURE.md @@ -0,0 +1,251 @@ +# Flux diffusion infrastructure - directory structure + +**Created**: December 5, 2025 +**Status**: ✓ Implementation Complete + +## Overview + +This document describes the directory structure created for Flux diffusion model support in Primus, following Megatron-Core conventions with production-ready enhancements. + +--- + +## Directory tree + +``` +Primus/ +├── primus/backends/megatron/ +│ ├── core/models/ +│ │ ├── common/diffusion_module/ # DiffusionModule base class +│ │ │ └── diffusion_module.py +│ │ └── diffusion/ # Diffusion models (Megatron-Core convention) +│ │ ├── common/ # Shared building blocks (config, embeddings, normalization) +│ │ │ ├── __init__.py +│ │ │ ├── config.py # ✓ BaseDiffusionConfig +│ │ │ ├── embeddings.py # ✓ TimeStepEmbedder, MLPEmbedder +│ │ │ └── normalization.py # ✓ AdaLN, AdaLNContinuous, RMSNorm +│ │ ├── flux/ # Flux-specific components +│ │ │ ├── __init__.py +│ │ │ ├── config.py # ✓ FluxConfig (with factory methods) +│ │ │ ├── model.py # ✓ Flux model +│ │ │ ├── layers.py # ✓ EmbedND, embedders +│ │ │ ├── layer_spec.py # ✓ get_flux_layer_spec, get_flux_*_spec_for_backend, MMDiTLayer +│ │ │ ├── attention.py # ✓ JointSelfAttention, FluxSingleAttention +│ │ │ ├── utils.py # ✓ generate_image_position_ids +│ │ │ └── checkpoint_converter.py # ✓ HF <-> Megatron conversion +│ │ └── __init__.py +│ │ +│ ├── training/diffusion/ # Training utilities +│ │ ├── schedulers/ +│ │ │ ├── __init__.py +│ │ │ ├── base.py # ✓ BaseScheduler +│ │ │ └── flow_matching.py # ✓ FlowMatchEulerDiscreteScheduler +│ │ ├── noise_utils.py # ✓ apply_flow_matching_noise, apply_ddpm_noise +│ │ ├── loss_computation.py # ✓ compute_flow_matching_loss, etc. +│ │ ├── timestep_sampling.py # ✓ LogitNormalSampler, UniformSampler +│ │ └── __init__.py +│ │ +│ └── data/ +│ ├── energon/ # Shared Energon infrastructure +│ │ └── __init__.py # ✓ Energon wrappers +│ │ +│ └── diffusion/ # Diffusion-specific data +│ ├── encoders/ # Hierarchical encoder registry +│ │ ├── image/ +│ │ │ ├── vae/ # VAE variants +│ │ │ │ └── __init__.py # ✓ AutoencoderKL, VQVAE, etc. +│ │ │ └── __init__.py +│ │ ├── text/ +│ │ │ ├── t5/ # T5 variants +│ │ │ │ └── __init__.py # ✓ T5-XXL, T5-Large, etc. +│ │ │ ├── clip/ # CLIP variants +│ │ │ │ └── __init__.py # ✓ CLIP-L, CLIP-H, etc. +│ │ │ └── __init__.py +│ │ └── __init__.py # ✓ EncoderRegistry +│ │ +│ ├── preprocessing/ +│ │ ├── image/ +│ │ │ └── __init__.py # ✓ Resizing, augmentation +│ │ └── __init__.py +│ │ +│ ├── task_encoders/ # Energon TaskEncoders +│ │ ├── __init__.py +│ │ └── image.py # ✓ EncodedDiffusionTaskEncoder, RawDiffusionTaskEncoder +│ │ +│ └── __init__.py +│ +├── primus/backends/megatron/ +│ └── megatron_pretrain_trainer.py # ✓ Shared Megatron pretrain trainer (drives diffusion pretraining) +│ +├── primus/configs/models/megatron/ +│ └── diffusion/ # YAML configs +│ ├── __init__.py +│ ├── flux_535m.yaml # ✓ Flux 535M config +│ ├── flux_12b.yaml # ✓ Flux 12B config +│ └── encoders.yaml # ✓ Encoder configs +│ +├── examples/megatron/ +│ ├── diffusion/ +│ │ └── README.md # ✓ Training guide (consolidated) +│ ├── configs/MI300X/diffusion/ # MI300X training configs +│ │ ├── flux_535m_pretrain.yaml +│ │ ├── flux_12b_fsdp2_energon_schnell_resample_local_spec.yaml +│ │ ├── flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml +│ │ └── ... +│ ├── configs/MI355X/diffusion/ # MI355X training configs (mirrors MI300X + MXFP4/MLPerf) +│ │ ├── flux_12b_ddp_energon_schnell_resample_*.yaml +│ │ ├── flux_12b_fsdp2_energon_schnell_resample_*.yaml +│ │ └── ... +│ └── prepare.py +│ +├── examples/run_pretrain.sh # Main training script +│ +├── tests/ +│ ├── unit_tests/backends/megatron/diffusion/ # Unit test suite +│ │ ├── test_flux_model.py +│ │ ├── test_flux_config.py +│ │ ├── test_flux_layers.py +│ │ ├── test_flux_embeddings.py +│ │ ├── test_flux_normalization.py +│ │ ├── test_flux_utils.py +│ │ ├── test_flux_checkpoint_converter.py +│ │ ├── test_flux_checkpoint_utils.py +│ │ ├── test_flux_layer_spec_backend_selection.py +│ │ ├── test_flux_compile_checkpoint_keys.py +│ │ ├── training/ +│ │ ├── data/ +│ │ └── distributed/ +│ └── integration_tests/backends/megatron/diffusion/ +│ ├── data/ +│ └── distributed/ +│ +└── docs/backends/megatron/ + └── diffusion/ # Documentation + ├── README.md # ✓ Overview + ├── STRUCTURE.md # ✓ This file + ├── architecture_overview.md # ✓ Design details + ├── data_preprocessing.md # ✓ Data guide (includes Flux-specific section) + ├── energon_integration.md # ✓ Energon patterns + ├── flux_architecture.md # ✓ Flux deep dive + ├── fp8_training.md # ✓ FP8 training guide + ├── api_reference.md # ✓ API documentation + └── adding_new_models.md # ✓ Extension guide +``` + +--- + +## Completed components + +### ✓ Base classes + +1. **DiffusionModule** (`core/models/common/diffusion_module/diffusion_module.py`) + - Base class for all diffusion models (extends MegatronModule) + - Provides Megatron-Core integration + - Required methods: `forward()` + - Loss computation: Use standalone functions from `loss_computation.py` + - Utility methods: `get_num_params()`, `set_requires_grad()` + +2. **BaseDiffusionConfig** (`common/config.py`) + - Extends `megatron.core.transformer.transformer_config.TransformerConfig` + - Common parameters: `in_channels`, `out_channels`, `patch_size` + - Validation method for configuration integrity + +3. **FluxConfig** (`flux/config.py`) + - Flux-specific configuration + - Parameters: `num_joint_layers`, `num_single_layers`, `context_dim`, `vec_in_dim` + - Factory methods: `flux_535m()`, `flux_12b()` + - 3D RoPE configuration: `axes_dim`, `theta` + +3. **BaseScheduler** (`schedulers/base.py`) + - Abstract base for diffusion schedulers + - Required: `add_noise()`, `get_velocity_target()`, `sample_timesteps()` + - Optional: `scale_model_input()`, `get_snr()`, `get_alpha()`, `get_sigma()` + +4. **FlowMatchEulerDiscreteScheduler** (`schedulers/flow_matching.py`) + - Concrete implementation for Flux + - Linear interpolation: `x_t = (1-t)*noise + t*data` + - Velocity target: `v = data - noise` + +### ✓ Directory structure + +- **25 `__init__.py` files** with comprehensive docstrings +- **Multiple implementation files** (models, configs, schedulers, data pipeline) +- **Complete test suite** with fixtures and helpers + +--- + +## Architectural decisions + +### 1. Models under `core/models/` +- Follows Megatron-Core convention (`megatron/core/models/gpt/`, etc.) +- Easier upstream tracking when Megatron-Core adds diffusion support + +### 2. Shared components in `common/` +- Standard approach stores shared code in model-specific directories +- Primus: `common/` for shared config, embeddings, and normalization +- Flux-specific: model class, MMDiT/single-block layer specs, joint attention, and `EmbedND` + +### 3. Hierarchical encoder structure +- `encoders/image/vae/`, `encoders/text/t5/`, `encoders/text/clip/` +- Registry pattern for config-driven selection +- Easy to add new encoder variants (5+ planned per modality) + +### 4. Shared Energon infrastructure +- `data/energon/` for cross-model utilities (VLM, diffusion, future) +- `data/diffusion/task_encoders/` for diffusion-specific TaskEncoders +- Traditional approach nests Energon under model-specific directories + +### 5. Synthetic (mock) data +- Synthetic datasets live in `primus/backends/megatron/data/synthetic/mock_datasets.py`, wired through `primus/backends/megatron/data/synthetic_dataset_provider.py`, so training can run without real data +- Unit tests exercise them under `tests/unit_tests/backends/megatron/diffusion/data/` + +### 6. No PyTorch lightning +- Pure Megatron patterns (no PTL DataModules) +- Better integration with Megatron training loop + +--- + +## Import examples + +```python +# Base classes +from primus.backends.megatron.core.models.diffusion.common import ( + BaseDiffusionConfig, +) + +# Flux configuration +from primus.backends.megatron.core.models.diffusion.flux import FluxConfig + +# Create configs +config_535m = FluxConfig.flux_535m() +config_12b = FluxConfig.flux_12b() + +# Schedulers +from primus.backends.megatron.training.diffusion.schedulers import ( + BaseScheduler, + FlowMatchEulerDiscreteScheduler, +) + +# Create scheduler +scheduler = FlowMatchEulerDiscreteScheduler() +timesteps = scheduler.sample_timesteps(batch_size=8, device='cuda') +``` + +--- + +## Validation status + +✓ All Python files syntactically correct +✓ No linter errors detected +✓ All imports properly structured +✓ Comprehensive docstrings +✓ Copyright headers applied (AMD 2025, Apache 2.0) + +--- + +## Files summary + +All infrastructure files, model implementations, data pipeline components, tests, and documentation are complete and ready for production use. + +--- + +**End of Structure Document** diff --git a/docs/04-technical-guides/diffusion-models/adding_new_models.md b/docs/04-technical-guides/diffusion-models/adding_new_models.md new file mode 100644 index 000000000..e975bae64 --- /dev/null +++ b/docs/04-technical-guides/diffusion-models/adding_new_models.md @@ -0,0 +1,711 @@ +# Adding new diffusion models + +This guide explains how to add new diffusion models to Primus, following the established patterns and architecture. + +--- + +## Table of contents + +1. [Overview](#overview) +2. [Prerequisites](#prerequisites) +3. [Step-by-step guide](#step-by-step-guide) +4. [Example: Adding DiT](#example-adding-dit) +5. [Testing your model](#testing-your-model) +6. [Best practices](#best-practices) + +--- + +## Overview + +Adding a new diffusion model involves: +1. Creating model configuration +2. Implementing model class +3. Adding necessary layers +4. Creating data pipeline components +5. Writing tests +6. Updating documentation + +**Time Estimate**: 5-10 days depending on model complexity + +--- + +## Prerequisites + +Before adding a new model, ensure you have: +- ✅ Understanding of the model architecture (paper, reference implementation) +- ✅ Access to pretrained weights (if applicable) +- ✅ Sample dataset for testing +- ✅ Familiarity with Primus diffusion architecture +- ✅ Development environment setup + +**Required Reading**: +- [Architecture Overview](architecture_overview.md) +- [Data Preprocessing Guide](data_preprocessing.md) +- [Energon Integration](energon_integration.md) + +--- + +## Step-by-step guide + +### Step 1: Create model directory + +Create a directory for your model under `core/models/diffusion/`: + +```bash +mkdir -p primus/backends/megatron/core/models/diffusion/dit +cd primus/backends/megatron/core/models/diffusion/dit +``` + +Create files: +```bash +touch __init__.py +touch config.py +touch model.py +touch layers.py # If model-specific layers needed +``` + +### Step 2: Implement configuration + +**File**: `config.py` + +```python +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +"""Configuration for DiT (Diffusion Transformer) model.""" + +from dataclasses import dataclass +from typing import Optional +from ..common.config import BaseDiffusionConfig + + +@dataclass +class DiTConfig(BaseDiffusionConfig): + """ + DiT-specific configuration. + + DiT uses a standard transformer architecture for diffusion. + """ + + # Model identification + model_type: str = "dit" + + # Architecture: Number of layers + num_layers: int = 28 # DiT-XL/2 default + + # Architecture: Dimensions + hidden_size: int = 1152 + num_attention_heads: int = 16 + + # Input dimensions + in_channels: int = 4 # VAE latent channels (standard SD VAE) + + # Context dimensions + context_dim: int = 768 # CLIP text embedding dimension + + # Patchification + patch_size: int = 2 # DiT uses 2x2 patches + + # Class conditioning (for conditional generation) + num_classes: int = 1000 # ImageNet classes + class_dropout_prob: float = 0.1 + + # Adaptive LayerNorm (DiT-specific) + use_adaptive_layernorm: bool = True + + def validate(self): + """Validate DiT-specific configuration.""" + # Call parent validation + super().validate() + + # DiT-specific validations + if self.num_layers <= 0: + raise ValueError(f"num_layers must be positive, got {self.num_layers}") + + if self.patch_size <= 0: + raise ValueError(f"patch_size must be positive, got {self.patch_size}") + + if self.num_classes < 0: + raise ValueError(f"num_classes must be non-negative, got {self.num_classes}") + + @classmethod + def dit_xl_2(cls, **kwargs): + """ + Create configuration for DiT-XL/2. + + Args: + **kwargs: Override default parameters + + Returns: + DiTConfig instance + """ + defaults = { + 'num_layers': 28, + 'hidden_size': 1152, + 'num_attention_heads': 16, + 'patch_size': 2, + } + defaults.update(kwargs) + return cls(**defaults) + + @classmethod + def dit_l_2(cls, **kwargs): + """Create configuration for DiT-L/2.""" + defaults = { + 'num_layers': 24, + 'hidden_size': 1024, + 'num_attention_heads': 16, + 'patch_size': 2, + } + defaults.update(kwargs) + return cls(**defaults) +``` + +### Step 3: Implement model class + +**File**: `model.py` + +```python +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +"""DiT model implementation.""" + +import torch +import torch.nn as nn +from primus.backends.megatron.core.models.common.diffusion_module.diffusion_module import DiffusionModule +from megatron.core.process_groups_config import ProcessGroupCollection +from ..common.layers import MMDiTLayer # Reuse shared components if applicable +from .config import DiTConfig + + +class DiT(DiffusionModule): + """ + DiT (Diffusion Transformer) model. + + Reference: "Scalable Diffusion Models with Transformers" (Peebles & Xie, 2023) + + Note: Inherits from DiffusionModule for Megatron-Core integration + (process groups, distributed checkpointing, attention backend config) + """ + + def __init__( + self, + config: DiTConfig, + encoder_configs: Optional[Dict[str, Any]] = None, + pg_collection: Optional[ProcessGroupCollection] = None, + ): + """ + Initialize DiT model. + + Args: + config: DiT configuration + encoder_configs: Optional encoder configurations (VAE, T5, CLIP) + pg_collection: Process group collection for distributed training + """ + super().__init__(config, pg_collection=pg_collection, encoder_configs=encoder_configs) + + self.config = config + + # Input projection (patchify) + self.input_proj = nn.Conv2d( + config.in_channels, + config.hidden_size, + kernel_size=config.patch_size, + stride=config.patch_size, + ) + + # Positional embedding + self.pos_embed = nn.Parameter( + torch.zeros(1, (config.seq_length // config.patch_size) ** 2, config.hidden_size) + ) + + # Timestep embedding + self.time_embed = nn.Sequential( + nn.Linear(config.hidden_size, config.hidden_size * 4), + nn.SiLU(), + nn.Linear(config.hidden_size * 4, config.hidden_size), + ) + + # Class embedding (for conditional generation) + if config.num_classes > 0: + self.class_embed = nn.Embedding(config.num_classes, config.hidden_size) + + # Transformer blocks + self.blocks = nn.ModuleList([ + DiTBlock(config) for _ in range(config.num_layers) + ]) + + # Output projection + self.output_proj = nn.Sequential( + nn.LayerNorm(config.hidden_size), + nn.Linear(config.hidden_size, config.patch_size ** 2 * config.out_channels), + ) + + # Initialize weights + self._init_weights() + + def forward(self, x, timesteps, context=None, class_labels=None, **kwargs): + """ + Forward pass through DiT. + + Args: + x: Noisy latents [B, C, H, W] + timesteps: Diffusion timesteps [B] + context: Text conditioning [B, S, D] (optional) + class_labels: Class labels [B] (optional) + + Returns: + Model prediction [B, C, H, W] + """ + B, C, H, W = x.shape + + # Patchify input + x = self.input_proj(x) # [B, hidden_size, H/p, W/p] + x = x.flatten(2).transpose(1, 2) # [B, N, hidden_size] + + # Add positional embedding + x = x + self.pos_embed + + # Embed timesteps + t_emb = self.time_embed(self._timestep_embedding(timesteps)) # [B, hidden_size] + + # Embed class labels (if provided) + if class_labels is not None and self.config.num_classes > 0: + c_emb = self.class_embed(class_labels) # [B, hidden_size] + # Combine with timestep embedding + cond = t_emb + c_emb + else: + cond = t_emb + + # Transformer blocks + for block in self.blocks: + x = block(x, cond, context) + + # Output projection + x = self.output_proj(x) # [B, N, p^2 * C] + + # Unpatchify + x = self._unpatchify(x, H, W) # [B, C, H, W] + + return x + + target: Ground truth target [B, C, H, W] + + Returns: + Loss scalar + """ + # Simple MSE loss (can be extended) + loss = nn.functional.mse_loss(model_output, target) + return loss + + def _timestep_embedding(self, timesteps): + """Create sinusoidal timestep embeddings.""" + # Standard sinusoidal embedding + half_dim = self.config.hidden_size // 2 + emb = torch.exp( + -torch.arange(half_dim, device=timesteps.device) * + (torch.log(torch.tensor(10000.0)) / (half_dim - 1)) + ) + emb = timesteps[:, None] * emb[None, :] + emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=-1) + return emb + + def _unpatchify(self, x, H, W): + """Convert patched tensor back to image.""" + p = self.config.patch_size + h = H // p + w = W // p + x = x.reshape(x.shape[0], h, w, p, p, self.config.out_channels) + x = x.permute(0, 5, 1, 3, 2, 4).contiguous() + x = x.reshape(x.shape[0], self.config.out_channels, H, W) + return x + + def _init_weights(self): + """Initialize model weights.""" + # Standard initialization (customize as needed) + pass + + +class DiTBlock(nn.Module): + """DiT transformer block with adaptive LayerNorm.""" + + def __init__(self, config): + super().__init__() + # Implementation details... + pass + + def forward(self, x, cond, context=None): + # Block forward pass + pass +``` + +### Step 4: Add model-specific layers (if needed) + +If your model has unique layers not shared with other models, add them to `layers.py`. + +### Step 5: Export model + +**File**: `__init__.py` + +```python +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +"""DiT model implementation.""" + +from .config import DiTConfig +from .model import DiT + +__all__ = [ + 'DiTConfig', + 'DiT', +] +``` + +### Step 6: Create configuration files + +**File**: `primus/configs/models/megatron/diffusion/dit_xl_2.yaml` + +```yaml +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# DiT-XL/2 Configuration + +model_type: dit + +# Architecture: Layers +num_layers: 28 + +# Architecture: Dimensions +hidden_size: 1152 +num_attention_heads: 16 + +# Input/Output Channels +in_channels: 4 # VAE latent channels +out_channels: 4 + +# Patchification +patch_size: 2 + +# Class Conditioning +num_classes: 1000 # ImageNet classes +class_dropout_prob: 0.1 + +# Adaptive LayerNorm +use_adaptive_layernorm: true + +# Precision Settings +bf16: true +fp16: false + +# Training +seq_length: 4096 +micro_batch_size: 8 +global_batch_size: 256 +learning_rate: 1.0e-4 +``` + +### Step 7: Write tests + +**File**: `tests/unit_tests/backends/megatron/diffusion/test_dit_model.py` + +```python +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +"""Unit tests for DiT model.""" + +import pytest +import torch +from primus.backends.megatron.core.models.diffusion.dit import DiT, DiTConfig + + +class TestDiTConfig: + """Tests for DiT configuration.""" + + def test_dit_xl_2_factory(self): + """Test DiT-XL/2 configuration factory method.""" + config = DiTConfig.dit_xl_2() + + assert config.model_type == "dit" + assert config.num_layers == 28 + assert config.hidden_size == 1152 + assert config.num_attention_heads == 16 + assert config.patch_size == 2 + + def test_dit_config_validation(self): + """Test configuration validation.""" + config = DiTConfig.dit_xl_2() + config.validate() # Should not raise + + # Invalid configuration + with pytest.raises(ValueError): + config = DiTConfig(num_layers=-1) + config.validate() + + +class TestDiTModel: + """Tests for DiT model.""" + + def test_dit_initialization(self): + """Test DiT model initialization.""" + config = DiTConfig.dit_xl_2() + model = DiT(config) + + assert model is not None + assert isinstance(model, DiffusionModule) + + def test_dit_forward_shapes(self): + """Test forward pass produces correct output shapes.""" + config = DiTConfig.dit_xl_2() + model = DiT(config) + + batch_size = 2 + latents = torch.randn(batch_size, 4, 32, 32) + timesteps = torch.rand(batch_size) + class_labels = torch.randint(0, 1000, (batch_size,)) + + output = model(latents, timesteps, class_labels=class_labels) + + assert output.shape == latents.shape + +### Loss Computation + +Use standalone loss functions from `loss_computation.py` instead of implementing loss as a method: + +```python +from primus.backends.megatron.training.diffusion.loss_computation import compute_flow_matching_loss + +# In your forward_step_func +target = noise - clean_latents +loss = compute_flow_matching_loss(prediction, clean_latents, noise) +``` + +Models do NOT implement loss as a method. Loss computation is: +- Separate from model architecture +- Reusable across models +- Testable independently + +if __name__ == "__main__": + pytest.main([__file__, "-v"]) +``` + +### Step 8: Update documentation + +1. Add model to `README.md` supported models list +2. Update `architecture_overview.md` with model-specific details +3. Create model-specific training guide (e.g., `dit_training.md`) + +### Step 9: Add example scripts + +**File**: Use `examples/run_pretrain.sh` with appropriate config + +```python +#!/usr/bin/env python3 +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +"""Training script for DiT model.""" + +import argparse +import yaml + +from primus.backends.megatron.core.models.diffusion.dit import DiT, DiTConfig +from primus.backends.megatron.training.diffusion.schedulers import DDPMScheduler +from primus.backends.megatron.data.dataloader import MegatronDataloaderWrapper +# ... other imports + + +def main(): + # Use examples/run_pretrain.sh with config from examples/megatron/configs/MI300X/diffusion/ + # MegatronDataloaderWrapper wraps an existing iterable (from dataset provider): + # dataloader = MegatronDataloaderWrapper(energon_loader_or_pytorch_loader) + # ... + + +if __name__ == "__main__": + main() +``` + +--- + +## Example: Adding DiT + +See the complete example in the step-by-step guide above. + +**Key Files Created**: +1. `core/models/diffusion/dit/config.py` - DiTConfig +2. `core/models/diffusion/dit/model.py` - DiT model +3. `configs/models/megatron/diffusion/dit_xl_2.yaml` - Config file +4. `tests/unit_tests/backends/megatron/diffusion/test_dit_model.py` - Tests +5. `examples/run_pretrain.sh` - Use with config from `examples/megatron/configs/MI300X/diffusion/` + +--- + +## Testing your model + +### Unit tests + +Run tests to verify implementation: + +```bash +# Run all DiT tests +pytest tests/unit_tests/backends/megatron/diffusion/test_dit_model.py -v + +# Run specific test +pytest tests/unit_tests/backends/megatron/diffusion/test_dit_model.py::TestDiTModel::test_dit_forward_shapes -v +``` + +### Integration tests + +Test with actual data: + +```bash +# Small dataset test +./examples/run_pretrain.sh --config examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain.yaml +``` + +### Validation + +Compare with reference implementation: +1. Load reference weights +2. Run same inputs through both models +3. Compare outputs (should match within tolerance) + +--- + +## Best practices + +### 1. Code organization +- ✅ Separate configuration from model code +- ✅ Reuse shared components from `common/` +- ✅ Keep model-specific code minimal +- ✅ Follow existing naming conventions + +### 2. Configuration +- ✅ Extend `BaseDiffusionConfig` +- ✅ Add factory methods for common sizes +- ✅ Implement validation +- ✅ Document all parameters + +### 3. Model implementation +- ✅ Extend `DiffusionModule` from `primus.backends.megatron.core.models.common.diffusion_module.diffusion_module` +- ✅ Implement required method: `forward()` +- ✅ Use standalone loss functions from `loss_computation.py` +- ✅ Add comprehensive docstrings +- ✅ Use type hints +- ✅ Include `pg_collection` parameter in `__init__` for distributed training + +### 4. Testing +- ✅ Test configuration validation +- ✅ Test model initialization +- ✅ Test forward pass shapes +- ✅ Test loss computation +- ✅ Test with mock data first + +### 5. Documentation +- ✅ Update README with new model +- ✅ Document architecture specifics +- ✅ Provide usage examples +- ✅ Reference original paper + +### 6. Performance +- ✅ Profile memory usage +- ✅ Optimize critical paths +- ✅ Support mixed precision +- ✅ Enable gradient checkpointing + +--- + +## Common pitfalls + +### 1. Import errors +❌ **Wrong**: Absolute imports +```python +from primus.backends.megatron.core.models.common.diffusion_module.diffusion_module import DiffusionModule +``` + +✅ **Right**: Relative imports +```python +from ...common.diffusion_module.diffusion_module import DiffusionModule +``` + +### 2. Configuration validation +❌ **Wrong**: No validation +```python +class DiTConfig(BaseDiffusionConfig): + pass # No validation +``` + +✅ **Right**: Validate parameters +```python +def validate(self): + super().validate() + if self.num_layers <= 0: + raise ValueError(f"num_layers must be positive") +``` + +### 3. Shape mismatches +❌ **Wrong**: Assuming fixed shapes +```python +def forward(self, x): + # Assumes x is always [B, 4, 32, 32] + pass +``` + +✅ **Right**: Handle variable shapes +```python +def forward(self, x): + B, C, H, W = x.shape + # Handle any valid shape + pass +``` + +### 4. Testing +❌ **Wrong**: No tests +```python +# Just implement and hope it works +``` + +✅ **Right**: Comprehensive tests +```python +def test_forward_shapes(self): + # Test various input shapes + pass +``` + +--- + +## Checklist + +Before submitting your new model: + +- [ ] Configuration class implemented and validated +- [ ] Model class extends `DiffusionModule` +- [ ] Required method implemented: `forward()` +- [ ] Loss computation uses standalone functions from `loss_computation.py` +- [ ] Process group support added (`pg_collection` parameter) +- [ ] YAML configuration files created +- [ ] Unit tests written and passing +- [ ] Integration tests run successfully +- [ ] Documentation updated +- [ ] Example training script provided +- [ ] Code follows Primus style guide +- [ ] No linter errors +- [ ] PR description includes architecture details + +--- + +## Getting help + +If you encounter issues: +1. Review existing models (Flux) for patterns +2. Check documentation in `docs/04-technical-guides/diffusion-models/` +3. Run tests in debug mode: `pytest --pdb` +4. Consult architecture overview for design principles + +--- + +**Last Updated**: December 2025 diff --git a/docs/04-technical-guides/diffusion-models/api_reference.md b/docs/04-technical-guides/diffusion-models/api_reference.md new file mode 100644 index 000000000..c08525eb2 --- /dev/null +++ b/docs/04-technical-guides/diffusion-models/api_reference.md @@ -0,0 +1,1059 @@ +# Flux model API reference + +## Overview + +This document provides comprehensive API reference for the Flux diffusion model implementation in Primus. Flux is a flow-based diffusion model that uses MMDiT (Multimodal Diffusion Transformer) architecture for high-quality text-to-image generation. + +--- + +## Base classes + +### DiffusionModule + +**Location**: `primus/backends/megatron/core/models/common/diffusion_module/diffusion_module.py` + +Base class for all diffusion models, providing Megatron-Core integration. + +```python +from primus.backends.megatron.core.models.common.diffusion_module import DiffusionModule +``` + +**Key Features**: +- Process group management (TP, PP, CP, DP) +- Attention backend configuration +- Distributed checkpointing support +- Common loss computation utilities + +**Inherited Methods** (available to all diffusion models): +- `get_num_params()` - Count trainable/total parameters +- `set_requires_grad()` - Freeze/unfreeze model +- `compute_diffusion_loss()` - Common loss helper (MSE, MAE, Huber) +- `sharded_state_dict()` - Distributed checkpointing + +> Note: in-model encoder loading (`load_encoders()`, `get_encoder_by_type()`, +> `get_encoders_by_type()`) is not implemented and raises `NotImplementedError`. +> Encode VAE/T5/CLIP inputs via the offline diffusion preprocessing pipeline +> (`primus.backends.megatron.data.diffusion.preprocessing`) instead. + +--- + +## Model architecture + +### Flux class + +**Location**: `primus/backends/megatron/core/models/diffusion/flux/model.py` + +```python +from primus.backends.megatron.core.models.diffusion.flux.model import Flux +from primus.backends.megatron.core.models.diffusion.flux.config import FluxConfig + +# Create model +config = FluxConfig.flux_535m() +model = Flux(config) +``` + +#### Architecture diagram + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Flux Model │ +├─────────────────────────────────────────────────────────────────┤ +│ │ +│ Input Processing: │ +│ ┌────────────┐ ┌────────────┐ │ +│ │ Image │──┐ ┌──│ Text │ │ +│ │ Latents │ │ │ │ Embeddings │ │ +│ │ [B,64,H,W] │ │ │ │ [B,S,4096] │ │ +│ └────────────┘ │ │ └────────────┘ │ +│ ▼ ▼ │ +│ ┌──────────────┐ │ +│ │ Linear Embed │ │ +│ └──────┬───────┘ │ +│ │ [B,seq,3072] │ +│ ▼ │ +│ ┌──────────────┐ │ +│ │ 3D RoPE │ │ +│ │ Position Emb │ │ +│ └──────┬───────┘ │ +│ │ │ +│ Conditioning: │ │ +│ ┌────────────┐ │ │ +│ │ Timestep │──┼──┐ │ +│ │ Embedding │ │ │ │ +│ └────────────┘ │ │ │ +│ ┌────────────┐ │ │ │ +│ │ CLIP │──┼──┤ │ +│ │ Pooled │ │ │ vec_emb [B,3072] │ +│ └────────────┘ │ │ │ +│ ┌────────────┐ │ │ │ +│ │ Guidance │──┼──┘ │ +│ │ (optional) │ │ │ +│ └────────────┘ │ │ +│ │ │ +│ Double Blocks │ │ +│ (Joint): │ │ +│ ┌────────────────▼──────────┐ │ +│ │ MMDiTLayer x N_joint │ N_joint = 1 (535M), 19 (12B) │ +│ │ ┌──────────────────────┐ │ │ +│ │ │ Joint Self-Attention │ │ (image + text together) │ +│ │ └──────────────────────┘ │ │ +│ │ ┌──────────────────────┐ │ │ +│ │ │ Image MLP │ │ │ +│ │ └──────────────────────┘ │ │ +│ │ ┌──────────────────────┐ │ │ +│ │ │ Text MLP │ │ │ +│ │ └──────────────────────┘ │ │ +│ └────────────┬──────────────┘ │ +│ │ │ +│ Single Blocks│ │ +│ (Combined): │ │ +│ ┌────────────▼──────────────┐ │ +│ │ FluxSingleTransformer │ N_single = 1 (535M), 38 (12B) │ +│ │ Block x N_single │ │ +│ │ ┌──────────────────────┐ │ │ +│ │ │ Self-Attention │ │ (image + text concatenated) │ +│ │ └──────────────────────┘ │ │ +│ │ ┌──────────────────────┐ │ │ +│ │ │ MLP │ │ │ +│ │ └──────────────────────┘ │ │ +│ └────────────┬──────────────┘ │ +│ │ (extract image tokens) │ +│ ▼ │ +│ Output Processing: │ +│ ┌────────────────────────┐ │ +│ │ AdaLNContinuous │ │ +│ │ (timestep conditioned) │ │ +│ └────────────┬───────────┘ │ +│ ▼ │ +│ ┌────────────────────────┐ │ +│ │ Linear Projection │ │ +│ └────────────┬───────────┘ │ +│ ▼ │ +│ [B,64,H,W] │ +│ Predicted Velocity │ +└─────────────────────────────────────────────────────────────────┘ +``` + +#### Parameter counts + +| Variant | Joint Layers | Single Layers | Total Parameters | Use Case | +|---------|-------------|---------------|------------------|----------| +| Flux 535M | 1 | 1 | ~535 million | Testing, debugging, prototyping | +| Flux 12B | 19 | 38 | ~12 billion | Production training | + +--- + +## Configuration + +### FluxConfig + +**Location**: `primus/backends/megatron/core/models/diffusion/flux/config.py` + +Complete configuration class for Flux models, inheriting from `BaseDiffusionConfig`. + +#### Key parameters + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `num_joint_layers` | int | 19 | Number of joint (MMDiT) transformer layers | +| `num_single_layers` | int | 38 | Number of single transformer layers | +| `hidden_size` | int | 3072 | Hidden dimension size | +| `num_attention_heads` | int | 24 | Number of attention heads | +| `in_channels` | int | 64 | Input channels (VAE latent dimension) | +| `context_dim` | int | 4096 | Text context dimension (T5-XXL) | +| `vec_in_dim` | int | 768 | Vector input dimension (CLIP pooled) | +| `model_channels` | int | 256 | Channels for timestep embedding | +| `guidance_embed` | bool | False | Enable guidance embedding for CFG | +| `guidance_scale` | float | 3.5 | Guidance scale for classifier-free guidance | +| `theta` | int | 10000 | Base for RoPE frequency computation | +| `axes_dim` | tuple | (16, 56, 56) | Dimensions for 3D RoPE axes | +| `patch_size` | int | 1 | Patch size for image tokens | +| `add_qkv_bias` | bool | True | Add bias to QKV projections | +| `rotary_interleaved` | bool | True | Interleave RoPE dimensions | +| `layernorm_epsilon` | float | 1e-6 | Epsilon for layer normalization | +| `hidden_dropout` | float | 0.0 | Hidden layer dropout rate | +| `attention_dropout` | float | 0.0 | Attention dropout rate | + +#### Configuration examples + +**Flux 535M (Testing)**: +```python +config = FluxConfig.flux_535m() +# Equivalent to: +config = FluxConfig( + num_joint_layers=1, + num_single_layers=1, + hidden_size=3072, + num_attention_heads=24, +) +``` + +**Flux 12B (Production)**: +```python +config = FluxConfig.flux_12b() +# Equivalent to: +config = FluxConfig( + num_joint_layers=19, + num_single_layers=38, + hidden_size=3072, + num_attention_heads=24, +) +``` + +**Custom Configuration**: +```python +config = FluxConfig( + num_joint_layers=4, + num_single_layers=8, + hidden_size=2048, + num_attention_heads=16, + guidance_embed=True, + patch_size=2, +) +``` + +--- + +## Components API + +### Embeddings + +#### TimeStepEmbedder + +**Location**: `primus/backends/megatron/core/models/diffusion/common/embeddings.py` + +Converts scalar timesteps to high-dimensional embeddings using sinusoidal encoding. + +```python +from primus.backends.megatron.core.models.diffusion.common.embeddings import TimeStepEmbedder + +embedder = TimeStepEmbedder(embedding_dim=256, hidden_dim=3072) +timesteps = torch.tensor([0, 100, 500, 999]) # [B] +t_emb = embedder(timesteps) # [B, 3072] +``` + +**Input**: Timesteps [B] in range [0, 1000] +**Output**: Embeddings [B, hidden_dim] + +#### MLPEmbedder + +Embeds vector conditioning (e.g., CLIP pooled embeddings) via 2-layer MLP. + +```python +from primus.backends.megatron.core.models.diffusion.common.embeddings import MLPEmbedder + +embedder = MLPEmbedder(in_dim=768, hidden_dim=3072) +clip_pooled = torch.randn(4, 768) # [B, 768] +embedded = embedder(clip_pooled) # [B, 3072] +``` + +**Input**: Vectors [B, in_dim] +**Output**: Embeddings [B, hidden_dim] + +--- + +### Normalization + +#### AdaLN (adaptive layer normalization) + +**Location**: `primus/backends/megatron/core/models/diffusion/common/normalization.py` + +Applies layer normalization conditioned on timestep embeddings. + +```python +from megatron.core.transformer.transformer_config import TransformerConfig +from primus.backends.megatron.core.models.diffusion.common.normalization import AdaLN + +config = TransformerConfig(hidden_size=3072) +adaln = AdaLN(config, n_adaln_chunks=6) + +timestep_emb = torch.randn(4, 3072) +shift, scale, gate, shift_mlp, scale_mlp, gate_mlp = adaln(timestep_emb) +# Each output: [B, 3072] +``` + +**Methods**: +- `forward(timestep_emb)`: Generate modulation parameters +- `modulate(x, shift, scale)`: Apply adaptive modulation +- `scale_add(residual, x, gate)`: Gated residual addition + +#### AdaLNContinuous + +Continuous variant of AdaLN for Flux output normalization. + +```python +from primus.backends.megatron.core.models.diffusion.common.normalization import AdaLNContinuous + +adaln = AdaLNContinuous(config, conditioning_embedding_dim=3072) +x = torch.randn(4, 256, 3072) # [B, seq, hidden] +cond = torch.randn(4, 3072) # [B, cond_dim] +x_norm = adaln(x, cond) # [B, seq, hidden] +``` + +#### RMSNorm + +Root Mean Square Layer Normalization (simpler, faster than LayerNorm). + +```python +from primus.backends.megatron.core.models.diffusion.common.normalization import RMSNorm + +norm = RMSNorm(hidden_size=3072) +x = torch.randn(4, 256, 3072) +x_norm = norm(x) +``` + +--- + +### Position embeddings + +#### EmbedND (3D RoPE) + +**Location**: `primus/backends/megatron/core/models/diffusion/flux/layers.py` + +Multi-dimensional Rotary Position Embedding for image patches. + +```python +from primus.backends.megatron.core.models.diffusion.flux.layers import ( + EmbedND, +) +from primus.backends.megatron.core.models.diffusion.flux.utils import ( + generate_image_position_ids, +) + +# Initialize +embed_nd = EmbedND(dim=3072, theta=10000, axes_dim=[16, 56, 56]) + +# Generate position IDs for 56x56 image patches +batch_size = 2 +height, width = 112, 112 # Unpacked dimensions (56*2, 56*2) +img_ids = generate_image_position_ids(batch_size, height, width) +# img_ids: [B, H*W/4, 3] where dimension 0 is always 0 + +# Get RoPE frequencies +rope_freqs = embed_nd(img_ids) # [3, B, H*W, 3072] +``` + +**Axes**: +- Axis 0: Channel groups (16 for 64 channels) +- Axis 1: Height positions (56 for 1024px image) +- Axis 2: Width positions (56 for 1024px image) + +--- + +### Attention mechanisms + +#### JointSelfAttention + +**Location**: `primus/backends/megatron/core/models/diffusion/flux/attention.py` + +Joint attention over image and text tokens (MMDiT architecture). + +```python +from primus.backends.megatron.core.models.diffusion.flux.attention import ( + JointSelfAttention, + JointSelfAttentionSubmodules, +) + +submodules = JointSelfAttentionSubmodules(...) +joint_attn = JointSelfAttention(config, submodules, layer_number=0) + +# Forward +img_tokens = torch.randn(3136, 2, 3072) # [seq_img, B, hidden] +txt_tokens = torch.randn(512, 2, 3072) # [seq_txt, B, hidden] +img_out, txt_out = joint_attn( + img_tokens, + attention_mask=None, + additional_hidden_states=txt_tokens, +) +``` + +**Input**: +- `hidden_states`: Image tokens [seq_img, B, hidden] +- `additional_hidden_states`: Text tokens [seq_txt, B, hidden] + +**Output**: Tuple of (img_output, txt_output) + +#### FluxSingleAttention + +Single-stream self-attention for image tokens only. + +```python +from primus.backends.megatron.core.models.diffusion.flux.attention import FluxSingleAttention + +single_attn = FluxSingleAttention(config, submodules, layer_number=0) + +img_tokens = torch.randn(3136, 2, 3072) +output = single_attn(img_tokens, attention_mask=None) +``` + +--- + +### Layer specifications + +#### MMDiTLayer + +**Location**: `primus/backends/megatron/core/models/diffusion/flux/layer_spec.py` + +Joint image-text transformer block. + +```python +from primus.backends.megatron.core.models.diffusion.flux.layer_spec import ( + MMDiTLayer, + get_flux_double_transformer_spec_for_backend, +) + +# Using factory function (recommended) +spec = get_flux_double_transformer_spec_for_backend(backend) +mmdit_layer = MMDiTLayer( + config=config, + submodules=spec.submodules, + layer_number=0, +) + +# Forward +img_tokens = torch.randn(3136, 2, 3072) +txt_tokens = torch.randn(512, 2, 3072) +emb = torch.randn(2, 3072) +img_out, txt_out = mmdit_layer(img_tokens, txt_tokens, emb=emb) +``` + +#### FluxSingleTransformerBlock + +Image-only transformer block. + +```python +from primus.backends.megatron.core.models.diffusion.flux.layer_spec import ( + FluxSingleTransformerBlock, + get_flux_single_transformer_spec_for_backend, +) + +spec = get_flux_single_transformer_spec_for_backend(backend) +single_block = FluxSingleTransformerBlock( + config=config, + submodules=spec.submodules, + layer_number=0, +) + +# Forward +img_tokens = torch.randn(3136, 2, 3072) +emb = torch.randn(2, 3072) +output, _ = single_block(img_tokens, emb=emb) +``` + +--- + +## Training utilities + +### Noise application + +**Location**: `primus/backends/megatron/training/diffusion/noise_utils.py` + +Pure functions for applying noise according to different diffusion forward processes. + +#### apply_flow_matching_noise() + +```python +from primus.backends.megatron.training.diffusion.noise_utils import apply_flow_matching_noise + +clean_latents = torch.randn(2, 16, 64, 64) +noise = torch.randn(2, 16, 64, 64) +sigma = torch.tensor([0.3, 0.7]).reshape(2, 1, 1, 1) + +noisy = apply_flow_matching_noise(clean_latents, noise, sigma) +# Formula: noisy = (1 - sigma) * clean + sigma * noise +``` + +**Parameters**: +- `clean_latents` (Tensor): Clean latents [any shape] +- `noise` (Tensor): Sampled noise [same shape as clean_latents] +- `sigma` (Tensor): Noise schedule values [broadcast compatible], range [0, 1] + +**Returns**: Noisy latents [same shape as clean_latents] + +**Reference**: [Flow Matching for Generative Modeling](https://arxiv.org/abs/2210.02747) + +#### apply_ddpm_noise() + +```python +from primus.backends.megatron.training.diffusion.noise_utils import apply_ddpm_noise + +clean = torch.randn(2, 3, 256, 256) +noise = torch.randn(2, 3, 256, 256) +alpha_bar = torch.tensor([0.9, 0.5]).reshape(2, 1, 1, 1) + +noisy = apply_ddpm_noise(clean, noise, alpha_bar) +# Formula: noisy = sqrt(alpha_bar) * clean + sqrt(1 - alpha_bar) * noise +``` + +**Parameters**: +- `clean_latents` (Tensor): Clean latents +- `noise` (Tensor): Sampled noise (same shape) +- `alpha_bar` (Tensor): Cumulative product of alphas, range (0, 1] + +**Returns**: Noisy latents + +**Reference**: [Denoising Diffusion Probabilistic Models](https://arxiv.org/abs/2006.11239) + +--- + +### Loss computation + +**Location**: `primus/backends/megatron/training/diffusion/loss_computation.py` + +Reusable loss computation logic for different diffusion training objectives. + +#### compute_flow_matching_loss() + +```python +from primus.backends.megatron.training.diffusion.loss_computation import compute_flow_matching_loss + +prediction = torch.randn(2, 16, 64, 64) # Model output +clean = torch.randn(2, 16, 64, 64) +noise = torch.randn(2, 16, 64, 64) + +loss = compute_flow_matching_loss(prediction, clean, noise) +# Formula: target = noise - clean +# loss = MSE(prediction, target) +``` + +**Parameters**: +- `prediction` (Tensor): Model output (predicted velocity) [any shape] +- `clean_latents` (Tensor): Original clean latents [same shape] +- `noise` (Tensor): Sampled noise [same shape] + +**Returns**: Scalar loss value (mean squared error) + +**Used by**: Flux, SD3, video models + +#### compute_epsilon_loss() + +```python +from primus.backends.megatron.training.diffusion.loss_computation import compute_epsilon_loss + +prediction = torch.randn(2, 3, 256, 256) +noise = torch.randn(2, 3, 256, 256) + +loss = compute_epsilon_loss(prediction, noise) +# Formula: loss = MSE(prediction, noise) +``` + +**Parameters**: +- `prediction` (Tensor): Model output (predicted noise) +- `noise` (Tensor): Sampled noise (ground truth) + +**Returns**: Scalar loss value + +**Used by**: DDPM and older models + +#### compute_v_prediction_loss() + +```python +from primus.backends.megatron.training.diffusion.loss_computation import compute_v_prediction_loss + +prediction = torch.randn(2, 16, 64, 64) +clean = torch.randn(2, 16, 64, 64) +noise = torch.randn(2, 16, 64, 64) +sigma = torch.tensor([0.3, 0.7]).reshape(2, 1, 1, 1) + +loss = compute_v_prediction_loss(prediction, clean, noise, sigma) +# Formula: v = sigma * noise - (1 - sigma) * clean +# loss = MSE(prediction, v) +``` + +**Parameters**: +- `prediction` (Tensor): Model output +- `clean_latents` (Tensor): Clean latents +- `noise` (Tensor): Sampled noise +- `sigma` (Tensor): Noise schedule values [broadcast compatible] + +**Returns**: Scalar loss value + +**Reference**: [Progressive Distillation for Fast Sampling](https://arxiv.org/abs/2202.00512) + +--- + +### Timestep sampling + +**Location**: `primus/backends/megatron/training/diffusion/timestep_sampling.py` + +Sampling strategies for training timesteps (hyperparameter optimization, separate from inference). + +#### LogitNormalSampler + +```python +from primus.backends.megatron.training.diffusion.timestep_sampling import LogitNormalSampler + +sampler = LogitNormalSampler(mean=0.0, std=1.0) +timesteps, sigmas = sampler.sample( + batch_size=32, + device='cuda', + scheduler=flow_scheduler +) +``` + +**Description**: Logit-normal distribution sampling, emphasizes boundary timesteps (t≈0 and t≈1000). + +**Parameters**: +- `mean` (float): Mean of normal distribution (default: 0.0) +- `std` (float): Standard deviation (default: 1.0) + +**Returns**: Tuple of (timesteps [B], sigmas [B]) + +**Reference**: [Stable Diffusion 3](https://arxiv.org/abs/2403.03206v1), Section 3.1 + +**Used by**: Flux, SD3 + +#### UniformSampler + +```python +from primus.backends.megatron.training.diffusion.timestep_sampling import UniformSampler + +sampler = UniformSampler() +timesteps, sigmas = sampler.sample(batch_size=32, device='cuda', scheduler=flow_scheduler) +``` + +**Description**: Uniform timestep sampling (baseline approach for comparison). + +**Returns**: Tuple of (timesteps [B], sigmas [B]) + +#### ModeSampler + +```python +from primus.backends.megatron.training.diffusion.timestep_sampling import ModeSampler + +sampler = ModeSampler(mode_scale=1.29) +timesteps, sigmas = sampler.sample(batch_size=32, device='cuda', scheduler=flow_scheduler) +``` + +**Description**: Mode-based sampling from SD3 paper (alternative to logit-normal). + +**Parameters**: +- `mode_scale` (float): Scaling factor (default: 1.29 from SD3 paper) + +**Returns**: Tuple of (timesteps [B], sigmas [B]) + +#### create_timestep_sampler() + +```python +from primus.backends.megatron.training.diffusion.timestep_sampling import create_timestep_sampler + +# Factory function for easy experimentation +sampler = create_timestep_sampler("logit_normal", mean=0.0, std=1.0) +sampler = create_timestep_sampler("uniform") +sampler = create_timestep_sampler("mode", mode_scale=1.5) +``` + +**Parameters**: +- `strategy` (str): Sampling strategy ("logit_normal", "uniform", "mode") +- `**kwargs`: Additional arguments for the sampler + +**Returns**: TimestepSampler instance + +--- + +### Training workflow example + +Complete training step using the utilities: + +```python +import torch +from torch.optim import AdamW +from primus.backends.megatron.core.models.diffusion.flux import Flux, FluxConfig +from primus.backends.megatron.training.diffusion.noise_utils import apply_flow_matching_noise +from primus.backends.megatron.training.diffusion.loss_computation import compute_flow_matching_loss +from primus.backends.megatron.training.diffusion.timestep_sampling import LogitNormalSampler +from primus.backends.megatron.training.diffusion.schedulers.flow_match_euler import ( + FlowMatchEulerDiscreteScheduler +) + +# Setup +config = FluxConfig.flux_535m() +model = Flux(config).cuda() +optimizer = AdamW(model.parameters(), lr=1e-4) + +# Initialize scheduler and timestep sampler +scheduler = FlowMatchEulerDiscreteScheduler(num_train_timesteps=1000) +timestep_sampler = LogitNormalSampler(mean=0.0, std=1.0) + +# Training loop +for batch in dataloader: + clean_latents = batch['latents'].cuda() # [B, 16, 64, 64] + txt_embeddings = batch['text'].cuda() # [B, 512, 4096] + clip_pooled = batch['clip'].cuda() # [B, 768] + + batch_size = clean_latents.shape[0] + + # 1. Sample timesteps + timesteps, sigmas = timestep_sampler.sample( + batch_size=batch_size, + device='cuda', + scheduler=scheduler + ) + + # 2. Sample noise + noise = torch.randn_like(clean_latents) + + # 3. Apply noise + sigma_reshaped = sigmas.view(-1, 1, 1, 1) + noisy_latents = apply_flow_matching_noise(clean_latents, noise, sigma_reshaped) + + # 4. Prepare position IDs + img_ids = generate_image_position_ids(batch_size, 128, 128).cuda() + txt_ids = torch.zeros(batch_size, 512, 3).cuda() + + # 5. Forward pass + predicted_velocity = model( + img=noisy_latents, + txt=txt_embeddings, + y=clip_pooled, + timesteps=sigmas, + img_ids=img_ids, + txt_ids=txt_ids, + ) + + # 6. Compute loss + loss = compute_flow_matching_loss(predicted_velocity, clean_latents, noise) + + # 7. Backward and optimize + optimizer.zero_grad() + loss.backward() + optimizer.step() + + print(f"Step {step}, Loss: {loss.item():.4f}") +``` + +--- + +## Usage examples + +### Basic inference + +```python +import torch +from primus.backends.megatron.core.models.diffusion.flux.model import Flux +from primus.backends.megatron.core.models.diffusion.flux.config import FluxConfig +from primus.backends.megatron.core.models.diffusion.flux.utils import generate_image_position_ids + +# 1. Setup model +config = FluxConfig.flux_535m() +model = Flux(config) +model.eval() + +# 2. Prepare inputs +batch_size = 1 +img_latents = torch.randn(batch_size, 64, 128, 128) # VAE latents +txt_embeddings = torch.randn(batch_size, 512, 4096) # T5-XXL embeddings +clip_pooled = torch.randn(batch_size, 768) # CLIP-L pooled +timesteps = torch.tensor([0.5]) # Diffusion timestep [0, 1] + +# 3. Generate position IDs +img_ids = generate_image_position_ids(batch_size, 256, 256) +txt_ids = torch.zeros(batch_size, 512, 3) + +# 4. Forward pass +with torch.no_grad(): + predicted_velocity = model( + img=img_latents, + txt=txt_embeddings, + y=clip_pooled, + timesteps=timesteps, + img_ids=img_ids, + txt_ids=txt_ids, + ) + +print(f"Output shape: {predicted_velocity.shape}") # [1, 64, 128, 128] +``` + +### Training step + +```python +import torch +from torch.optim import AdamW + +# Setup +config = FluxConfig.flux_535m() +model = Flux(config) +model.train() + +optimizer = AdamW(model.parameters(), lr=1e-4, weight_decay=0.01) + +# Prepare batch +batch_size = 4 +img = torch.randn(batch_size, 64, 128, 128) +txt = torch.randn(batch_size, 512, 4096) +y = torch.randn(batch_size, 768) +timesteps = torch.rand(batch_size) + +img_ids = generate_image_position_ids(batch_size, 256, 256) +txt_ids = torch.zeros(batch_size, 512, 3) + +# Add noise for flow matching +original = img.clone() +noise = torch.randn_like(img) +t = timesteps.view(-1, 1, 1, 1) +noisy_img = original + noise * t + +# Velocity target for flow matching +velocity_target = noise - original + +# Training step +optimizer.zero_grad() + +# Forward pass +output = model(noisy_img, txt, y, timesteps, img_ids, txt_ids) + +# Loss computation (using standalone function) +from primus.backends.megatron.training.diffusion.loss_computation import compute_flow_matching_loss +target = noise - clean_latents +loss = compute_flow_matching_loss(output, clean_latents, noise) + +# Backward pass +loss.backward() + +# Optimizer step +optimizer.step() + +print(f"Loss: {loss.item():.4f}") +``` + +### With guidance (classifier-free guidance) + +```python +# Enable guidance in config +config = FluxConfig.flux_535m(guidance_embed=True) +model = Flux(config) +model.eval() + +# Prepare inputs with guidance +guidance_scale = torch.tensor([3.5]) # Typical guidance scale + +with torch.no_grad(): + output = model( + img=img_latents, + txt=txt_embeddings, + y=clip_pooled, + timesteps=timesteps, + img_ids=img_ids, + txt_ids=txt_ids, + guidance=guidance_scale, # Add guidance + ) +``` + +### Different resolutions + +```python +# Flux can handle different resolutions +resolutions = [ + (64, 64), # 512x512 pixels + (128, 128), # 1024x1024 pixels + (192, 192), # 1536x1536 pixels +] + +for height, width in resolutions: + img = torch.randn(1, 64, height, width) + img_ids = generate_image_position_ids(1, height, width) + + with torch.no_grad(): + output = model(img, txt, y, timesteps, img_ids, txt_ids) + + assert output.shape == img.shape +``` + +--- + +## Methods reference + +### Flux.forward() + +```python +def forward( + self, + img: Tensor, # [B, C, H, W] Image latents from VAE + txt: Tensor, # [B, S_txt, D_txt] T5-XXL embeddings + y: Tensor, # [B, D_pool] CLIP pooled embeddings + timesteps: Tensor, # [B] Timesteps in [0, 1] + img_ids: Tensor, # [B, H*W, 3] Image position IDs + txt_ids: Tensor, # [B, S_txt, 3] Text position IDs + guidance: Optional[Tensor] = None, # [B] Guidance scale + controlnet_double_block_samples: Optional[Tensor] = None, + controlnet_single_block_samples: Optional[Tensor] = None, +) -> Tensor: # Returns: [B, C, H, W] Predicted velocity +``` + +## Loss computation + +Loss is computed using standalone functions from `loss_computation.py`: + +### compute_flow_matching_loss() + +```python +from primus.backends.megatron.training.diffusion.loss_computation import compute_flow_matching_loss + +def compute_flow_matching_loss( + prediction: Tensor, # [any shape] Model prediction + clean_latents: Tensor, # [same shape] Original clean latents + noise: Tensor, # [same shape] Sampled noise +) -> Tensor: # Returns: Scalar loss +``` + +### Flux.get_num_params() + +```python +def get_num_params( + self, + trainable_only: bool = True, +) -> int: # Returns: Number of parameters +``` + +--- + +## Testing + +### Running tests + +```bash +# Run all Flux tests +pytest tests/unit_tests/backends/megatron/diffusion/ -v + +# Run specific test files +pytest tests/unit_tests/backends/megatron/diffusion/test_flux_embeddings.py -v +pytest tests/unit_tests/backends/megatron/diffusion/test_flux_normalization.py -v +pytest tests/unit_tests/backends/megatron/diffusion/test_flux_config.py -v +pytest tests/unit_tests/backends/megatron/diffusion/test_flux_layers.py -v +pytest tests/unit_tests/backends/megatron/diffusion/test_flux_model.py -v +pytest tests/unit_tests/backends/megatron/diffusion/test_flux_layer_spec_backend_selection.py -v + +# Run with coverage +pytest tests/unit_tests/backends/megatron/diffusion/ --cov=primus.backends.megatron.core.models.diffusion --cov-report=html +``` + +### Test coverage + +- **Component tests**: 80+ tests covering all components +- **Model tests**: 17+ tests for full model +- **Integration tests**: 11+ tests for complete workflows + +--- + +## Performance considerations + +### Memory usage + +| Configuration | Model Weights | Training (bf16) | Training (fp32) | +|--------------|---------------|-----------------|-----------------| +| Flux 535M | ~2 GB | ~6-8 GB | ~10-12 GB | +| Flux 12B | ~24 GB | ~60-80 GB | ~100-120 GB | + +### Throughput (estimated) + +On MI300X (192GB): +- **Flux 535M**: ~5-10 samples/sec (depends on resolution) +- **Flux 12B**: ~0.5-1 samples/sec (requires multi-GPU) + +### Optimization tips + +1. **Use mixed precision**: `torch.autocast(device_type='cuda', dtype=torch.bfloat16)` +2. **Enable CUDA graphs**: Set `enable_cuda_graph=True` in config +3. **Use Transformer Engine**: Automatically used with factory functions +4. **Gradient checkpointing**: Can be enabled for memory savings + +--- + +## Common issues + +### Issue: Import errors + +```python +# Old import style +from some_other_library import Flux + +# Correct Primus import +from primus.backends.megatron.core.models.diffusion.flux.model import Flux +``` + +### Issue: Position ID shape mismatch + +```python +# Position IDs must be [B, seq, 3] for 3D RoPE +img_ids = generate_image_position_ids(batch_size, height, width) +# Not: img_ids = torch.randn(batch_size, height * width, 2) # Wrong! +``` + +### Issue: Timestep range + +```python +# Flux expects timesteps in [0, 1] +timesteps = torch.rand(batch_size) # Correct: [0, 1] +# Not: timesteps = torch.randint(0, 1000, (batch_size,)) # Wrong range! +``` + +--- + +## API compatibility + +### Primus architecture features + +| Aspect | Primus Implementation | +|--------|----------------------| +| Import path | `primus.backends.megatron.core.models.diffusion.flux` | +| Base class | `DiffusionModule` (extends MegatronModule) | +| Config parent | `BaseDiffusionConfig` (extends TransformerConfig) | +| Layer organization | Unified `TransformerBlock` with heterogeneous specs | +| Checkpoint format | `transformer.layers.{0-56}` unified namespace | +| Process groups | Via `pg_collection` parameter | + +### Key design choices + +**TransformerBlock Architecture**: +- Primus uses Megatron-Core's `TransformerBlock` with heterogeneous layer specifications +- Unified checkpoint format for simpler distributed training +- Note: pipeline parallelism is not supported for diffusion models (`pipeline_model_parallel_size` must be 1) + +**Example Usage**: + +```python +# Primus native approach +from primus.backends.megatron.core.models.diffusion.flux import Flux, FluxConfig + +# Primus +from primus.backends.megatron.core.models.diffusion.flux.model import Flux +from primus.backends.megatron.core.models.diffusion.flux.config import FluxConfig +config = FluxConfig() +model = Flux(config) +``` + +For more advanced examples, see `examples/run_pretrain.sh`. + +--- + +## References + +### Papers +- **Flux**: "Flux: A Scalable Diffusion Model for High-Resolution Image Synthesis" +- **MMDiT**: "Scaling Rectified Flow Transformers for High-Resolution Image Synthesis" +- **Flow Matching**: "Flow Matching for Generative Modeling" +- **RoPE**: "RoFormer: Enhanced Transformer with Rotary Position Embedding" +- **DiT**: "Scalable Diffusion Models with Transformers" (Peebles & Xie, 2023) + +### Source code +- **Primus Implementation**: `primus/backends/megatron/core/models/diffusion/flux/` +- **Megatron-Core**: `megatron/core/transformer/` +- **Official Flux**: Black Forest Labs (HuggingFace) + +--- + +## Version information + +- **Primus Version**: Current +- **Megatron-Core Version**: Latest +- **Transformer Engine**: Optional (recommended for performance) + +--- + +## Support + +For issues or questions: +1. Check this API reference +2. Read architecture guide: `docs/04-technical-guides/diffusion-models/flux_architecture.md` +3. See examples in docstrings +4. Check test files for usage patterns diff --git a/docs/04-technical-guides/diffusion-models/architecture_overview.md b/docs/04-technical-guides/diffusion-models/architecture_overview.md new file mode 100644 index 000000000..8ef25764f --- /dev/null +++ b/docs/04-technical-guides/diffusion-models/architecture_overview.md @@ -0,0 +1,499 @@ +# Architecture overview + +This document provides a detailed overview of the diffusion model architecture in Primus, including design decisions, directory structure, and implementation patterns. + +--- + +## Table of contents + +1. [Design philosophy](#design-philosophy) +2. [Directory structure](#directory-structure) +3. [Architectural decisions](#architectural-decisions) +4. [Component hierarchy](#component-hierarchy) +5. [Data flow](#data-flow) +6. [Comparison with alternative implementations](#comparison-with-alternative-implementations) + +--- + +## Design philosophy + +### Core principles + +1. **Megatron-Core Native**: Built on Megatron-Core patterns and conventions +2. **Extensibility**: Easy to add new models (DiT, MovieGen, custom) +3. **Clarity**: Clear separation between shared and model-specific code +4. **Reusability**: Shared components across multiple models +5. **Performance**: Support for precalculated data and multi-GPU training + +### Architectural advantages + +| Aspect | Primus Design Choice | +|--------|---------------------| +| Model Location | `core/models/diffusion/` (Megatron-Core convention) | +| Layer Organization | Unified `TransformerBlock` with heterogeneous specs | +| Shared Code | Dedicated `common/` directory | +| Encoders | Hierarchical `encoders/{type}/{variant}/` | +| Energon | Shared `data/energon/` for all models | +| Mock Data | Synthetic providers under `data/synthetic/` | +| Framework | Pure Megatron (no PyTorch Lightning dependency) | + +--- + +## Directory structure + +### High-level organization + +``` +primus/backends/megatron/ +├── core/models/diffusion/ # Core model implementations +├── training/diffusion/ # Training utilities (schedulers, etc.) +└── data/ + ├── energon/ # Shared Energon infrastructure + └── diffusion/ # Diffusion-specific data +``` + +### Detailed breakdown + +#### 1. Core models (`core/models/diffusion/`) + +Following Megatron-Core convention (`megatron/core/models/gpt/`, `megatron/core/models/multimodal/`): + +``` +core/models/diffusion/ +├── common/ # Shared building blocks +│ ├── __init__.py +│ ├── config.py # BaseDiffusionConfig +│ ├── embeddings.py # TimeStepEmbedder, Timesteps, MLPEmbedder +│ └── normalization.py # RMSNorm, AdaLN, AdaLNContinuous +│ +└── flux/ # Flux-specific components + ├── __init__.py + ├── config.py # FluxConfig + ├── model.py # Flux (main model class) + ├── layer_spec.py # MMDiTLayer, FluxSingleTransformerBlock + ├── attention.py # JointSelfAttention, FluxSingleAttention + ├── layers.py # EmbedND (RoPE), embedders + ├── checkpoint_converter.py # HF <-> Megatron conversion + └── utils.py # image position ids, helpers +``` + +**Rationale**: +- `common/`: shared building blocks (base config, timestep/positional embeddings, normalization) reusable across diffusion models +- `flux/`: Flux-specific code (model class, MMDiT and single-block layer specs, joint attention, RoPE embedders) +- Clear separation keeps model-specific code isolated and shared code reusable for future models + +#### 2. Training (`training/diffusion/`) + +``` +training/diffusion/ +├── __init__.py +└── schedulers/ + ├── __init__.py + ├── base.py # BaseScheduler + ├── flow_matching.py # FlowMatchEulerDiscreteScheduler + ├── ddpm.py # [Future] DDPMScheduler + ├── edm.py # [Future] EDMScheduler + └── euler.py # [Future] EulerDiscreteScheduler +``` + +**Rationale**: +- Schedulers define noise schedules and training targets +- Separate from model code for clarity +- Easy to add new schedulers (DDPM, EDM, etc.) + +#### 3. Data pipeline (`data/`) + +``` +data/ +├── energon/ # Shared Energon utilities +│ └── __init__.py +├── dataloader.py # MegatronDataloaderWrapper (wraps any iterable) +│ +└── diffusion/ + ├── __init__.py + ├── encoders/ # Hierarchical encoder registry + │ ├── __init__.py + │ ├── registry.py # EncoderRegistry + │ ├── image/ + │ │ └── vae/ + │ │ ├── __init__.py + │ │ ├── autoencoder_kl.py # AutoencoderKL + │ │ └── vqvae.py # VQVAE + │ └── text/ + │ ├── t5/ + │ │ ├── __init__.py + │ │ ├── t5_xxl.py # T5-XXL + │ │ └── t5_large.py # T5-Large + │ └── clip/ + │ ├── __init__.py + │ ├── clip_l.py # CLIP-L + │ └── clip_h.py # CLIP-H + │ + ├── preprocessing/ + │ ├── __init__.py + │ └── image/ + │ ├── __init__.py + │ ├── transforms.py # Resizing, normalization + │ └── augmentation.py # Data augmentation + │ + └── task_encoders/ + ├── __init__.py + └── image.py # EncodedDiffusionTaskEncoder +``` + +**Rationale**: +- **Energon shared**: VLM and other models can reuse Energon utilities +- **Hierarchical encoders**: Organized by modality and variant type +- **Registry pattern**: Config-driven encoder selection +- **Preprocessing separated**: Clear pipeline stages + +--- + +## Architectural decisions + +### Decision 1: Models under `core/models/` + +**Choice**: `primus/backends/megatron/core/models/diffusion/` +**Not**: `primus/backends/megatron/models/diffusion/` + +**Reasoning**: +- Aligns with Megatron-Core structure +- Easier upstream tracking +- Clear that these are Megatron-Core compatible +- Consistent with existing Primus structure (`core/models/gpt/`) + +### Decision 2: Separate `common/` directory + +**Choice**: Shared components in `common/` +**Not**: Everything in `flux/` or flat structure + +**Reasoning**: +- Standard approach puts shared code in model-specific directories +- Primus: `common/` makes it explicit what's shared +- Future DiT implementation trivial (reuse from `common/`) +- Clear contract: if in `common/`, must work for all models + +**Shared Components**: +- `JointSelfAttention`: Used by DiT and Flux joint layers +- `FluxSingleAttention`: Used by DiT and Flux single layers +- `MMDiTLayer`: Joint (multimodal) transformer block +- `FluxSingleTransformerBlock`: Single-modality transformer block + +**Flux-Only Components**: +- `EmbedND`: 3D RoPE position embedding (Flux-specific) +- `Flux` model class + +### Decision 3: Hierarchical encoder structure + +**Choice**: `encoders/image/vae/`, `encoders/text/t5/`, `encoders/text/clip/` +**Not**: Flat `encoders/conditioner.py` + +**Reasoning**: +- Traditional approach uses flat structure with all encoders in one file +- User requirement: support 5+ variants per modality +- Registry pattern enables config-driven selection +- Easy to add new encoders without modifying existing files + +**Registry Pattern**: +```python +from primus.backends.megatron.data.diffusion.encoders import get_encoder + +# Config-driven selection +vae = get_encoder('autoencoder_kl', config=vae_config) +t5 = get_encoder('t5_xxl', config=t5_config) +clip = get_encoder('clip_l', config=clip_config) +``` + +### Decision 4: Shared Energon infrastructure + +**Choice**: `data/energon/` for shared utilities +**Not**: Nested under `data/diffusion/` + +**Reasoning**: +- Energon is general-purpose (VLM, diffusion, future models) +- Traditional approach nests under model-specific directories +- Megatron-LM has Energon at example level (not in core) +- Primus approach: shared infra + model-specific TaskEncoders + +**Pattern**: +```python +# Shared: data/dataloader.py +# MegatronDataloaderWrapper wraps any iterable (Energon loader, PyTorch DataLoader, etc.) +wrapper = MegatronDataloaderWrapper(dataloader) + +# Model-specific: data/diffusion/task_encoders/image.py +class EncodedDiffusionTaskEncoder: + """Diffusion-specific encoding logic""" + pass +``` + +### Decision 5: Model provider at adapter level + +**Choice**: construct the model through provider functions in the Megatron trainer/adapter layer under `primus/backends/megatron/`, above `core/models/`. + +**Reasoning**: +- Model providers are adapter/wrapper functions rather than a standalone module; they live alongside the trainers (for example `primus/backends/megatron/megatron_pretrain_trainer.py`) +- They sit above the core models to add Primus-specific functionality +- Example: wrap the model with a custom loss, precision handling, or checkpoint logic + +### Decision 6: No PyTorch lightning + +**Choice**: Pure Megatron patterns +**Not**: PyTorch Lightning DataModules + +**Reasoning**: +- Primus doesn't use PyTorch Lightning +- Framework-specific implementations reduce flexibility +- Better integration with Megatron training loop +- Follows Megatron-LM's `MegatronDataloaderWrapper` wrapper pattern + +--- + +## Component hierarchy + +### 1. Model hierarchy + +``` +nn.Module (PyTorch) +└── MegatronModule + └── DiffusionModule (abstract) + └── Flux (concrete) + ├── Joint layers: MMDiTLayer × num_joint_layers + │ └── JointSelfAttention (shared) + ├── Single layers: FluxSingleTransformerBlock × num_single_layers + │ └── FluxSingleAttention (shared) + └── Embeddings: + ├── TimeStepEmbedder (shared) + ├── MLPEmbedder (shared) + └── EmbedND (Flux-specific, 3D RoPE) +``` + +### 2. Configuration hierarchy + +``` +TransformerConfig (Megatron-Core) +└── BaseDiffusionConfig + └── FluxConfig + ├── flux_535m() factory + └── flux_12b() factory +``` + +### 3. Scheduler hierarchy + +``` +BaseScheduler (abstract) +├── FlowMatchEulerDiscreteScheduler (Flux) +├── DDPMScheduler (future) +├── EDMScheduler (future) +└── EulerDiscreteScheduler (future) +``` + +### 4. Encoder hierarchy + +``` +BaseEncoder (abstract) +├── ImageEncoder +│ └── VAE +│ ├── AutoencoderKL (Flux) +│ └── VQVAE (future) +└── TextEncoder + ├── T5 + │ ├── T5-XXL (Flux) + │ └── T5-Large (future) + └── CLIP + ├── CLIP-L (Flux) + └── CLIP-H (future) +``` + +--- + +## Data flow + +### Training pipeline + +``` +Raw Data (images + captions) + ↓ +[Optional] Precalculation + ├─ VAE → latents [B, 64, H/8, W/8] + ├─ T5-XXL → embeddings [B, 512, 4096] + └─ CLIP-L → pooled [B, 768] + ↓ +WebDataset/Energon Format (.tar files) + ↓ +MegatronDataloaderWrapper (from data/dataloader.py) + ↓ +EncodedDiffusionTaskEncoder (from data/diffusion/task_encoders/) + ├─ Load precalculated data, OR + └─ Encode on-the-fly (slower) + ↓ +Training Batch: + ├─ latents: [B, 64, H, W] + ├─ t5_embeddings: [B, S, 4096] + └─ clip_pooled: [B, 768] + ↓ +FlowMatchEulerDiscreteScheduler + ├─ Sample timesteps: t ~ U(0, 1) + ├─ Sample noise: ε ~ N(0, I) + ├─ Add noise: x_t = (1-t)*ε + t*x_0 + └─ Compute target: v = x_0 - ε + ↓ +Flux Model Forward Pass + ├─ Embed timesteps + ├─ Embed pooled text (CLIP) + ├─ Joint layers (process latents + T5 embeddings) + ├─ Single layers (process latents only) + └─ Output: v_pred [B, 64, H, W] + ↓ +Loss Computation: MSE(v_pred, v_target) + ↓ +Backward Pass & Optimizer Step +``` + +### Inference pipeline + +``` +Text Prompt + ↓ +Text Encoders + ├─ T5-XXL → embeddings [1, S, 4096] + └─ CLIP-L → pooled [1, 768] + ↓ +Initialize Noise: x_0 ~ N(0, I) + ↓ +Sampling Loop (t = 1.0 → 0.0) + ├─ Model forward: v_t = Flux(x_t, t, embeddings) + ├─ Update: x_{t-dt} = x_t + v_t * dt + └─ Repeat until t = 0 + ↓ +Latents: x_0 [1, 64, H, W] + ↓ +VAE Decoder + ↓ +Generated Image [1, 3, H*8, W*8] +``` + +--- + +## Comparison with alternative implementations + +### Primus architectural advantages + +#### 1. TransformerBlock architecture (Primus innovation) +- **Primus**: Unified `TransformerBlock` with heterogeneous layer specs +- **Others**: Separate `nn.ModuleList` containers for double/single blocks +- **Benefit**: Better PP slicing, unified checkpointing, future-proof + +#### 2. Megatron-core native +- **Primus**: Pure Megatron-Core, no framework dependencies +- **Others**: Often integrated with PyTorch Lightning or other frameworks +- **Benefit**: Tighter integration, simpler training loops + +#### 3. Checkpoint format +- **Primus**: Unified `transformer.layers.{0-56}` structure +- **Others**: Separate `double_blocks.{i}` and `single_blocks.{j}` +- **Benefit**: Simpler distributed checkpointing + +#### 4. Encoder architecture +- **Primus**: Registry-based, hierarchical organization +- **Others**: Direct imports from monolithic files +- **Benefit**: Easy extensibility for new encoder variants + +### File organization comparison + +| Component | Standard Location | Primus Location | Improvement | +|-----------|------------------|-----------------|-------------| +| Model | `models/diffusion/flux/` | `core/models/diffusion/flux/` | Megatron-Core convention | +| Layers | Mixed locations | `common/` for shared, `flux/` for specific | Clear boundaries | +| Encoders | Single file | `data/diffusion/encoders/{type}/{variant}/` | Hierarchical, extensible | +| Tests | Mixed with code | `tests/unit_tests/backends/megatron/diffusion/` | Proper separation | + +--- + +## Implementation status + +### Core infrastructure ✅ +- ✅ Directory structure +- ✅ Base classes (DiffusionModule, BaseDiffusionConfig, BaseScheduler) +- ✅ DiffusionModule with Megatron-Core integration +- ✅ FluxConfig with factory methods +- ✅ FlowMatchEulerDiscreteScheduler implementation +- ✅ Configuration files (YAML) +- ✅ Testing framework (290+ tests) +- ✅ Documentation structure + +### Flux model implementation ✅ +- ✅ Flux model architecture +- ✅ MMDiT layers and attention +- ✅ Embeddings (RoPE, timestep, vector) +- ✅ Encoder registry and loaders +- ✅ TaskEncoder for Energon +- ✅ Data pipeline + +--- + +## Extension points + +### Adding a new model (e.g., DiT) + +1. **Create model directory**: `core/models/diffusion/dit/` +2. **Add config**: Extend `BaseDiffusionConfig` +3. **Implement model**: Extend `DiffusionModule` (which extends MegatronModule) + - Inherit process group management + - Get distributed checkpointing support + - Access attention backend configuration +4. **Reuse shared components**: Import from `common/` +5. **Add tests**: `tests/unit_tests/backends/megatron/diffusion/test_dit_model.py` +6. **Update configs**: Add `dit_config.yaml` + +### Adding a new encoder variant + +1. **Create encoder file**: e.g., `data/diffusion/encoders/text/t5/t5_large.py` +2. **Implement encoder class**: Extend `BaseEncoder` +3. **Register**: Add to `ENCODER_REGISTRY` +4. **Add config**: Update `encoders.yaml` +5. **Add tests**: Test in `tests/unit_tests/backends/megatron/diffusion/data/encoders/` + +### Adding a new scheduler + +1. **Create scheduler file**: `training/diffusion/schedulers/ddpm.py` +2. **Implement**: Extend `BaseScheduler` +3. **Export**: Add to `__init__.py` +4. **Add tests**: `tests/unit_tests/backends/megatron/diffusion/training/test_scheduler.py` +5. **Document**: Update this file and README + +--- + +## Performance considerations + +### Memory optimization +- **Precalculated data**: 5-10x faster, lower memory +- **Frozen encoders**: Only train diffusion model +- **Gradient checkpointing**: Trade compute for memory +- **Mixed precision**: bf16 on MI300X (compatible with H100/A100) + +### Multi-GPU scaling +- **Tensor Parallelism**: Split model across GPUs +- **Pipeline Parallelism**: Split layers across GPUs +- **Data Parallelism**: Replicate model, split data +- **Sequence Parallelism**: For very long sequences + +### Best practices +1. Use precalculated mode for training +2. Freeze encoders (standard practice) +3. Use bf16 on modern hardware +4. Start with TP=1, PP=1, scale as needed +5. Profile before optimizing + +--- + +## References + +- **Megatron-Core**: [nvidia/Megatron-LM](https://github.com/NVIDIA/Megatron-LM) transformer patterns +- **Flux**: [black-forest-labs/FLUX.1](https://huggingface.co/black-forest-labs/FLUX.1-dev) +- **Flow Matching**: Rectified flow and flow matching papers +- **NeMo**: [nvidia/NeMo](https://github.com/NVIDIA/NeMo) - Alternative diffusion implementation + +--- + +**Last Updated**: December 2025 diff --git a/docs/04-technical-guides/diffusion-models/data_preprocessing.md b/docs/04-technical-guides/diffusion-models/data_preprocessing.md new file mode 100644 index 000000000..a99a9754f --- /dev/null +++ b/docs/04-technical-guides/diffusion-models/data_preprocessing.md @@ -0,0 +1,629 @@ +# Data preprocessing guide + +This guide explains how to prepare datasets for Flux and other diffusion models in Primus, including pre-encoding of VAE latents and text embeddings into Energon WebDataset format. + +--- + +## Table of contents + +1. [Overview](#overview) +2. [Quick start](#quick-start) +3. [Two pipelines](#two-pipelines) +4. [Running preprocessing](#running-preprocessing) +5. [Configuration](#configuration) +6. [Authentication](#authentication) +7. [Finalization](#finalization) +8. [Output format](#output-format) +9. [Validation](#validation) +10. [Troubleshooting](#troubleshooting) + +--- + +## Overview + +Diffusion models require three types of encodings: +1. **VAE latents**: Images encoded to latent space +2. **Text embeddings**: Captions encoded with T5-XXL (sequence) +3. **Pooled embeddings**: Captions encoded with CLIP-L (pooled) + +### Why pre-encode? + +**Benefits**: +- 5-10x faster training (no online encoding) +- Lower GPU memory usage (encoders not loaded during training) +- Deterministic inputs (same preprocessing for all runs) +- Eliminates encoder differences as a variable + +**When to Use**: +- Training (highly recommended) +- Fine-tuning on fixed datasets +- Benchmarking and reproducibility + +**When NOT to Use**: +- Interactive data augmentation needed +- Dataset too large to store pre-encoded +- Rapid prototyping with changing data + +--- + +## Quick start + +Preprocess the Pokemon dataset with a single command: + +```bash +primus-cli direct -- data diffusion-encoded \ + --config primus/configs/data/megatron/diffusion/preprocessing/quickstart_pokemon.yaml \ + --hf-token-file /path/to/.hf_token +``` + +This will: +1. Download the `diffusers/pokemon-gpt4-captions` dataset from HuggingFace +2. Encode all images with VAE and captions with T5/CLIP +3. Write Energon WebDataset tar shards to `/workspace/Primus/data/quickstart_pokemon` +4. Automatically finalize the dataset (create `dataset.yaml`, run `energon prepare`, validate) + +The default encoder model (`black-forest-labs/FLUX.1-dev`) is gated and requires a HuggingFace token. Get one at https://huggingface.co/settings/tokens and save it to a file. + +--- + +## Two pipelines + +Primus provides two preprocessing pipelines via the `primus data` CLI: + +### `diffusion-encoded` (recommended for training) + +Pre-encodes images with VAE and text with T5/CLIP. Produces larger datasets but enables faster training since encoders are not needed at training time. + +```bash +primus-cli direct -- data diffusion-encoded \ + --config primus/configs/data/megatron/diffusion/preprocessing/example_huggingface.yaml \ + --hf-token-file /path/to/.hf_token +``` + +**Output**: WebDataset shards containing `latents.pth`, `prompt_embeds.pth`, `pooled_prompt_embeds.pth`, `caption.txt` + +**Use when**: Training on production datasets, maximum training speed is needed, storage is available. + +### `diffusion-raw` + +Stores raw images and captions without encoding. Smaller datasets but encoding happens on-the-fly during training (requires GPU + encoders loaded in memory). + +```bash +primus-cli direct -- data diffusion-raw \ + --source-type huggingface \ + --hf-dataset diffusers/pokemon-gpt4-captions \ + --output-dir /workspace/Primus/data/raw_pokemon \ + --hf-token-file /path/to/.hf_token +``` + +**Output**: WebDataset shards containing `jpg` (or `png`/`webp`) and `txt` files. + +**Use when**: Storage is limited, experimenting with different encoders, rapid prototyping. + +**Note**: `diffusion-raw` does not support `--config` files. All options must be passed as CLI arguments. + +--- + +## Running preprocessing + +### Using a config file (recommended) + +The `--config` flag is supported by `diffusion-encoded` only. The simplest approach uses a YAML config file: + +```bash +primus-cli direct -- data diffusion-encoded \ + --config primus/configs/data/megatron/diffusion/preprocessing/example_huggingface.yaml \ + --hf-token-file /path/to/.hf_token +``` + +Available example configs in `primus/configs/data/megatron/diffusion/preprocessing/`: + +| Config | Source | Description | +|--------|--------|-------------| +| `quickstart_pokemon.yaml` | HuggingFace | Minimal config, 256px, fast | +| `example_huggingface.yaml` | HuggingFace | Full example with all options | +| `example_directory.yaml` | Local directory | Images + captions from disk | +| `example_webdataset.yaml` | WebDataset | Existing tar archives | +| `example_base.yaml` | N/A | Comprehensive reference with all fields | +| `text_to_image_2m_10k.yaml` | HuggingFace | 10K subset of text-to-image-2M (1024px) | + +### Using CLI arguments directly + +All config values can be provided as CLI arguments: + +```bash +primus-cli direct -- data diffusion-encoded \ + --source-type huggingface \ + --hf-dataset diffusers/pokemon-gpt4-captions \ + --output-dir /workspace/Primus/data/encoded_pokemon \ + --model-path black-forest-labs/FLUX.1-dev \ + --batch-size 8 \ + --precision bf16 \ + --hf-token-file /path/to/.hf_token +``` + +### CLI overrides config values + +When using both `--config` and CLI arguments, CLI arguments take priority: + +```bash +primus-cli direct -- data diffusion-encoded \ + --config primus/configs/data/megatron/diffusion/preprocessing/example_huggingface.yaml \ + --hf-token-file /path/to/.hf_token \ + --output-dir /my/custom/path \ + --batch-size 16 \ + --max-samples 1000 +``` + +Priority order (highest to lowest): +1. Explicitly provided CLI arguments +2. YAML config file values +3. CLI default values + +### Multi-GPU processing + +Use `--nproc-per-node` for data-parallel preprocessing across multiple GPUs: + +```bash +primus-cli direct -- --nproc-per-node=8 data diffusion-encoded \ + --config primus/configs/data/megatron/diffusion/preprocessing/example_huggingface.yaml \ + --hf-token-file /path/to/.hf_token +``` + +Each GPU processes a subset of the data. Shards are named to avoid conflicts across ranks. + +--- + +## Configuration + +### YAML config structure + +Preprocessing configs have four sections: + +```yaml +source: + type: huggingface # huggingface | directory | webdataset + hf_dataset: diffusers/pokemon-gpt4-captions + hf_split: train + +output: + output_dir: /workspace/Primus/data/encoded_pokemon + shard_size: 1000 # samples per tar shard + max_samples: null # null = process all + compress: false + +model: + model_path: black-forest-labs/FLUX.1-dev # HF repo or local path + precision: bf16 # bf16 | fp16 | fp32 + batch_size: 8 + # Optional per-encoder overrides: + vae_path: null + t5_path: null + clip_path: null + +image: + image_size: 1024 + center_crop: false +``` + +### Source types + +**HuggingFace** (`type: huggingface`): +```yaml +source: + type: huggingface + hf_dataset: diffusers/pokemon-gpt4-captions + hf_split: train + hf_data_files: null # optional: specific files within dataset +``` + +**Local Directory** (`type: directory`): +```yaml +source: + type: directory + input_dir: /data/my_images +``` + +Expected directory structure for `directory` source: +``` +my_images/ +├── images/ +│ ├── 00001.jpg +│ ├── 00002.png +│ └── ... +└── captions/ + ├── 00001.txt + ├── 00002.txt + └── ... +``` + +**WebDataset** (`type: webdataset`): +```yaml +source: + type: webdataset + input_path: /data/existing_shards/*.tar +``` + +### Model configuration + +The `model_path` defaults to `black-forest-labs/FLUX.1-dev`, which downloads VAE, T5-XXL, and CLIP-L encoders from HuggingFace. This model is gated and requires authentication (see [Authentication](#authentication)). + +Individual encoder paths can be overridden: + +```yaml +model: + model_path: black-forest-labs/FLUX.1-dev + vae_path: /local/models/vae # use local VAE instead + t5_path: null # falls back to model_path + clip_path: null # falls back to model_path +``` + +--- + +## Authentication + +The default encoder model (`FLUX.1-dev`) is gated on HuggingFace and requires authentication. Primus supports three authentication methods, checked in priority order: + +### 1. Token file (recommended) + +```bash +primus-cli direct -- data diffusion-encoded \ + --config your_config.yaml \ + --hf-token-file /path/to/.hf_token +``` + +The token file must have secure permissions (600 or 400). Create it with: + +```bash +echo "hf_your_token_here" > /path/to/.hf_token +chmod 600 /path/to/.hf_token +``` + +### 2. Environment variable + +```bash +export HF_TOKEN=hf_your_token_here +primus-cli direct -- data diffusion-encoded --config your_config.yaml +``` + +### 3. HuggingFace CLI login + +```bash +huggingface-cli login +primus-cli direct -- data diffusion-encoded --config your_config.yaml +``` + +If authentication fails, Primus provides a clear error message indicating which encoder failed and how to fix it. + +--- + +## Finalization + +Finalization is **automatic by default**. After preprocessing completes, Primus automatically: + +1. **Creates `.nv-meta/dataset.yaml`** with `CrudeWebdataset` sample type and encoding subflavor +2. **Runs `energon prepare`** to index the tar shards and create split assignments +3. **Validates the dataset** using Primus's custom validation (metadata checks, sample count verification, energon API spot-check) + +### Skipping finalization + +To skip automatic finalization (e.g., for manual post-processing): + +```bash +primus-cli direct -- data diffusion-encoded \ + --config your_config.yaml \ + --hf-token-file /path/to/.hf_token \ + --no-finalize +``` + +### Custom train/val/test splits + +By default, 100% of data goes to the training split. To create validation and test splits: + +```bash +primus-cli direct -- data diffusion-encoded \ + --config your_config.yaml \ + --hf-token-file /path/to/.hf_token \ + --train-split 0.8 +``` + +This creates an 80% train / 10% val / 10% test split. + +--- + +## Output format + +### Directory structure + +After preprocessing and finalization, the output directory contains: + +``` +encoded_pokemon/ +├── 000000.tar # WebDataset shard +├── 000001.tar +├── 000000.tar.idx # Energon index files +├── 000001.tar.idx +└── .nv-meta/ # Energon metadata + ├── dataset.yaml # Dataset type configuration + ├── split.yaml # Train/val/test split assignments + └── .info.json # Shard counts and sample counts +``` + +### Pre-encoded shard contents (`diffusion-encoded`) + +Each tar shard contains samples with these keys: + +``` +000000.tar: +├── 0000000000.latents.pth # VAE latents tensor +├── 0000000000.prompt_embeds.pth # T5-XXL embeddings tensor +├── 0000000000.pooled_prompt_embeds.pth # CLIP-L pooled embeddings tensor +├── 0000000000.caption.txt # Original caption text +├── 0000000001.latents.pth +├── 0000000001.prompt_embeds.pth +├── ... +``` + +Tensor shapes: +- `latents.pth`: `[64, H/8, W/8]` (e.g., `[64, 128, 128]` for 1024x1024 images) +- `prompt_embeds.pth`: `[seq_len, 4096]` (T5-XXL hidden dim) +- `pooled_prompt_embeds.pth`: `[768]` (CLIP-L pooled dim) + +### Raw shard contents (`diffusion-raw`) + +``` +000000.tar: +├── 0000000000.jpg # Preprocessed image +├── 0000000000.txt # Caption text +├── 0000000001.jpg +├── 0000000001.txt +├── ... +``` + +### Dataset YAML + +The auto-generated `.nv-meta/dataset.yaml` uses `CrudeWebdataset` format: + +```yaml +__module__: megatron.energon +__class__: CrudeWebdataset +subflavors: + encoding: preencoded # or 'raw' for diffusion-raw +``` + +--- + +## Validation + +### Automatic validation + +Validation runs automatically as part of finalization. It performs four checks: + +1. **Metadata check**: Verifies `.nv-meta/` files exist and are valid (`.info.json`, `split.yaml`, `dataset.yaml`) +2. **Sample count check**: Spot-checks that tar shard entry counts match `.info.json` +3. **Sample load check**: Loads one sample through Energon's Python API (same code path as training) +4. **Summary report**: Prints dataset statistics (encoding, total samples, splits, data shapes, size) + +### Standalone validation + +To validate a dataset independently: + +```bash +python -m primus.backends.megatron.data.diffusion.preprocessing.validate /path/to/dataset + +# For raw datasets: +python -m primus.backends.megatron.data.diffusion.preprocessing.validate /path/to/dataset --encoding raw +``` + +### Programmatic validation + +```python +from primus.backends.megatron.data.diffusion.preprocessing.validate import validate_energon_dataset + +ok = validate_energon_dataset('/path/to/dataset', encoding='preencoded') +``` + +### Known Energon CLI limitations + +The standard Energon CLI tools (`energon info`, `energon preview`, `energon lint`) do **not** work correctly with `CrudeWebdataset` format. Primus uses custom validation instead: + +- `energon info` raises `KeyError: 'sample_type'` +- `energon preview` raises `TypeError` (expects dataclass, `CrudeSample` is a dict) +- `energon lint` raises `AssertionError` (expects registered cookers) + +Use Primus's built-in validation or the standalone script above. + +--- + +## Troubleshooting + +### HuggingFace authentication failure + +**Symptoms**: Error mentioning "token", "gated", "401", or "403" when downloading encoders. + +**Solutions**: +1. Provide a token: `--hf-token-file /path/to/.hf_token` +2. Set environment variable: `export HF_TOKEN=hf_xxx` +3. Run `huggingface-cli login` +4. Accept the model's license on https://huggingface.co/black-forest-labs/FLUX.1-dev + +### Out of memory during preprocessing + +**Symptoms**: CUDA out of memory error during encoding. + +**Solutions**: +1. Reduce batch size: `--batch-size 4` or `--batch-size 1` +2. Use smaller image size: `--image-size 512` +3. Use fp16 precision: `--precision fp16` +4. Use multi-GPU to distribute work: `--nproc-per-node=8` + +### Missing dependencies + +**Symptoms**: `ModuleNotFoundError` for `webdataset`, `megatron-energon`, `tqdm`, etc. + +**Solution**: +```bash +pip install -r requirements.txt +``` + +Or set `PRIMUS_AUTO_INSTALL=1` in the container to auto-install missing packages. + +### Finalization fails + +**Symptoms**: Error during `energon prepare` or validation after preprocessing. + +**Solutions**: +1. Check that tar shards exist in the output directory +2. Ensure `megatron-energon` is installed (`pip install megatron-energon`) +3. Re-run with `--no-finalize`, then manually inspect the output before finalizing + +### Slow preprocessing + +**Symptoms**: Low throughput (< 10 samples/sec). + +**Solutions**: +1. Increase batch size (limited by VRAM): `--batch-size 16` +2. Use multiple GPUs: `--nproc-per-node=8` +3. Use bf16 precision (faster on supported hardware): `--precision bf16` +4. For raw pipeline, reduce image quality: `--image-quality 85` + +--- + +## Best practices + +1. **Always pre-encode for production training**: 5-10x speedup is worth the storage +2. **Test on small dataset first**: Use `--max-samples 100` to verify the pipeline works +3. **Use bf16 precision**: Good balance of speed, storage, and quality +4. **Start with quickstart_pokemon.yaml**: Verify your setup before processing large datasets +5. **Keep raw data**: Pre-encoding is a one-way transformation +6. **Version your configs**: Track which preprocessing config produced each dataset + +--- + +## Flux-specific data preparation (torchrun) + +This section covers preparing datasets for Flux training using `torchrun` directly +inside a Docker container, as an alternative to the `primus-cli` workflow above. + +### Prerequisites + +Start the container and optionally install requirements: + +```bash +bash tools/docker/start_container.sh +docker exec dev_primus bash -c 'pip install -r /workspace/Primus/requirements.txt' +``` + +This mounts the repository to `/workspace/Primus` inside the container. +Override the image with `DOCKER_IMAGE`: + +```bash +DOCKER_IMAGE=docker.io/rocm/primus:v26.1 bash tools/docker/start_container.sh +``` + +### Input directory structure + +When using `--source-type directory`, organize your data as follows: + +``` +dataset/ +├── images/ +│ ├── 0000000.png +│ ├── 0000001.png +│ └── ... +└── captions/ + ├── 0000000.txt + ├── 0000001.txt + └── ... +``` + +The file stem links each image to its caption (e.g., `images/0000000.png` pairs +with `captions/0000000.txt`). Images can be `.jpg`, `.jpeg`, `.png`, or `.webp`. +Captions must be UTF-8 `.txt` files. Samples without a matching caption are skipped. + +Other supported source types: +- **huggingface** -- Load directly from HuggingFace Hub. Requires `--hf-dataset`. +- **webdataset** -- Read from existing WebDataset tar archives. Requires `--input-path`. + +### Image sizing + +**Fixed size (default):** Every image is resized to `--image-size` pixels square +(default 1024). Use `--center-crop` to control center-cropping. + +**Variable size (`--variable-size`):** Preserves aspect ratio by scaling the +longest side to `--max-size` (default 1024) and rounding dimensions to multiples +of 16. Only use this when all images share the same dimensions -- mixed tensor +sizes cause load imbalance across GPUs. + +### Key parameters + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `--source-type` | — | `directory`, `huggingface`, or `webdataset` | +| `--input-dir` | — | Dataset root (for `directory` source) | +| `--image-size` | 1024 | Square target size | +| `--variable-size` | off | Preserve aspect ratio | +| `--max-size` | 1024 | Maximum dimension in variable-size mode | +| `--model-path` | `black-forest-labs/FLUX.1-dev` | Base model for encoder weights | +| `--t5-max-length` | 512 | T5 token limit (use 256 for FLUX.1-schnell) | +| `--batch-size` | 8 | Encoding batch size per GPU | +| `--output-dir` | — | Destination for encoded WebDataset shards | +| `--shard-size` | 1000 | Samples per tar shard | + +### Examples + +**From host (via `docker exec`):** + +```bash +docker exec dev_primus bash -c '\ + export HF_HOME=/workspace/Primus/checkpoints/flux; \ +PYTHONPATH=/workspace/Primus:/workspace/Primus/third_party/Megatron-LM:$PYTHONPATH \ + torchrun --nproc_per_node=8 /workspace/Primus/primus/cli/main.py \ + data diffusion-encoded \ + --source-type directory \ + --input-dir /workspace/Primus/data/dataset \ + --output-dir /workspace/Primus/data/dataset_encoded_256 \ + --image-size 256 \ + --t5-max-length 256' +``` + +**Inside the container:** + +```bash +export HF_HOME=/workspace/Primus/checkpoints/flux +PYTHONPATH=/workspace/Primus:/workspace/Primus/third_party/Megatron-LM:$PYTHONPATH \ + torchrun --nproc_per_node=8 /workspace/Primus/primus/cli/main.py \ + data diffusion-encoded \ + --source-type directory \ + --input-dir /workspace/Primus/data/dataset \ + --output-dir /workspace/Primus/data/dataset_encoded_256 \ + --image-size 256 \ + --t5-max-length 256 +``` + +Both commands encode a local directory dataset at 256px on 8 GPUs, then create an Energon dataset by default. + +### Limitations + +**Uniform output size only.** When using `--variable-size`, images may produce +tensors of different shapes. The current data pipeline does not support mixed +tensor sizes because samples are distributed evenly across GPUs and unequal +shapes cause load imbalance and eventual timeout. Use fixed `--image-size` if +your images have various sizes. + +--- + +## Next steps + +After preprocessing: +1. **Training**: Use the preprocessed dataset path in your training config +2. **Validation**: The dataset is ready for training immediately after finalization + +See: +- [Energon Integration](energon_integration.md) for TaskEncoder and dataloader details +- [Config Directory Guide](../../../primus/configs/data/megatron/diffusion/README.md) for config file reference +- Example configs in `primus/configs/data/megatron/diffusion/preprocessing/` + +--- + +**Last Updated**: June 2026 diff --git a/docs/04-technical-guides/diffusion-models/energon_integration.md b/docs/04-technical-guides/diffusion-models/energon_integration.md new file mode 100644 index 000000000..97941a67c --- /dev/null +++ b/docs/04-technical-guides/diffusion-models/energon_integration.md @@ -0,0 +1,514 @@ +# Energon integration guide + +This guide explains how Megatron-Energon is integrated with Primus diffusion models, including the Cooker/TaskEncoder pattern, dataloader configuration, and dataset format. + +--- + +## Table of contents + +1. [Overview](#overview) +2. [Energon architecture](#energon-architecture) +3. [Dataset format](#dataset-format) +4. [TaskEncoder and cooker pattern](#taskencoder-and-cooker-pattern) +5. [Dataloader setup](#dataloader-setup) +6. [Dataset configuration](#dataset-configuration) +7. [Implementation examples](#implementation-examples) +8. [Best practices](#best-practices) + +--- + +## Overview + +### What is Megatron-Energon? + +Megatron-Energon is NVIDIA's data loading framework for large-scale multimodal training. It provides: +- **WebDataset integration**: Efficient streaming from .tar archives +- **Task encoding**: Flexible data transformation pipeline via Cookers +- **Multi-worker support**: Parallel data loading +- **Deterministic iteration**: Reproducible training +- **Checkpoint resumption**: Resume from any step + +### Why Energon for diffusion? + +- **Proven at scale**: Used by NVIDIA for LLM and multimodal training +- **Flexible**: Supports various data formats and transformations +- **Efficient**: Optimized for multi-GPU training +- **Compatible**: Works with Megatron parallelism (TP, PP, DP) + +### Primus integration strategy + +``` +Shared Infrastructure (data/) + ├─ MegatronDataloaderWrapper + ├─ Dataset configuration parsers + └─ Common utilities + ↓ +Model-Specific TaskEncoders (data/diffusion/task_encoders/) + ├─ EncodedDiffusionTaskEncoder (preencoded data) + ├─ RawDiffusionTaskEncoder (raw images + text) + └─ Custom task encoders +``` + +**Key Principle**: Share infrastructure, separate domain logic. + +--- + +## Energon architecture + +### Component stack + +``` +Training Loop + ↓ +MegatronDataloaderWrapper (Primus wrapper, cyclic iteration) + ↓ +Megatron-Energon Core + ├─ WebDataset Reader (reads .tar shards) + ├─ Cooker (transforms raw dict → typed Sample) + ├─ TaskEncoder.batch() (stacks samples → batch) + └─ Worker Pool + ↓ +.tar Shards (CrudeWebdataset format) +``` + +### Data flow + +``` +1. Load from .tar shard + → raw_dict = {'__key__': '0000000000', + 'latents.pth': bytes, + 'prompt_embeds.pth': bytes, + 'pooled_prompt_embeds.pth': bytes, + 'caption.txt': bytes} + +2. Cooker function (e.g., cook_preencoded_diffusion) + → Deserializes bytes to tensors + → Returns typed DiffusionSample dataclass + → DiffusionSample(latents=Tensor[64,128,128], + prompt_embeds=Tensor[512,4096], + pooled_prompt_embeds=Tensor[768], + caption="a photo of...") + +3. TaskEncoder.batch() + → Stacks list of DiffusionSample into batch dict + → batch = {'latents': [B, 64, H, W], + 'prompt_embeds': [B, seq_len, 4096], + 'pooled_prompt_embeds': [B, 768]} + +4. Return to training loop via MegatronDataloaderWrapper + → Forward pass, loss, backprop +``` + +--- + +## Dataset format + +### CrudeWebdataset + +Primus uses Energon's `CrudeWebdataset` format for preprocessed datasets. This is the simplest Energon format -- it stores raw key-value pairs in tar shards without requiring a strict schema. + +The dataset type is configured in `.nv-meta/dataset.yaml`: + +```yaml +__module__: megatron.energon +__class__: CrudeWebdataset +subflavors: + encoding: preencoded # or 'raw' +``` + +The `subflavors.encoding` field tells the TaskEncoder which Cooker to use: +- `preencoded`: Uses `cook_preencoded_diffusion` -- loads pre-encoded tensors +- `raw`: Uses `cook_raw_images` -- loads raw images and text + +### Pre-encoded shard contents + +Each tar shard contains samples with these keys: + +| Key | Type | Shape | Description | +|-----|------|-------|-------------| +| `latents.pth` | Tensor | `[64, H/8, W/8]` | VAE-encoded image latents | +| `prompt_embeds.pth` | Tensor | `[seq_len, 4096]` | T5-XXL text embeddings | +| `pooled_prompt_embeds.pth` | Tensor | `[768]` | CLIP-L pooled embeddings | +| `caption.txt` | str | N/A | Original caption text | + +### Raw shard contents + +| Key | Type | Description | +|-----|------|-------------| +| `jpg` / `png` / `webp` | bytes | Preprocessed image | +| `txt` | str | Caption text | + +### Known Energon CLI limitations + +The standard Energon CLI tools have issues with `CrudeWebdataset`: +- `energon info` raises `KeyError: 'sample_type'` +- `energon preview` raises `TypeError` (expects dataclass, `CrudeSample` is a dict) +- `energon lint` raises `AssertionError` (expects registered cookers) + +Primus includes custom validation (`primus.backends.megatron.data.diffusion.preprocessing.validate`) as a replacement. See the [Data Preprocessing Guide](data_preprocessing.md#validation) for details. + +--- + +## TaskEncoder and cooker pattern + +Primus uses Energon's Cooker pattern rather than the older `encode_sample()` approach. Cookers are `@stateless` functions that transform raw sample dicts into typed dataclass instances, dispatched based on `subflavors`. + +### DiffusionSample dataclass + +Location: `primus/backends/megatron/data/diffusion/task_encoders/image.py` + +```python +# (imports like torch omitted for brevity) +from dataclasses import dataclass +from megatron.energon import Sample + +@dataclass +class DiffusionSample(Sample): + """ + Diffusion training sample with framework-standard field names. + + Inherits from megatron.energon.Sample to ensure __key__, __restore_key__, + and __subflavors__ are properly tracked for deterministic training resumption. + """ + latents: torch.Tensor # [C, H, W] VAE latents + prompt_embeds: torch.Tensor # [seq_len, hidden_dim] T5 embeddings + pooled_prompt_embeds: torch.Tensor # [hidden_dim] CLIP pooled + caption: str = "" +``` + +### Cooker functions + +Cookers are `@stateless` functions registered with a `Cooker` wrapper that specifies which `subflavors` they handle: + +```python +# (imports like torch, io omitted for brevity) +from megatron.energon import Cooker, basic_sample_keys, stateless + +@stateless +def cook_preencoded_diffusion(sample: dict) -> DiffusionSample: + """Load precalculated VAE latents and text embeddings from disk.""" + + def load_tensor(data): + if isinstance(data, bytes): + return torch.load(io.BytesIO(data), map_location='cpu') + return data + + latents = load_tensor(sample.get('latents.pth')) + prompt_embeds = load_tensor(sample.get('prompt_embeds.pth')) + pooled_prompt_embeds = load_tensor(sample.get('pooled_prompt_embeds.pth')) + + caption = sample.get('caption.txt', b'') + if isinstance(caption, bytes): + caption = caption.decode('utf-8') + + return DiffusionSample( + **basic_sample_keys(sample), + latents=latents, + prompt_embeds=prompt_embeds, + pooled_prompt_embeds=pooled_prompt_embeds, + caption=caption, + ) + + +@stateless +def cook_raw_images(sample: dict) -> Dict[str, Any]: + """Load raw images and text -- NO encoding, just data loading.""" + return { + **basic_sample_keys(sample), + 'images': sample.get('images'), + 'txt': sample.get('txt'), + } +``` + +The cooker code above is simplified for clarity. See `primus/backends/megatron/data/diffusion/task_encoders/image.py` for the full implementation, which includes additional input type handling and validation. + +### EncodedDiffusionTaskEncoder + +The TaskEncoder registers Cookers and provides the `batch()` method: + +```python +from megatron.energon import DefaultTaskEncoder, SampleDecoder, Cooker, WorkerConfig + +class EncodedDiffusionTaskEncoder(DefaultTaskEncoder[DiffusionSample, DiffusionSample, dict, dict]): + """TaskEncoder for PRE-ENCODED diffusion data.""" + + decoder = SampleDecoder(image_decode="pil") + + cookers = [ + Cooker(cook_preencoded_diffusion, has_subflavors={"encoding": "preencoded"}), + ] + + def __init__(self, worker_config: Optional[WorkerConfig] = None): + super().__init__() + self.worker_config = worker_config + + def batch(self, samples: List[DiffusionSample]) -> Dict[str, torch.Tensor]: + return { + 'latents': torch.stack([s.latents for s in samples]), + 'prompt_embeds': torch.stack([s.prompt_embeds for s in samples]), + 'pooled_prompt_embeds': torch.stack([s.pooled_prompt_embeds for s in samples]), + } +``` + +### RawDiffusionTaskEncoder + +For raw (on-the-fly encoding) datasets: + +```python +class RawDiffusionTaskEncoder(DefaultTaskEncoder): + """TaskEncoder for RAW diffusion data (images and text).""" + + decoder = SampleDecoder(image_decode="pil") + + cookers = [ + Cooker(cook_raw_images, has_subflavors={"encoding": "raw"}), + ] + + def __init__(self, worker_config: Optional[WorkerConfig] = None): + super().__init__() + self.worker_config = worker_config + + def batch(self, samples: List[Dict]) -> Dict[str, Any]: + return { + 'images': [s['images'] for s in samples], + 'txt': [s['txt'] for s in samples], + } +``` + +### How cooker dispatch works + +The Cooker framework matches samples to cooker functions based on `subflavors`: + +1. `dataset.yaml` specifies `subflavors: { encoding: preencoded }` +2. Energon reads a sample from the tar shard +3. The `has_subflavors` on each `Cooker` is checked against the sample's subflavors +4. The matching cooker function is called to transform the raw dict into a typed sample +5. `TaskEncoder.batch()` stacks multiple samples into a training batch + +This decouples data format (what's in the tar) from data loading logic (how to interpret it). + +--- + +## Dataloader setup + +### MegatronDataloaderWrapper + +Location: `primus/backends/megatron/data/dataloader.py` + +The `MegatronDataloaderWrapper` is a generic wrapper that makes any iterable compatible with Megatron's training loop. It provides: +- Cyclic iteration (never raises StopIteration) +- Optional checkpoint support via duck typing (`save_state_rank()` / `restore_state_rank()`) +- Works with PyTorch DataLoader, Energon loaders, and synthetic data + +```python +from primus.backends.megatron.data.dataloader import MegatronDataloaderWrapper + +wrapper = MegatronDataloaderWrapper(pytorch_or_energon_loader) +``` + +Note: Originally named `EnergonDataloader`, renamed to `MegatronDataloaderWrapper` to reflect its generic nature (it has no Energon dependencies). The old name is available as a deprecated alias. + +### Usage example + +```python +from primus.backends.megatron.data.dataloader import MegatronDataloaderWrapper + +# Create Energon loader (with TaskEncoder configured) +energon_loader = get_loader(...) +dataloader = MegatronDataloaderWrapper(energon_loader) + +# Use in training loop +for batch in dataloader: + latents = batch['latents'] # [B, 64, H, W] + prompt_embeds = batch['prompt_embeds'] # [B, seq_len, 4096] + pooled_prompt_embeds = batch['pooled_prompt_embeds'] # [B, 768] + + output = model(latents, timesteps, prompt_embeds, pooled_prompt_embeds) + loss = criterion(output, target) + loss.backward() +``` + +--- + +## Dataset configuration + +### Per-dataset dataset.yaml + +Each dataset directory has a `.nv-meta/dataset.yaml` that specifies the Energon dataset type: + +```yaml +__module__: megatron.energon +__class__: CrudeWebdataset +subflavors: + encoding: preencoded +``` + +This file is auto-generated by Primus during finalization. See [Data Preprocessing Guide](data_preprocessing.md#finalization). + +### Metadataset (multi-dataset mixing) + +For combining multiple datasets with different weights: + +```yaml +__module__: megatron.energon +__class__: Metadataset + +splits: + train: + datasets: + - weight: 0.7 + path: /data/laion_precalculated/ + - weight: 0.3 + path: /data/coco_precalculated/ +``` + +Energon samples proportionally to weights (70% from LAION, 30% from COCO). + +--- + +## Implementation examples + +### Example 1: Pre-encoded training + +```python +from primus.backends.megatron.data.dataloader import MegatronDataloaderWrapper +from primus.backends.megatron.data.diffusion.task_encoders import EncodedDiffusionTaskEncoder + +# TaskEncoder is configured by the dataset provider +# The cooker automatically handles subflavors dispatch +energon_loader = get_loader(...) +dataloader = MegatronDataloaderWrapper(energon_loader) + +for batch in dataloader: + output = model( + batch['latents'], + batch['prompt_embeds'], + batch['pooled_prompt_embeds'], + ) +``` + +### Example 2: Raw data training (on-the-fly encoding) + +```python +from primus.backends.megatron.data.diffusion.task_encoders import RawDiffusionTaskEncoder + +# Raw TaskEncoder passes through images and text without encoding +# Encoding happens in the model's forward_step +energon_loader = get_loader(...) +dataloader = MegatronDataloaderWrapper(energon_loader) + +for batch in dataloader: + images = batch['images'] # List of PIL Images + captions = batch['txt'] # List of caption strings + # Model handles VAE/T5/CLIP encoding in forward_step +``` + +### Example 3: Multi-GPU training + +```python +import torch.distributed as dist + +dist.init_process_group(backend='nccl') + +energon_loader = get_loader(...) +dataloader = MegatronDataloaderWrapper(energon_loader) + +for batch in dataloader: + output = model(batch['latents'], ...) +``` + +--- + +## Best practices + +### 1. Pre-encoded mode +- **Always use for production training**: 5-10x faster +- **Validate first**: Test with small dataset using `quickstart_pokemon.yaml` +- **Version datasets**: Track which preprocessing config produced each dataset + +### 2. Cooker design +- **Use `@stateless`**: Cookers must be stateless and side-effect-free +- **Use `basic_sample_keys()`**: Always forward `__key__`, `__restore_key__`, `__subflavors__` +- **Keep it simple**: Cookers should only deserialize and restructure data, not transform it +- **Use subflavors for dispatch**: Let the framework choose the right cooker + +### 3. TaskEncoder design +- **Single responsibility**: One task encoder per data format family +- **Minimal `batch()`**: Only stack tensors, avoid computation in the batch method +- **Separate concerns**: Data loading in Cooker, encoding in model forward_step + +### 4. Dataloader configuration +- **Num workers**: Match CPU cores (typically 4-8) +- **Batch size**: Max out GPU memory +- **Shuffle**: Always true for training +- **Drop last**: True to avoid irregular batches + +### 5. Debugging +- **Small dataset**: Test with 100 samples first (`--max-samples 100`) +- **Single worker**: Set `num_workers=1` for debugging +- **Validate**: Run `python -m primus.backends.megatron.data.diffusion.preprocessing.validate /path/to/dataset` + +--- + +## Customization patterns + +### Custom cooker function + +Add a new cooker for a different data format: + +```python +from megatron.energon import Cooker, basic_sample_keys, stateless + +@stateless +def cook_my_custom_format(sample: dict) -> DiffusionSample: + """Custom cooker for a different data layout.""" + latents = torch.load(io.BytesIO(sample['vae_output.pth']), map_location='cpu') + prompt = torch.load(io.BytesIO(sample['text_embed.pth']), map_location='cpu') + pooled = torch.load(io.BytesIO(sample['clip_embed.pth']), map_location='cpu') + + return DiffusionSample( + **basic_sample_keys(sample), + latents=latents, + prompt_embeds=prompt, + pooled_prompt_embeds=pooled, + ) + +class CustomDiffusionTaskEncoder(DefaultTaskEncoder[DiffusionSample, DiffusionSample, dict, dict]): + decoder = SampleDecoder(image_decode="pil") + cookers = [ + Cooker(cook_my_custom_format, has_subflavors={"encoding": "custom_v2"}), + ] + + def batch(self, samples): + return { + 'latents': torch.stack([s.latents for s in samples]), + 'prompt_embeds': torch.stack([s.prompt_embeds for s in samples]), + 'pooled_prompt_embeds': torch.stack([s.pooled_prompt_embeds for s in samples]), + } +``` + +### Multiple cookers in one TaskEncoder + +A single TaskEncoder can register multiple cookers for different subflavors: + +```python +class MultiFormatTaskEncoder(DefaultTaskEncoder[DiffusionSample, DiffusionSample, dict, dict]): + cookers = [ + Cooker(cook_preencoded_diffusion, has_subflavors={"encoding": "preencoded"}), + Cooker(cook_my_custom_format, has_subflavors={"encoding": "custom_v2"}), + ] +``` + +--- + +## References + +- **Megatron-Energon**: [nvidia/Megatron-LM](https://github.com/NVIDIA/Megatron-LM) multimodal examples +- **WebDataset**: [webdataset/webdataset](https://github.com/webdataset/webdataset) +- **NeMo Implementation**: `nemo/collections/diffusion/data/` +- **Primus TaskEncoders**: `primus/backends/megatron/data/diffusion/task_encoders/image.py` +- **Primus Dataloader**: `primus/backends/megatron/data/dataloader.py` + +--- + +**Last Updated**: March 2026 diff --git a/docs/04-technical-guides/diffusion-models/flux_architecture.md b/docs/04-technical-guides/diffusion-models/flux_architecture.md new file mode 100644 index 000000000..61dcb6bb6 --- /dev/null +++ b/docs/04-technical-guides/diffusion-models/flux_architecture.md @@ -0,0 +1,926 @@ +# Flux architecture deep dive + +## Table of contents + +1. [Overview](#overview) +2. [Architecture principles](#architecture-principles) +3. [Model components](#model-components) +4. [Data flow](#data-flow) +5. [Mathematical formulation](#mathematical-formulation) +6. [Implementation details](#implementation-details) +7. [Megatron-core integration](#megatron-core-integration) +8. [Performance optimizations](#performance-optimizations) + +--- + +## Overview + +Flux is a **flow-based diffusion model** for high-quality text-to-image generation. It uses an innovative **MMDiT (Multimodal Diffusion Transformer)** architecture that jointly processes image and text tokens through shared transformer blocks. + +### Key innovations + +1. **Flow Matching**: Uses rectified flow instead of traditional diffusion +2. **MMDiT Architecture**: Joint image-text attention in early layers +3. **3D RoPE**: Multi-dimensional rotary position embeddings for spatial awareness +4. **Two-Stage Processing**: Joint layers followed by image-only layers + +### Model variants + +| Variant | Joint Layers | Single Layers | Parameters | Use Case | +|---------|--------------|---------------|------------|----------| +| Flux 535M | 1 | 1 | ~535M | Development, testing | +| Flux 12B | 19 | 38 | ~12B | Production deployment | + +--- + +## Architecture principles + +### 1. Flow matching framework + +Unlike traditional diffusion (which adds Gaussian noise), Flux uses **rectified flow**: + +``` +Forward process: z_t = (1-t) * z_0 + t * z_1 +where: + - z_0 = original image (latent) + - z_1 = random noise + - t ∈ [0, 1] is the flow timestep +``` + +The model predicts the **velocity field** v_θ: + +``` +v_θ(z_t, t, c) ≈ z_1 - z_0 +``` + +**Advantages**: +- Straight-line interpolation paths (more efficient than diffusion curves) +- Faster sampling (fewer steps needed) +- Better training stability + +### 2. MMDiT (multimodal diffusion transformer) + +Traditional DiT processes image tokens independently. MMDiT jointly processes image and text: + +``` +┌─────────────┐ ┌─────────────┐ +│ Image Tokens│ │ Text Tokens │ +└──────┬──────┘ └──────┬──────┘ + │ │ + └───────┬───────────┘ + │ + ┌──────▼──────┐ + │ Joint Attn │ ← Cross-attend image ↔ text + └──────┬──────┘ + │ + ┌──────▼──────┐ + │ Split │ + └──┬───────┬──┘ + │ │ + ┌───────▼──┐ ┌─▼────────┐ + │ Image MLP│ │ Text MLP │ ← Separate processing + └──────────┘ └──────────┘ +``` + +**Benefits**: +- Better text-image alignment +- Richer cross-modal interactions +- Improved compositional understanding + +### 3. Two-stage processing + +Flux uses a unique two-stage architecture: + +**Stage 1: Joint Processing (Double Blocks)** +- Both image and text tokens +- Multi-modal attention +- Rich semantic understanding + +**Stage 2: Image Refinement (Single Blocks)** +- Image tokens only (text concatenated but not separated) +- Focus on spatial coherence +- Fine-grained detail generation + +--- + +## Model components + +### 1. Input embeddings + +#### Image path + +``` +Image (RGB) → VAE Encoder → Latents [B, 64, H/8, W/8] + ↓ + Patchify + Linear + ↓ + Image Tokens [B, H*W, 3072] +``` + +**VAE**: AutoencoderKL (8x downsampling) +- Input: 1024×1024 RGB image +- Output: 64×128×128 latent + +**Linear Projection**: Maps 64 channels → 3072 hidden dim + +#### Text path + +``` +Caption → T5-XXL Encoder → Embeddings [B, S, 4096] + ↓ + Linear Projection + ↓ + Text Tokens [B, S, 3072] + +Caption → CLIP-L Encoder → Pooled [B, 768] + ↓ + MLP Embedder + ↓ + Vector Embedding [B, 3072] +``` + +**T5-XXL**: Context-rich text embeddings (max 512 tokens) +**CLIP-L**: Global style/semantic vector (768 dim) + +#### Conditioning + +``` +Timestep t ∈ [0, 1] → Sinusoidal Encoding → [B, 256] + ↓ + MLP + ↓ + Timestep Embedding [B, 3072] + +(Optional) Guidance scale g → Linear → [B, 3072] +``` + +**Combined Conditioning Vector**: +``` +vec = timestep_emb + clip_pooled_emb + [guidance_emb] +``` + +### 2. Position embeddings (3D RoPE) + +Flux uses **3D Rotary Position Embeddings** for spatial awareness: + +**Axes**: +1. **Axis 0**: Channel groups (16 groups for 64 channels) +2. **Axis 1**: Height positions (e.g., 128 for 1024px) +3. **Axis 2**: Width positions (e.g., 128 for 1024px) + +**Implementation**: +```python +# Generate position IDs for each axis +pos_ids = [ + (h * W + w) // (16 * patch_size), # Axis 0: channel group + h, # Axis 1: height + w, # Axis 2: width +] + +# Compute frequencies for each axis +theta_i = theta ^ (2i / dim_axis) +freqs = pos_id / theta_i + +# Apply RoPE rotation +cos_freq = cos(freqs) +sin_freq = sin(freqs) +``` + +**Advantages**: +- Encodes spatial structure (height × width) +- Encodes channel relationships +- Works for any resolution (generalization) + +### 3. MMDiT layer (double block) + +Each MMDiT layer performs: + +``` +Input: img [B, H*W, D], txt [B, S, D], vec [B, D] + +1. Pre-normalization (AdaLN with timestep conditioning) + img_norm = AdaLN(img, vec) + txt_norm = AdaLN(txt, vec) + +2. Joint Self-Attention + img_qkv = Linear(img_norm) # [B, H*W, 3*D] + txt_qkv = Linear(txt_norm) # [B, S, 3*D] + + # Concatenate for joint attention + joint_qkv = concat([img_qkv, txt_qkv], dim=1) # [B, H*W+S, 3*D] + + # Apply attention + joint_out = Attention(joint_qkv) # [B, H*W+S, D] + + # Split back + img_attn, txt_attn = split(joint_out, [H*W, S]) + +3. Gated Residual Addition + img = img + gate_img * img_attn + txt = txt + gate_txt * txt_attn + +4. Feed-Forward Networks (separate for img and txt) + img_mlp = AdaLN(img, vec) → Linear → GELU → Linear + txt_mlp = AdaLN(txt, vec) → Linear → GELU → Linear + + img = img + gate_img_mlp * img_mlp + txt = txt + gate_txt_mlp * txt_mlp + +Output: img [B, H*W, D], txt [B, S, D] +``` + +**Key Features**: +- **Shared attention space**: Image and text attend to each other +- **Adaptive gating**: Timestep-conditioned residual connections +- **Separate MLPs**: Modality-specific processing + +### 4. Flux single block + +After joint processing, image tokens go through single blocks: + +``` +Input: img [B, H*W, D], txt [B, S, D], vec [B, D] + +1. Concatenate (but don't split later) + combined = concat([img, txt], dim=1) # [B, H*W+S, D] + +2. Pre-normalization (AdaLN) + combined_norm = AdaLN(combined, vec) + +3. Self-Attention + qkv = Linear(combined_norm) # [B, H*W+S, 3*D] + attn_out = Attention(qkv) # [B, H*W+S, D] + +4. Gated Residual + combined = combined + gate * attn_out + +5. Feed-Forward + mlp_norm = AdaLN(combined, vec) + mlp_out = Linear → GELU → Linear + combined = combined + gate_mlp * mlp_out + +6. Extract image tokens + img = combined[:, :H*W, :] # Only use image part + +Output: img [B, H*W, D] (text tokens discarded) +``` + +**Rationale**: +- Text still influences attention (as keys/values) +- Output focuses on image generation +- More efficient than full joint processing + +### 5. Output processing + +``` +Image Tokens [B, H*W, D] + ↓ + AdaLNContinuous(vec) ← Final timestep conditioning + ↓ + Linear Projection + ↓ + [B, H*W, 64] + ↓ + Reshape + ↓ + [B, 64, H, W] ← Predicted velocity field +``` + +--- + +## Data flow + +### Complete forward pass + +``` + Input + │ + ┌───────────────────┼───────────────────┐ + │ │ │ + Image Text Timestep + [B,64,H,W] [B,S,4096] [B] + │ │ │ + ▼ ▼ ▼ + img_linear txt_linear time_emb + │ │ │ + ├─────── + ─────────┴─────── vec ──────┤ + │ (conditioning) │ + ▼ │ + img [B,H*W,3072] │ + txt [B,S,3072] │ + │ │ + │ ┌──────────┐ │ + └──────────────► MMDiT ├────◄───────┤ + ┌──────────────◄ Layer 1 ├────────────┘ + │ └──────────┘ + │ ┌──────────┐ + └──────────────► MMDiT ├────◄───────┐ + ┌──────────────◄ Layer 2 ├────────────┤ + │ └──────────┘ │ + ... vec + │ ┌──────────┐ │ + └──────────────► MMDiT ├────◄───────┘ + ┌──────────────◄ Layer N ├────────────┐ + │ └──────────┘ │ + │ │ + img [B,H*W,3072] │ + txt [B,S,3072] │ + │ │ + │ ┌──────────┐ │ + └──────────────► Single ├────◄───────┤ + ┌──────────────◄ Block 1 ├────────────┘ + │ └──────────┘ + │ ┌──────────┐ + └──────────────► Single ├────◄───────┐ + ┌──────────────◄ Block 2 ├────────────┤ + │ └──────────┘ │ + ... vec + │ ┌──────────┐ │ + └──────────────► Single ├────◄───────┘ + ┌──────────────◄ Block M ├────────────┐ + │ └──────────┘ │ + │ │ + img [B,H*W,3072] │ + │ │ + ▼ │ + AdaLNContinuous ◄───────────────────────────┘ + │ + ▼ + Linear Projection + │ + ▼ + Reshape to [B,64,H,W] + │ + ▼ + Predicted Velocity +``` + +### Training data flow + +``` +Original Image + │ + ▼ + VAE Encode → z_0 [B,64,H,W] + │ + ├─────────────┐ + │ │ + ▼ ▼ + z_0 Sample Noise → z_1 + │ │ + │ Sample t ~ Uniform(0,1) + │ │ + └──────┬──────┘ + │ + z_t = (1-t)*z_0 + t*z_1 ← Noisy latent + │ + ▼ + Flux Model(z_t, text, t) + │ + ▼ + v_pred [B,64,H,W] ← Predicted velocity + │ + ▼ + Loss = MSE(v_pred, z_1 - z_0) ← Flow matching loss +``` + +--- + +## Mathematical formulation + +### Flow matching objective + +**Forward Process**: +``` +z_t = (1 - t) * z_0 + t * z_1, where t ~ Uniform(0, 1) +``` + +**Velocity Target**: +``` +v* = dz_t/dt = z_1 - z_0 +``` + +**Training Loss**: +``` +L = E_{z_0, z_1, t, c} [ ||v_θ(z_t, t, c) - (z_1 - z_0)||² ] + +where: + z_0 = VAE(image) # Original latent + z_1 ~ N(0, I) # Random noise + c = (text_emb, clip_pooled) # Conditioning + v_θ = Flux model # Predicted velocity +``` + +### Sampling (inference) + +**Euler Integration** (first-order ODE solver): +``` +z_0 = z_1 # Start from noise +for t in [1.0, 0.9, ..., 0.1, 0.0]: + v_t = Flux(z_t, t, c) + z_{t-Δt} = z_t - Δt * v_t +``` + +**Higher-Order Solvers** (optional): +- Heun's method (2nd order) +- DPM-Solver (adaptive) + +### Classifier-free guidance + +During inference, use guidance scale `w`: + +``` +v_guided = v_uncond + w * (v_cond - v_uncond) + +where: + v_cond = Flux(z_t, t, c_text) # With text + v_uncond = Flux(z_t, t, c_empty) # Without text (null prompt) + w = guidance scale (typically 3-5) +``` + +**Implementation**: Use guidance embedding in config: +```python +config = FluxConfig(guidance_embed=True, guidance_scale=3.5) +``` + +### 3D RoPE mathematics + +For position `(h, w)` in image: + +**Position IDs**: +``` +pid = [floor((h*W + w) / 16), h, w] # [channel_group, height, width] +``` + +**Frequencies**: +``` +θ_i = θ_base ^ (2i / d_axis), for i = 0, ..., d_axis/2 + +freq_{axis,i} = pid[axis] / θ_i +``` + +**RoPE Rotation**: +``` +q_rot = [q[:d/2] * cos(freq) - q[d/2:] * sin(freq), + q[:d/2] * sin(freq) + q[d/2:] * cos(freq)] + +k_rot = [k[:d/2] * cos(freq) - k[d/2:] * sin(freq), + k[:d/2] * sin(freq) + k[d/2:] * cos(freq)] +``` + +--- + +## Implementation details + +### Memory layout + +Megatron-Core uses **sequence-first format**: `[seq, batch, hidden]` + +**Conversions**: +```python +# User format: [B, C, H, W] +img_latents = torch.randn(B, 64, H, W) + +# Reshape to [B, H*W, 64] +img_seq = rearrange(img_latents, 'b c h w -> b (h w) c') + +# Project to hidden_size +img_tokens = linear(img_seq) # [B, H*W, 3072] + +# Convert to Megatron format: [seq, batch, hidden] +img_megatron = rearrange(img_tokens, 'b s d -> s b d') +``` + +### Adaptive layer normalization + +**Standard AdaLN**: +```python +class AdaLN: + def forward(self, timestep_emb): + modulation = MLP(SiLU(timestep_emb)) + shift, scale, gate = split(modulation, 3) + return shift, scale, gate + + @staticmethod + def modulate(x, shift, scale): + return LayerNorm(x) * (1 + scale) + shift +``` + +**Usage in Layer**: +```python +shift, scale, gate = adaln(timestep_emb) +x_norm = AdaLN.modulate(x, shift, scale) +x_attn = attention(x_norm) +x = x + gate * x_attn # Gated residual +``` + +### Attention implementation + +**Using Megatron SelfAttention**: +```python +from megatron.core.transformer.attention import SelfAttention + +attn = SelfAttention( + config=config, + submodules=submodules, + layer_number=layer_idx, + attn_mask_type=AttnMaskType.no_mask, # Flux uses no mask +) + +# Megatron expects [seq, batch, hidden] +output = attn(hidden_states) +``` + +**Joint Attention Trick**: +```python +# Concatenate image and text +combined = torch.cat([img, txt], dim=0) # [seq_img+seq_txt, batch, hidden] + +# Single attention call processes both +joint_output = attention(combined) + +# Split back +img_out = joint_output[:seq_img] +txt_out = joint_output[seq_img:] +``` + +--- + +## Megatron-core integration + +### TransformerConfig + +Flux uses Megatron's `TransformerConfig`: + +```python +from megatron.core.transformer.transformer_config import TransformerConfig + +config = TransformerConfig( + num_layers=19 + 38, # joint + single + hidden_size=3072, + num_attention_heads=24, + ffn_hidden_size=3072 * 4, # Standard 4x expansion + layernorm_epsilon=1e-6, + hidden_dropout=0.0, + attention_dropout=0.0, + add_qkv_bias=True, + # ... other Megatron params +) +``` + +### Layer specs + +Flux provides factory functions for layer specs: + +```python +from primus.backends.megatron.core.models.diffusion.flux.layer_spec import ( + get_flux_double_transformer_spec_for_backend, + get_flux_single_transformer_spec_for_backend, + get_flux_layer_spec, +) + +# For MMDiT layers (pass backend from config) +double_spec = get_flux_double_transformer_spec_for_backend(backend) + +# For single blocks +single_spec = get_flux_single_transformer_spec_for_backend(backend) + +# Or use high-level API for full TransformerBlock +layer_specs = get_flux_layer_spec(config, backend=backend) +``` + +### Distributed training support + +Flux inherits Megatron's parallelism: + +**Tensor Parallelism** (TP): +```python +config = TransformerConfig( + tensor_model_parallel_size=8, # 8-way TP + sequence_parallel=True, # Sequence parallelism +) +``` + +**Pipeline Parallelism** (PP): not supported for diffusion models. The forward +path runs embeddings/output head on every rank and does not relay activations +between stages, so `pipeline_model_parallel_size` must be 1 (PP > 1 is rejected +at config construction). + +**Data Parallelism**: Handled automatically by trainer + +--- + +## Performance optimizations + +### 1. Transformer engine + +Flux uses NVIDIA Transformer Engine for FP8 training: + +```python +from megatron.core.extensions.transformer_engine import ( + TELayerNormColumnParallelLinear, + TERowParallelLinear, +) + +# Automatically used in layer specs +linear_qkv = TELayerNormColumnParallelLinear(...) +linear_proj = TERowParallelLinear(...) +``` + +**Benefits**: +- FP8 matmul (faster, less memory) +- Fused operations (LayerNorm + Linear) +- Automatic scaling for numerical stability + +### 2. Flash attention + +Enabled via Megatron: + +```python +config = TransformerConfig( + attention_type='flash_attention', # Use Flash Attention 2 +) +``` + +**Speedup**: 2-3x faster attention, 4x less memory + +### 3. Gradient checkpointing + +For large models: + +```python +config = TransformerConfig( + recompute_granularity='selective', # Checkpoint expensive ops + recompute_method='uniform', + recompute_num_layers=19, # Checkpoint all joint layers +) +``` + +**Memory Savings**: ~40% reduction, ~20% slower + +### 4. Fused operations + +```python +config = TransformerConfig( + bias_activation_fusion=True, # Fuse bias + activation + masked_softmax_fusion=True, # Fuse mask + softmax + gradient_accumulation_fusion=True, # Fuse grad accumulation +) +``` + +### 5. Mixed precision + +```python +from primus.backends.megatron.training.diffusion.loss_computation import compute_flow_matching_loss + +# Using PyTorch autocast +with torch.autocast(device_type='cuda', dtype=torch.bfloat16): + output = model(img, txt, y, timesteps, img_ids, txt_ids) + target = noise - clean_latents + loss = compute_flow_matching_loss(output, clean_latents, noise) + +# Backward pass (handled automatically) +scaler = torch.cuda.amp.GradScaler() +scaler.scale(loss).backward() +scaler.step(optimizer) +scaler.update() +``` + +--- + +## Comparison with other models + +### Flux vs DiT + +| Aspect | DiT | Flux | +|--------|-----|------| +| Architecture | Image-only transformer | MMDiT (image + text joint) | +| Conditioning | AdaLN (injected) | Joint attention | +| Position Encoding | Learned 2D | 3D RoPE | +| Diffusion Type | DDPM | Flow matching | + +### Flux vs stable diffusion + +| Aspect | Stable Diffusion (UNet) | Flux (Transformer) | +|--------|-------------------------|---------------------| +| Backbone | UNet with ResNet blocks | Full transformer | +| Text Integration | Cross-attention | Joint self-attention | +| Scalability | Limited (U-Net bottleneck) | Excellent (transformer scaling) | +| Efficiency | Fast (fewer params) | Slower but higher quality | + +--- + +## Design decisions + +### Why two-stage (joint + single)? + +**Joint Blocks**: +- Deep semantic understanding +- Text-image alignment +- Compositional reasoning + +**Single Blocks**: +- Spatial refinement +- Detail generation +- Efficient (no text processing) + +**Alternative**: All joint blocks → slower, marginal quality gain + +### Why flow matching? + +**Advantages over DDPM**: +1. **Simpler training**: Straight-line interpolation (no schedule design) +2. **Faster sampling**: Fewer steps (10-20 vs 50-100) +3. **Better mode coverage**: Straighter paths → less error accumulation + +**Math**: Rectified flow is ODE-based (vs SDE for DDPM) + +### Why 3D RoPE? + +**Advantages**: +1. **Spatial awareness**: Encodes (height, width) structure +2. **Resolution flexibility**: Works for any image size +3. **Channel grouping**: Models relationships between channels + +**Alternative**: Learned absolute embeddings → less flexible + +--- + +## Primus implementation highlights + +### TransformerBlock architecture + +Primus's key architectural enhancement is the use of Megatron-Core's **TransformerBlock with heterogeneous layer specifications**: + +```mermaid +graph TD + subgraph traditional[Traditional Approach] + A[Model] --> B[double_blocks: ModuleList] + A --> C[single_blocks: ModuleList] + B --> D[Manual iteration] + C --> D + D --> E[Manual PP splitting] + end + + subgraph primus[Primus Approach] + F[Model] --> G[transformer: TransformerBlock] + G --> H[layer_specs: heterogeneous] + H --> J[Unified checkpoint format] + end +``` + +**Benefits**: +1. **Unified Checkpointing**: Single `transformer.layers.{0-56}` namespace +2. **Future-Proof**: Native support for new Megatron-Core features +3. **Cleaner Code**: No manual iteration over separate block lists + +### Layer specification pattern + +```python +# Primus approach +layer_specs = get_flux_layer_spec(config, backend=backend) +# Or manually: +# layer_specs = [ +# *[get_flux_double_transformer_spec_for_backend(backend) for _ in range(19)], +# *[get_flux_single_transformer_spec_for_backend(backend) for _ in range(38)], +# ] + +transformer = TransformerBlock( + config=config, + spec=TransformerBlockSubmodules(layer_specs=layer_specs) +) + +# Automatic PP slicing handled by TransformerBlock +# No manual offset calculation needed +``` + +### Checkpoint format comparison + +**Traditional Format**: +``` +double_blocks.0.attn.qkv.weight +double_blocks.18.mlp.fc2.bias +single_blocks.0.attn.qkv.weight +single_blocks.37.mlp.fc2.bias +``` + +**Primus Format** (TransformerBlock): +``` +transformer.layers.0.self_attention.linear_qkv.weight # Joint layer 0 +transformer.layers.18.mlp.linear_fc2.bias # Joint layer 18 +transformer.layers.19.self_attention.linear_qkv.weight # Single layer 0 +transformer.layers.56.mlp.linear_fc2.bias # Single layer 37 +``` + +Benefits: Simpler distributed checkpointing, easier layer inspection, consistent with Megatron GPT models. + +--- + +## Future enhancements + +### Planned features + +1. **ControlNet Support**: + - Spatial conditioning (pose, depth, edges) + - Residual connections from control encoder + +2. **Multi-Resolution Training**: + - Dynamic image sizes during training + - Aspect ratio bucketing + +3. **Efficient Sampling**: + - DPM-Solver integration + - Distillation for 1-step generation + +4. **LoRA Fine-Tuning**: + - Low-rank adaptation for custom styles + - Efficient personalization + +### Research directions + +- **Sparse Attention**: Reduce quadratic complexity for high-res +- **Mixture of Experts**: Conditional computation for efficiency +- **3D Extension**: Video generation with Flux architecture + +--- + +## References + +### Papers + +1. **Flow Matching**: Lipman et al., "Flow Matching for Generative Modeling", 2022 +2. **Rectified Flow**: Liu et al., "Flow Straight and Fast: Learning to Generate and Transfer Data with Rectified Flow", 2022 +3. **DiT**: Peebles & Xie, "Scalable Diffusion Models with Transformers", 2023 +4. **RoPE**: Su et al., "RoFormer: Enhanced Transformer with Rotary Position Embedding", 2021 +5. **Transformer Engine**: NVIDIA, "Transformer Engine: Accelerating Transformer Training", 2022 + +### Code references + +- **Primus Flux**: `primus/backends/megatron/core/models/diffusion/flux/` +- **Megatron-Core**: `megatron/core/transformer/` +- **Transformer Engine**: `transformer_engine/pytorch/` +- **Official Flux**: Black Forest Labs (HuggingFace) + +--- + +## Appendix: Hyperparameters + +### Flux 535M training + +```yaml +model: + num_joint_layers: 1 + num_single_layers: 1 + hidden_size: 3072 + num_attention_heads: 24 + +training: + batch_size: 256 # global + learning_rate: 1e-4 + weight_decay: 0.01 + lr_schedule: cosine + warmup_steps: 10000 + total_steps: 500000 + +optimization: + optimizer: AdamW + beta1: 0.9 + beta2: 0.999 + epsilon: 1e-8 + gradient_clip: 1.0 +``` + +### Flux 12B training + +```yaml +model: + num_joint_layers: 19 + num_single_layers: 38 + hidden_size: 3072 + num_attention_heads: 24 + +training: + batch_size: 2048 # global, multi-node + learning_rate: 1e-4 + weight_decay: 0.01 + lr_schedule: cosine + warmup_steps: 10000 + total_steps: 1000000 + +optimization: + optimizer: AdamW + beta1: 0.9 + beta2: 0.95 + epsilon: 1e-8 + gradient_clip: 1.0 + +parallelism: + tensor_parallel: 8 + pipeline_parallel: 4 + data_parallel: 8 + sequence_parallel: True +``` + +--- + +*For API usage, see [api_reference.md](api_reference.md).* diff --git a/docs/04-technical-guides/diffusion-models/fp8_training.md b/docs/04-technical-guides/diffusion-models/fp8_training.md new file mode 100644 index 000000000..9067bf201 --- /dev/null +++ b/docs/04-technical-guides/diffusion-models/fp8_training.md @@ -0,0 +1,514 @@ +# FP8 training for Flux models + +Complete guide for training Flux diffusion models with FP8 (8-bit floating point) precision on AMD MI300X GPUs using Transformer Engine's delayed scaling recipe. + +## Overview + +FP8 training provides significant memory and speed improvements while maintaining numerical stability through delayed scaling: + +- **~2x memory reduction** (activations and weights) +- **1.5-2x training speedup** on AMD MI300X +- **Maintains numerical stability** via delayed scaling +- **Enables larger batch sizes** or higher resolutions + +## Table of contents + +- [Prerequisites](#prerequisites) +- [Quick start](#quick-start) +- [Configuration](#configuration) +- [Performance benchmarks](#performance-benchmarks) +- [Troubleshooting](#troubleshooting) +- [Best practices](#best-practices) +- [AMD MI300X specific](#amd-mi300x-specific) + +--- + +## Prerequisites + +### Hardware requirements + +- **AMD MI300X GPUs** with ROCm 6.0+ support +- **Minimum GPUs:** + - Flux 535M: 1x MI300X (testing) + - Flux 12B: 2x MI300X with TP=2 (can train with FP8) + +### Software requirements + +1. **ROCm 6.0+** with FP8 tensor core support +2. **Transformer Engine 2.1.0+** with ROCm backend +3. **PyTorch** with ROCm support +4. **Megatron-LM** (included in Primus) + +### Verification + +Verify your environment has: +- Transformer Engine 2.1.0+ with ROCm backend +- FP8 support (run `python3 -c "import transformer_engine.pytorch as te; print(te.fp8.is_fp8_available())"`) + +--- + +## Quick start + +### Test FP8 with Flux 535M (recommended first step) + +```bash +# 1. Prepare test dataset (or use existing) +# See primus/configs/data/megatron/diffusion/README.md + +# 2. Train Flux 535M with FP8 +EXP=examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain_fp8.yaml \ +GPUS_PER_NODE=1 \ +bash examples/run_pretrain.sh +``` + +### Production training with Flux 12B + +```bash +# After validating with 535M, scale to 12B (TransformerEngine FP8) +EXP=examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml \ +GPUS_PER_NODE=8 \ +NNODES=4 \ +bash examples/run_slurm_pretrain.sh + +# Or local-spec FP8 (no TransformerEngine dependency) +EXP=examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml \ +GPUS_PER_NODE=8 \ +NNODES=4 \ +bash examples/run_slurm_pretrain.sh +``` + +--- + +## Configuration + +### FP8 model configuration + +FP8 is configured at the model level. Two pre-configured files are available: + +- `primus/configs/models/megatron/diffusion/flux_535m_fp8.yaml` +- `primus/configs/models/megatron/diffusion/flux_12b_fp8.yaml` + +**Key FP8 Parameters:** + +```yaml +# Enable FP8 +fp8: "e4m3" # E4M3 format (recommended) + # Alternative: "hybrid" (E4M3 activations + E5M2 gradients) + +# FP8 Recipe +fp8_recipe: "delayed" # Delayed scaling (most stable) + # Alternatives: "tensorwise", "blockwise", "mxfp8" + +# Scaling Configuration +fp8_margin: 0 # Margin for scaling factor (0 = no margin) +fp8_amax_history_len: 1024 # History window for delayed scaling + # Larger = more stable, smaller = adapts faster +fp8_amax_compute_algo: "most_recent" # or "max" + +# Gradient Precision +fp8_wgrad: true # Enable FP8 for weight gradients (recommended) + +# Attention Precision +fp8_dot_product_attention: false # Keep attention in higher precision +fp8_multi_head_attention: false # Keep MHA in higher precision +``` + +### Training configuration adjustments + +**Batch Sizes with FP8:** + +```yaml +# Flux 12B - can increase batch size with FP8 memory savings +micro_batch_size: 2 # vs 1 for BF16 +global_batch_size: 256 # same as BF16 + +# Flux 535M - can increase significantly +micro_batch_size: 4 # vs 2 for BF16 +global_batch_size: 32 +``` + +**Optimizer Settings (Same as BF16):** + +```yaml +optimizer: adamw +lr: 1.0e-4 +min_lr: 1.0e-5 +weight_decay: 0.01 +clip_grad: 1.0 # Gradient clipping still important! +``` + +**Parallelism with FP8:** + +```yaml +# Flux 12B - can potentially reduce TP with FP8 +tensor_model_parallel_size: 2 # or reduce to 1 with FP8 +pipeline_model_parallel_size: 1 +context_parallel_size: 1 +``` + +--- + +## Performance benchmarks + +### Memory usage + +| Model | Precision | Memory/GPU | Batch Size | Notes | +|-------|-----------|------------|------------|-------| +| Flux 535M | BF16 | ~7-10GB | 2 | Baseline | +| Flux 535M | FP8 | ~3-5GB | 4 | ~50% reduction | +| Flux 12B | BF16 | ~40-50GB | 1 | TP=2 required | +| Flux 12B | FP8 | ~20-25GB | 2 | TP=2, ~50% reduction | + +### Training speed + +| Model | Precision | Steps/sec | Speedup | Hardware | +|-------|-----------|-----------|---------|----------| +| Flux 535M | BF16 | ~20-30 | 1.0x | 1x MI300X | +| Flux 535M | FP8 | ~30-50 | 1.5-2x | 1x MI300X | +| Flux 12B | BF16 | ~0.5-1.0 | 1.0x | 32x MI300X | +| Flux 12B | FP8 | ~0.8-1.5 | 1.5-2x | 32x MI300X | + +### Expected results + +- **Memory:** ~50% reduction vs BF16 +- **Speed:** 1.5-2x faster training +- **Quality:** Loss curves within 5% of BF16 +- **Convergence:** Similar or faster than BF16 + +--- + +## Troubleshooting + +### NaN or inf in losses + +**Problem:** Training becomes unstable with NaN/Inf values + +**Solutions:** + +1. **Increase scaling history:** + ```yaml + fp8_amax_history_len: 2048 # or 4096 + ``` + +2. **Disable FP8 for weight gradients:** + ```yaml + fp8_wgrad: false + ``` + +3. **Use more conservative scaling:** + ```yaml + fp8_amax_compute_algo: "max" # instead of "most_recent" + ``` + +4. **Add scaling margin:** + ```yaml + fp8_margin: 1 # or 2 + ``` + +5. **Keep first/last layers in BF16:** + ```yaml + first_last_layers_bf16: true + num_layers_at_start_in_bf16: 2 + num_layers_at_end_in_bf16: 2 + ``` + +### Out of memory even with FP8 + +**Problem:** Still hitting OOM errors with FP8 enabled + +**Solutions:** + +1. **Reduce micro batch size:** + ```yaml + micro_batch_size: 1 # back to minimum + ``` + +2. **Enable gradient checkpointing:** + ```yaml + recompute_granularity: "selective" # or "full" + recompute_method: "block" + ``` + +3. **Increase tensor parallelism:** + ```yaml + tensor_model_parallel_size: 4 # distribute more + ``` + +4. **Reduce sequence length:** + ```yaml + seq_length: 2048 # if applicable + ``` + +### FP8 not available + +**Problem:** Setup script shows "FP8 not available" + +**Checks:** + +1. **Verify GPU model:** + ```bash + rocm-smi --showproductname + # Should show MI300X + ``` + +2. **Check ROCm version:** + ```bash + rocm-smi --showversion + # Should be 6.0+ + ``` + +3. **Verify Transformer Engine:** + ```bash + python3 -c "import transformer_engine; print(transformer_engine.__version__)" + # Should be 2.1.0+ + ``` + +4. **Test FP8 directly:** + ```python + import transformer_engine.pytorch as te + print(te.fp8.is_fp8_available()) # Should be True + ``` + +### Slower than expected + +**Problem:** FP8 training is not faster than BF16 + +**Checks:** + +1. **Verify FP8 is actually enabled:** + - Check logs for FP8 context messages + - Run with `NCCL_DEBUG=INFO` to see precision info + +2. **Check batch size:** + - Ensure you increased micro_batch_size with FP8 + - Small batches may not show speedup + +3. **Verify tensor cores:** + - FP8 requires tensor core support + - Check ROCm driver configuration + +4. **Profile training:** + ```yaml + log_timers_to_tensorboard: true + ``` + - Compare FP8 vs BF16 step times + +--- + +## Best practices + +### Recommended workflow + +1. **Start with 535M:** + - Validate FP8 works correctly + - Test for 100-1000 steps + - Verify no NaN/Inf + +2. **Validate on small 12B run:** + - Train for 1000-5000 steps + - Compare loss with BF16 baseline + - Check memory and speed improvements + +3. **Production training:** + - Monitor closely for first 10K steps + - Watch for numerical issues + - Compare checkpoints with BF16 + +### Training configuration + +**Conservative (stable):** +```yaml +fp8_recipe: "delayed" +fp8_amax_history_len: 2048 +fp8_amax_compute_algo: "max" +fp8_wgrad: false +``` + +**Balanced (recommended):** +```yaml +fp8_recipe: "delayed" +fp8_amax_history_len: 1024 +fp8_amax_compute_algo: "most_recent" +fp8_wgrad: true +``` + +**Aggressive (maximum performance):** +```yaml +fp8_recipe: "tensorwise" # Requires TE 2.2.0+ +fp8_amax_history_len: 512 +fp8_amax_compute_algo: "most_recent" +fp8_wgrad: true +``` + +### Monitoring + +**Key metrics to watch:** + +1. **Loss curves:** + - Should be smooth (no spikes) + - Should decrease normally + - Compare with BF16 baseline + +2. **Gradient norms:** + - Should be stable + - No sudden jumps to infinity + +3. **Memory usage:** + - Should be ~50% of BF16 + - Check with `rocm-smi` + +4. **Training speed:** + - Should be 1.5-2x faster + - Measure steps/second + +### Checkpointing + +- **FP8 checkpoints are compatible with BF16** +- Can switch between FP8/BF16 training +- Optimizer state includes FP8 scaling factors +- Checkpoints are same size as BF16 + +--- + +## Autotune (local spec FP8) + +> **Scope:** This section covers the **local-spec** FP8 path (`PrimusTurboFloat8LocalSpecProvider`, no TransformerEngine), e.g. `flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml`. The TransformerEngine prerequisites and checks elsewhere in this guide (`te.fp8.is_fp8_available()`, "FP8 Not Available") do **not** apply here -- this path quantizes via Primus Turbo directly. + +### Enable autotune (and do not pin the FP8 GEMM backend) + +The local-spec FP8 kernels benefit from the Primus-Turbo autotuner, which picks the best backend per GEMM shape. Enable it with `PRIMUS_TURBO_AUTO_TUNE=1`. + +`PRIMUS_TURBO_AUTO_TUNE=1` is necessary but **not sufficient**: an explicit `PRIMUS_TURBO_GEMM_BACKEND` short-circuits autotune (the FP8 kernel dispatcher returns the user-specified backend before the autotune step), so it must be unset (or scoped so it does not cover FP8) for autotune to engage. + +**Note:** some base images bake `PRIMUS_TURBO_GEMM_BACKEND` as an *empty string* rather than leaving it unset. An empty value is not treated as "unset" and can raise `KeyError ''` on the first FP8 GEMM. If you hit this, `unset PRIMUS_TURBO_GEMM_BACKEND` before launching. + +```bash +unset PRIMUS_TURBO_GEMM_BACKEND # or scope it so it does not cover FP8 +export PRIMUS_TURBO_AUTO_TUNE=1 + +EXP=examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml \ + bash examples/run_pretrain.sh +``` + +### Contrast with MXFP4 + +For MXFP4/FP4 + AITER with a tuned CSV, do the **opposite**: leave `PRIMUS_TURBO_AUTO_TUNE` unset, because autotune disables the AITER preshuffle fast path. See the [MXFP4 Training Guide](mxfp4_training.md) ("Preshuffle fast path"). Do not copy the MXFP4 env recipe for FP8. + +--- + +## AMD MI300X specific + +### Environment variables + +```bash +# Optional: set for better performance +export HSA_FORCE_FINE_GRAIN_PCIE=1 # Better PCIe performance +export NCCL_DEBUG=INFO # For debugging +export HSA_ENABLE_SDMA=0 # Disable SDMA for stability +``` + +### ROCm optimization + +1. **HipBLASLt tuning:** + ```bash + # Generate optimal GEMM kernels for your hardware + # See ROCm documentation for details + ``` + +2. **NCCL configuration:** + ```bash + export NCCL_IB_DISABLE=0 # Enable InfiniBand if available + export NCCL_NET_GDR_LEVEL=3 # GPU Direct RDMA + ``` + +3. **Memory management:** + ```bash + export HSA_OVERRIDE_GFX_VERSION=9.4.2 # For MI300X + ``` + +### Known issues + +1. **Transformer Engine ROCm support:** + - Verify TE version supports ROCm FP8 + - Some recipes may require specific TE versions + +2. **Numerical stability:** + - MI300X may require longer history (fp8_amax_history_len) + - Start conservative and tune + +3. **Multi-node training:** + - Ensure RCCL/NCCL properly configured + - Test single-node first + +--- + +## Testing + +### Integration test + +```bash +# Quick 100-step validation run +EXP=examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain_fp8.yaml \ +GPUS_PER_NODE=1 \ +bash examples/run_pretrain.sh +``` + +### Convergence test + +1. Train both BF16 and FP8 for 5000 steps +2. Compare loss curves (should be within 5%) +3. Generate images from checkpoints +4. Compare quality visually + +--- + +## Numerical verification status + +To set expectations for external users, the precision/convergence claims in this +codebase fall into two tiers: + +- **Backed by in-repo tests** (CI-runnable on supported hardware): structural and + convention checks—attention TE-vs-local-spec equivalence, RNG/seed + determinism, chimera init, VAE resample reproducibility, fused delayed-scale + update, and MLPerf warmup FP8 state. +- **Asserted, not yet backed by an in-repo test:** end-to-end *tensor parity* + against HuggingFace/Diffusers FLUX from a real checkpoint, and the exact MLPerf + v5.1 eval sample-count / validation-timestep semantics. These are validated by + internal reference runs but no committed test reproduces them. + +Tracked follow-ups (file as public-repo issues): + +1. A real-checkpoint forward-parity test (535M minimum) comparing Primus Flux + against HF/Diffusers within a documented tolerance. +2. A robustness test for the MLPerf validation-timestep fallback path. + +Treat any "bit-exact / matches NeMo / matches MLPerf / within X%" statement in +source comments as *asserted, unverified* until the parity test above lands. + +## References + +- [Transformer Engine Documentation](https://docs.nvidia.com/deeplearning/transformer-engine/) +- [AMD ROCm Documentation](https://rocm.docs.amd.com/) +- [Megatron-LM FP8 Guide](https://github.com/NVIDIA/Megatron-LM/blob/main/docs/llm/fp8.md) +- [FP8 Formats Explained (E4M3 vs E5M2)](https://arxiv.org/abs/2209.05433) + +--- + +## Support + +For issues or questions: + +1. Check this guide first +2. Verify Transformer Engine and FP8 support (see Prerequisites) +3. Review logs for error messages +4. File issue with: + - Hardware specs (GPU model, ROCm version) + - Software versions (TE, PyTorch, Megatron-LM) + - Config files used + - Error logs + +--- + +**Happy FP8 Training! 🚀** + +*Last updated: 2026-01-10* diff --git a/docs/04-technical-guides/diffusion-models/mxfp4_training.md b/docs/04-technical-guides/diffusion-models/mxfp4_training.md new file mode 100644 index 000000000..d45f64f52 --- /dev/null +++ b/docs/04-technical-guides/diffusion-models/mxfp4_training.md @@ -0,0 +1,210 @@ +# MXFP4 training for Flux models + +Guide for training Flux diffusion models in **MXFP4** (E2M1 mantissa + E8M0 block-of-32 scales) on AMD MI355X GPUs using Primus's local-spec MXFP4 implementation backed by Primus-Turbo and AITER. + +## Overview + +MXFP4 stores activations and weights in 4-bit microscale floating-point with one E8M0 exponent shared per block of 32 elements. The Primus integration: + +- Uses a **local spec** (`PrimusTurboMXFP4LocalSpecProvider`) with **no Transformer Engine dependency**—MXFP4 linear layers are self-contained autograd `Function`s that call Primus-Turbo's `gemm_fp4_impl` directly, so the path is `torch.compile`-friendly with minimal graph breaks. +- Keeps **attention, optimizer state / main params, and inter-rank communication in BF16**. Only the MMA inputs of the column- and row-parallel linears are quantized. +- Supports two backward modes via `mxfp4_backward_precision`: pure **MXFP4** (default) or **FP8** hybrid (E5M2 backward with tensorwise scaling on HipBLASLt). +- Dispatches the FP4 GEMM through Primus-Turbo's pluggable backend layer, which can route to either AITER (recommended for MI355X) or HipBLASLt. + +## Table of contents + +- [Prerequisites](#prerequisites) +- [Quick start](#quick-start) +- [Configuration](#configuration) +- [Primus-Turbo backend selection](#primus-turbo-backend-selection) +- [Tuned GEMMs](#tuned-gemms) +- [Troubleshooting](#troubleshooting) +- [Verification status](#verification-status) + +--- + +## Prerequisites + +### Hardware + +- **AMD Instinct MI355X** (gfx950) with FP4 tensor-core support. The MXFP4 linear-layer modules assert `check_mxfp4_support()` at construction and will refuse to initialize on unsupported devices ([`primus/backends/megatron/core/extensions/primus_turbo_mxfp4_local.py`](../../../primus/backends/megatron/core/extensions/primus_turbo_mxfp4_local.py)). +- Single node (the local-spec layers require `tensor_model_parallel_size: 1`). + +### Software + +- ROCm-compatible install of `aiter` (provides `aiter.gemm_a4w4` and the tuned-config loader in `aiter/jit/core.py`). +- Primus-Turbo with FP4 backend registered (`primus_turbo.pytorch.kernels.gemm.gemm_fp4_impl`). +- `enable_primus_turbo: true` and `use_turbo_attention: true` in the training config. + +--- + +## Quick start + +The verified MXFP4 config is `examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_mxfp4.yaml`. Launch with the AITER backend and the pre-tuned GEMM CSV: + +```bash +# Path to a checkout of the `tuned_gemm_configs` directory. +# Set TUNED_GEMM_DIR to wherever you have the tuned configs available. +export TUNED_GEMM_DIR=${TUNED_GEMM_DIR:-/path/to/tuned_gemm_configs} + +export EXP=examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_mxfp4.yaml +export PRIMUS_TURBO_GEMM_BACKEND=FP4:AITER +export AITER_CONFIG_GEMM_A4W4=$TUNED_GEMM_DIR/mi355x/flux_12b.csv +export AITER_LOG_TUNED_CONFIG=1 # recommended: confirms each shape hits the CSV + +bash examples/run_pretrain.sh +``` + +The pre-tuned CSV is distributed via an internal tuned-config source (`tuned_gemm_configs/mi355x/flux_12b.csv`). If you do not have access, omit `AITER_CONFIG_GEMM_A4W4` and AITER will fall back to its bundled `a4w4_blockscale_tuned_gemm.csv` (slower for Flux 12B shapes). + +--- + +## Configuration + +The relevant overrides in [`examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_mxfp4.yaml`](../../../examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_mxfp4.yaml): + +```yaml +# MXFP4 precision +fp4: "mxfp4" +fp4_recipe: "mxfp4" # default is "nvfp4" in trainer_base.yaml; must override +mxfp4_backward_precision: "mxfp4" # "mxfp4" (pure) or "fp8" (hybrid) + +# Local spec + Primus-Turbo +transformer_impl: "local" +enable_primus_turbo: true +use_turbo_attention: true + +# Required by the MXFP4 linear-layer modules +tensor_model_parallel_size: 1 +gradient_accumulation_fusion: false +# sequence_parallel must remain false +``` + +### Knob semantics + +| Knob | Values | Notes | +|------|--------|-------| +| `fp4` | `"mxfp4"` | Top-level switch to enable FP4. | +| `fp4_recipe` | `"mxfp4"` for this guide | Default in [`primus/configs/modules/megatron/trainer_base.yaml`](../../../primus/configs/modules/megatron/trainer_base.yaml) is `nvfp4`; the MXFP4 config overrides it. | +| `mxfp4_backward_precision` | `"mxfp4"` or `"fp8"` | Exhaustive set (checked by branch in [`primus_turbo_mxfp4_local.py`](../../../primus/backends/megatron/core/extensions/primus_turbo_mxfp4_local.py)). `"fp8"` uses E5M2 with tensorwise HipBLASLt for backward. | +| `mxfp4_gradient_stochastic_rounding` | `true` / `false` | Optional. Enables SR on FP4 gradient quantization. | + +--- + +## Primus-Turbo backend selection + +The FP4 GEMM call is routed by `GEMMFP4KernelDispatcher` in `Primus-Turbo/primus_turbo/pytorch/kernels/gemm/gemm_fp4_impl.py`. Backends are selected with the precision-scoped env var `PRIMUS_TURBO_GEMM_BACKEND` (declared in `Primus-Turbo/primus_turbo/common/constants.py`): + +```bash +# Single backend for every precision: +export PRIMUS_TURBO_GEMM_BACKEND=AITER + +# Precision-scoped (recommended): route FP4 GEMMs to AITER, leave others to defaults: +export PRIMUS_TURBO_GEMM_BACKEND=FP4:AITER + +# Per-precision routing: +export PRIMUS_TURBO_GEMM_BACKEND=FP4:AITER,FP8:HIPBLASLT +``` + +The dispatcher (`GlobalBackendManager` / `AutoKernelDispatcher.dispatch` in `Primus-Turbo/primus_turbo/pytorch/core/backend.py`) resolves the backend in this order: **explicit env > code-set > auto-tune > registered default > fallback**. + +### Preshuffle fast path + +When **all** of the following are true, MXFP4 GEMMs take the preshuffled fast path with no per-call shuffle overhead: + +- `PRIMUS_TURBO_GEMM_BACKEND=FP4:AITER` (or `AITER`) is set. +- `PRIMUS_TURBO_AUTO_TUNE` is unset or `0`. + +The `_enable_preshuffle()` helper in `primus/backends/megatron/core/extensions/primus_turbo_mxfp4_local.py` returns `True` under these conditions (FP4 backend pinned to AITER and auto-tune off), and the call becomes `aiter.gemm_a4w4(..., bpreshuffle=True)`. This helper reproduces the upstream `enable_preshuffle()` that Primus-Turbo removed in PR #383 ("refactor preshuffle ..."), which moved per-call preshuffle control onto `Float4QuantConfig.use_preshuffle`; Primus keeps the runtime probe locally because `MXFP4LinearFunction` passes a plain `bool` into its custom ops. + +> **Do not combine `PRIMUS_TURBO_AUTO_TUNE=1` with a tuned CSV.** Auto-tune disables the preshuffle fast path, so each call pays the shuffle cost while AITER still picks the same kernel internally. For production runs, leave `PRIMUS_TURBO_AUTO_TUNE` unset. + +--- + +## Tuned GEMMs + +AITER reads its tuned-GEMM CSV from the `AITER_CONFIG_GEMM_A4W4` env var (handled in `aiter/jit/core.py`; the default is the bundled `aiter/configs/a4w4_blockscale_tuned_gemm.csv`). Each row maps `(cu_num, M, N, K)` to a profiled kernel and split-K factor. + +For Flux 12B on MI355X, the pre-tuned CSV is provided by an internal tuned-config source (`tuned_gemm_configs/mi355x/flux_12b.csv`). See that directory's `README.md` for the tuning runbook, CSV schema, ASM-vs-CK kernel distinction, and re-tuning triggers. + +### Verifying the CSV is being used + +Set `AITER_LOG_TUNED_CONFIG=1`. AITER will log one line per **hit**: + +``` +shape is M:16384, N:9216, K:3072, found padded_M: 16384, N:9216, K:3072 is tuned on cu_num = 256 in /path/to/tuned_gemm_configs/mi355x/flux_12b.csv, kernel name is _ZN5aiter42f4gemm_bf16_per1x32Fp4_BpreShuffle_256x256E, splitK is 0! +``` + +**Miss** lines are printed unconditionally (no env var required) and look like: + +``` +shape is M:..., N:..., K:..., not found tuned config in /path/to/flux_12b.csv, will use default config! +``` + +Any miss line means the CSV needs re-tuning for that shape—follow the runbook in `tuned_gemm_configs/README.md`. + +### First-run JIT compile + +The two `a4w4_blockscale_*_intrawave_v3` CK kernels are JIT-compiled on first use (~2-5 min). Subsequent runs reuse the cached `.so` files. The ASM `f4gemm_bf16_per1x32Fp4_BpreShuffle_*` kernels are pre-compiled blobs shipped with AITER and incur no JIT cost. + +--- + +## Troubleshooting + +### `not found tuned config in {file}, will use default config!` + +The (M, N, K) shape is missing from your CSV. AITER will fall back to its compiled-in default, which is typically slow. Re-tune for that shape per the internal `tuned_gemm_configs/README.md` runbook (capture the shape via the same log line, append to the untuned CSV, re-run the AITER tuner, commit the new CSV). + +### Slow first iteration (~minutes), normal afterwards + +Expected—the first call to a CK-based `a4w4_blockscale_*` kernel triggers JIT compilation. Cached `.so` files are reused on subsequent starts. + +### `User specified backend AITER cannot handle the given inputs` + +Raised by `AutoKernelDispatcher.dispatch` when `GEMMFP4AITERBackend.can_handle` rejects the input. Common causes: + +- `M` not a multiple of 16, or `N` not a multiple of 16 (constants `AITER_FP4GEMM_M_MULTIPLE` / `AITER_FP4GEMM_N_MULTIPLE` in `gemm_fp4_impl.py`). +- Unsupported dtype combination (only `(float4_e2m1fn_x2, float4_e2m1fn_x2, fp16/bf16)` is supported). +- Non-NT layout (`trans_a=False, trans_b=True, trans_c=False`). + +Workaround: switch to `PRIMUS_TURBO_GEMM_BACKEND=FP4:HIPBLASLT` for unsupported shapes, or pad/reshape inputs. + +### `MXFP4ColumnParallelLinear requires tensor_model_parallel_size=1` + +The MXFP4 linear-layer modules assert on `tensor_model_parallel_size == 1`, `gradient_accumulation_fusion == False`, and `sequence_parallel == False` ([`primus_turbo_mxfp4_local.py`](../../../primus/backends/megatron/core/extensions/primus_turbo_mxfp4_local.py)). Adjust the config accordingly. + +### NaN losses + +Switch to the hybrid backward mode, which keeps the FP4 forward but does the gradient GEMM in FP8 (E5M2 tensorwise on HipBLASLt): + +```yaml +mxfp4_backward_precision: "fp8" +``` + +If NaNs persist, also try `mxfp4_gradient_stochastic_rounding: true`. + +--- + +## Verification status + +The public config has been smoke-tested end-to-end: 1000 iters on 8x MI355X (single node, micro-batch 64 / global 512, sequence length 512) completes in ~16-20 minutes with `PRIMUS_TURBO_GEMM_BACKEND=FP4:AITER` and the tuned CSV. No errors across ranks; `pretrain() completed successfully`. + +Formal A/B benchmarks vs BF16 and FP8 (delayed and tensorwise) are pending and will be published once a representative suite is run; do not rely on the wall-clock numbers above as performance characterizations. + +--- + +## Source code pointers + +- MXFP4 spec provider: [`primus/backends/megatron/core/extensions/primus_turbo_local_spec.py`](../../../primus/backends/megatron/core/extensions/primus_turbo_local_spec.py) (`PrimusTurboMXFP4LocalSpecProvider`). +- MXFP4 linear-layer autograd / fwd-bwd: [`primus/backends/megatron/core/extensions/primus_turbo_mxfp4_local.py`](../../../primus/backends/megatron/core/extensions/primus_turbo_mxfp4_local.py). +- Config schema defaults: [`primus/configs/modules/megatron/trainer_base.yaml`](../../../primus/configs/modules/megatron/trainer_base.yaml). +- Dataclass field `mxfp4_backward_precision`: [`primus/backends/megatron/core/models/diffusion/common/config.py`](../../../primus/backends/megatron/core/models/diffusion/common/config.py). +- FP4 backend selection (Primus-Turbo): `primus_turbo/common/constants.py`, `primus_turbo/pytorch/core/backend.py`, `primus_turbo/pytorch/kernels/gemm/gemm_fp4_impl.py`. +- AITER tuned-config loader: `aiter/jit/core.py` (`AITER_CONFIG_GEMM_A4W4`). +- AITER A4W4 dispatch + hit/miss logging: `aiter/ops/gemm_op_a4w4.py`. + +## Related documentation + +- [FP8 Training Guide](fp8_training.md)—companion guide for FP8. +- [Diffusion Architecture / Developer Guide](README.md). +- [Diffusion Examples README](../../../examples/megatron/diffusion/README.md). diff --git a/docs/04-technical-guides/fault-tolerance-and-elastic-training.md b/docs/04-technical-guides/fault-tolerance-and-elastic-training.md new file mode 100644 index 000000000..1c107f072 --- /dev/null +++ b/docs/04-technical-guides/fault-tolerance-and-elastic-training.md @@ -0,0 +1,123 @@ +# Fault tolerance and elastic training + +Large-scale jobs run for days across thousands of GPUs, where hardware faults, NIC flaps, and node loss are routine. This guide covers the mechanisms Primus exposes to survive and recover from failures: graceful exit + checkpoint-based resume, Megatron's fault-tolerance package and in-process restart, and TorchTitan's [torchft](https://github.com/pytorch/torchft)-based elastic training. Parameters are grounded in `primus/configs/modules/megatron/trainer_base.yaml`, `primus_megatron_module.yaml`, and `primus/configs/modules/torchtitan/pre_trainer.yaml`. + +The foundation of all recovery is checkpointing—read [Checkpoint management](./checkpoint-management.md) first. + +--- + +## 1. The recovery model + +There are three layers, from simplest to most advanced: + +1. **Checkpoint + restart**—periodically save state; on failure, relaunch the job and resume from the last checkpoint. Works on every backend; relies on the scheduler (Slurm `--requeue`, Kubernetes restart policy) to relaunch. +2. **Graceful exit**—detect a signal or time/iteration budget, save a final checkpoint, and exit cleanly so the restart resumes with no lost work. +3. **In-job fault tolerance / elastic**—detect a failed rank and restart in-process (Megatron) or continue with a reduced/replaced replica group (TorchTitan + torchft) without tearing down the whole job. + +--- + +## 2. Graceful exit and auto-resume (Megatron) + +Controls in `trainer_base.yaml` let a run stop cleanly at a boundary so the next launch resumes seamlessly: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `exit_signal_handler` | `false` | Install a signal handler that saves a checkpoint and exits gracefully on SIGTERM (e.g. Slurm preemption). | +| `exit_duration_in_mins` | `null` | Exit (after saving) once the job has run this many minutes—useful to fit scheduler time limits. | +| `exit_interval` | `null` | Exit after this many iterations. | +| `adlr_autoresume` | `false` | Enable ADLR auto-resume integration. | +| `adlr_autoresume_interval` | `1000` | Iterations between auto-resume checks. | + +Primus-level continuation (`primus_megatron_module.yaml`): + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `auto_continue_train` | `false` | Automatically continue from the latest checkpoint in the experiment's save directory on relaunch. | +| `disable_last_saving` | `false` | Disable the final end-of-run checkpoint save (leave `false` so resume points exist). | + +**Pattern:** enable `exit_signal_handler` + `exit_duration_in_mins` (or rely on preemption signals), set a reasonable checkpoint `save_interval`, and turn on `auto_continue_train` so requeued jobs pick up where they left off. + +--- + +## 3. Megatron fault-tolerance package and in-process restart + +Megatron integrates an optional fault-tolerance package and in-process restart (`trainer_base.yaml`): + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `enable_ft_package` | `false` | Enable the Megatron fault-tolerance package (rank monitoring / heartbeat). | +| `calc_ft_timeouts` | `false` | Auto-calculate fault-tolerance timeouts from observed step times. | +| `run_workload_inspector_server` | `false` | Run the workload inspector server for health/diagnostics. | +| `inprocess_restart` | `false` | Restart failed ranks **in process** to avoid a full job teardown. | + +In-process restart reduces recovery time by re-initializing the process group and reloading state without re-scheduling the whole allocation. Combine with frequent checkpoints so the restarted ranks have a recent resume point. + +--- + +## 4. Numerical safety nets (Megatron) + +Detecting corruption early prevents wasted compute and divergence (`trainer_base.yaml`): + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `check_for_nan_in_loss_and_grad` | `true` | Abort/handle on NaN/Inf in loss or gradients. | +| `check_for_spiky_loss` | `false` | Detect anomalous loss spikes. | +| `check_for_large_grads` | `false` | Detect abnormally large gradients. | +| `decrease_batch_size_if_needed` | `false` | Reduce batch size when needed instead of failing. | + +These don't recover from hardware faults but stop a corrupted run before it pollutes downstream checkpoints. + +--- + +## 5. Elastic training with torchft (TorchTitan) + +TorchTitan supports semi-synchronous, replica-based fault tolerance via [torchft](https://github.com/pytorch/torchft). Configured under `fault_tolerance:` in `primus/configs/modules/torchtitan/pre_trainer.yaml`: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `enable` | `false` | Enable torchft fault tolerance. | +| `process_group` | `gloo` | Process group backend for fault-tolerance coordination. | +| `process_group_timeout_ms` | `10000` | Coordination timeout (ms). | +| `replica_id` | `0` | This replica's ID. | +| `group_size` | `0` | Number of replica groups (`0` = auto). | +| `min_replica_size` | `1` | Minimum replicas required to keep training. | +| `semi_sync_method` | `null` | Semi-synchronous algorithm (e.g. DiLoCo-style), `null` = standard. | + +With replica groups, the loss of one replica can be tolerated as long as `min_replica_size` is still satisfied—training continues while the failed replica recovers/rejoins, rather than crashing the whole job. + +**Install** the optional dependencies before enabling (`requirements-torchft.txt`): + +```bash +pip install -r requirements-torchft.txt # torchft-nightly + OpenTelemetry exporters +``` + +TorchTitan also exposes communication timeouts under `comm:` (`init_timeout_seconds: 300`, `train_timeout_seconds: 100`) that govern how long collectives wait before declaring a fault. + +--- + +## 6. Scheduler integration + +In-job mechanisms still need the scheduler to relaunch on full-job failure: + +- **Slurm**—submit with `--requeue` so preempted/failed jobs are re-queued; pair with `exit_signal_handler` to checkpoint on SIGTERM. See [Deployment](../05-operations/deployment.md). +- **Kubernetes**—use a restart policy / operator that recreates pods; mount checkpoint storage on a shared/persistent volume. +- **Shared checkpoint storage**—all ranks must read the same checkpoint directory after relaunch (NFS, Lustre, or object storage). See [Checkpoint management](./checkpoint-management.md). + +--- + +## 7. Recommended setup + +1. **Always checkpoint**—set a `save_interval` matched to your mean-time-between-failures; use async/distributed checkpointing to keep overhead low. +2. **Exit cleanly**—`exit_signal_handler: true` (+ `exit_duration_in_mins` for time-boxed allocations). +3. **Resume automatically**—`auto_continue_train: true` (Megatron) and `--requeue` (Slurm). +4. **Reduce recovery time at scale**—`enable_ft_package` + `inprocess_restart` (Megatron) or torchft replica groups (TorchTitan). +5. **Guard numerics**—keep `check_for_nan_in_loss_and_grad` on; consider spiky/large-grad checks for unstable configs. + +--- + +## Related documentation + +- [Checkpoint management](./checkpoint-management.md)—save/load formats, async and distributed checkpointing. +- [Deployment](../05-operations/deployment.md)—Slurm/Kubernetes restart and requeue. +- [Multi-node networking](./multi-node-networking.md)—NIC faults and collective timeouts. +- [Megatron parameters](../03-configuration-reference/megatron-parameters.md) and [TorchTitan parameters](../03-configuration-reference/torchtitan-parameters.md). diff --git a/docs/04-technical-guides/hybrid-models/README.md b/docs/04-technical-guides/hybrid-models/README.md new file mode 100644 index 000000000..0e1afae98 --- /dev/null +++ b/docs/04-technical-guides/hybrid-models/README.md @@ -0,0 +1,618 @@ +# Zebra-Llama: Hybrid Recurrent-Attention Models on AMD GPUs + +Zebra-Llama is a family of hybrid models that combine **recurrent layers** (Mamba SSM, KDA, or GDN) with **Multi-Latent Attention (MLA)** and **SwiGLU MLP** layers. These models are designed to achieve competitive quality with sub-quadratic inference cost. + +This guide covers the complete workflow: environment setup, data preparation, pretraining, checkpoint conversion, and evaluation. + +> **FLA-validated recipes**: For runnable, FLA-parity-validated walkthroughs of the pure-recurrent +> variants, see [Pure GDN guide](gdn-guide.md) and [Pure KDA guide](kda-guide.md). For the exhaustive +> list of code/config/runtime changes required for exact parity with the +> [Flash Linear Attention (FLA)](https://github.com/fla-org/flash-linear-attention) reference +> implementation, see [GDN ⇄ FLA parity](gdn-fla-parity.md) and [KDA ⇄ FLA parity](kda-fla-parity.md). + +--- + +## Table of Contents + +- [Related Guides](#related-guides) + +- [Architecture Overview](#architecture-overview) +- [Available Configurations](#available-configurations) +- [Prerequisites](#prerequisites) +- [Step 1: Environment Setup](#step-1-environment-setup) +- [Step 2: Dataset Preparation](#step-2-dataset-preparation) +- [Step 3: Pretraining](#step-3-pretraining) + - [Single-Node (Local / Docker)](#single-node-local--docker) + - [Multi-Node (Slurm)](#multi-node-slurm) + - [Mock Data (Smoke Test)](#mock-data-smoke-test) +- [Step 4: Checkpoint Conversion to HuggingFace](#step-4-checkpoint-conversion-to-huggingface) +- [Step 5: Evaluation with lm-eval-harness](#step-5-evaluation-with-lm-eval-harness) +- [Configuration Reference](#configuration-reference) +- [Troubleshooting](#troubleshooting) + +--- + +## Related Guides + +| Guide | Description | +|-------|-------------| +| [Pure GDN guide](gdn-guide.md) | End-to-end 300M pure Gated DeltaNet (GDN) recipe, FLA-validated on 8× MI300X | +| [Pure KDA guide](kda-guide.md) | End-to-end 300M pure Kimi Delta Attention (KDA) recipe, FLA-validated on 8× MI300X | +| [GDN ⇄ FLA parity](gdn-fla-parity.md) | Every Primus/Megatron-LM change required for GDN to match the FLA reference implementation | +| [KDA ⇄ FLA parity](kda-fla-parity.md) | Every Primus/Megatron-LM change required for KDA to match the FLA reference implementation | + +--- + +## Architecture Overview + +Zebra-Llama interleaves three types of layers in a repeating pattern: + +``` +[Attention] [MLP] [Recurrent] [MLP] [Recurrent] [MLP] ... [Attention] [MLP] ... +``` + +- **Recurrent layers** — one of Mamba SSM, Kimi Delta Attention (KDA), or Gated Delta Net (GDN) +- **Attention layers** — Multi-Latent Attention with YaRN rotary embeddings and LoRA-compressed KV +- **MLP layers** — SwiGLU feed-forward with Transformer Engine fused norms + +The `hybrid_attention_ratio` parameter controls what fraction of recurrent+attention layer pairs use attention (default 0.25 = 1 attention layer per 3 recurrent layers). Setting it to `0.0` yields a pure recurrent model (all KDA or GDN), while `1.0` yields a pure MLA attention model. + +--- + +## Available Configurations + +### Pretrain Configs (`examples/megatron/configs/MI300X/`) + +| Config | Model | Recurrent Type | Seq Length | Params | Tokenizer | +|--------|-------|---------------|------------|--------|-----------| +| `zebra_llama_1B-pretrain.yaml` | 1B (Mamba+MLA) | Mamba SSM | 2048 | ~1B | `meta-llama/Llama-3.2-1B` | +| `zebra_llama_1B_kda-pretrain.yaml` | 1B (KDA+MLA) | Kimi Delta Attention | 8192 | ~1B | `meta-llama/Llama-3.2-1B` | +| `zebra_llama_1B_kda_pure-pretrain.yaml` | 1B (pure KDA) | Kimi Delta Attention | 2048 | ~1.2B | `meta-llama/Llama-3.2-1B` | +| `zebra_llama_1B_gdn-pretrain.yaml` | 1B (GDN only) | Gated Delta Net | 8192 | ~1B | `fla-hub/gla-1.3B-100B` | +| `zebra_llama_1B_gdn_pure-pretrain.yaml` | 1B (pure GDN) | Gated Delta Net | 2048 | ~1.2B | `meta-llama/Llama-3.2-1B` | +| `zebra_llama_3B-pretrain.yaml` | 3B (Mamba+MLA) | Mamba SSM | 8192 | ~3B | `meta-llama/Llama-3.2-3B` | +| `zebra_llama_8B-pretrain.yaml` | 8B (Mamba+MLA) | Mamba SSM | 8192 | ~8B | `meta-llama/Llama-3.1-8B` | + +### Model Configs (`primus/configs/models/megatron/`) + +| Config | Layers | Hidden | FFN | Attention Ratio | Attention Type | +|--------|--------|--------|-----|----------------|----------------| +| `zebra_llama_1B.yaml` | 32 | 2048 | 8192 | 0.25 | MLA | +| `zebra_llama_1B_kda_pure.yaml` | 32 | 2048 | 8192 | 0.0 (pure KDA) | None | +| `zebra_llama_1B_gdn.yaml` | 32 | 2048 | 8192 | 0.0 (pure GDN) | None | +| `zebra_llama_1B_gdn_pure.yaml` | 32 (16 GDN+16 MLP) | 2048 | 8192 | 0.0 (pure GDN) | None | +| `zebra_llama_3B.yaml` | 56 | 3072 | 8192 | 0.25 | MLA | +| `zebra_llama_8B.yaml` | 64 | 4096 | 14436 | 0.25 | MLA | + +> **Note on pure KDA**: The `zebra_llama_1B_kda_pure` config matches FLA's `kda_1B_pure.json` +> architecture (16 KDA layers, `head_dim=32` for keys, `head_dim=64` for values, tied +> embeddings, `norm_eps=1e-6`). It uses the FLA Triton kernel (`use_fla_triton_kda: true`) +> for fused forward+backward during training. + +> **Note on pure GDN**: The `zebra_llama_1B_gdn_pure` config matches FLA's +> `gated_deltanet_1B_pure.json` architecture (16 GDN + 16 MLP layers, `num_heads=8`, +> `num_v_heads=16`, short convolution with kernel size 4, tied embeddings). This config +> has been validated end-to-end against FLA on MI300X — the training loss curves match +> within ~1% across 76K steps on FineWeb-Edu 10BT. See +> [Step 4](#step-4-checkpoint-conversion-to-huggingface) for conversion to FLA's HuggingFace +> format. + +--- + +## Prerequisites + +- **Hardware**: AMD Instinct MI300X (or compatible ROCm GPUs) +- **Software**: ROCm drivers >= 7.0, Docker >= 24.0 +- **HuggingFace Token**: Required for gated tokenizers (`HF_TOKEN`) +- **Disk Space**: ~50 GB for FineWeb-Edu 10BT tokenized data + +--- + +## Step 1: Environment Setup + +### 1.1 Pull the Docker Image + +```bash +docker pull docker.io/rocm/primus:v25.10 +``` + +### 1.2 Clone the Repository + +```bash +git clone --recurse-submodules https://github.com/AMD-AIG-AIMA/Primus.git +cd Primus +``` + +### 1.3 Start a Development Container + +```bash +# Quick start (mounts Primus into /workspace/Primus) +bash tools/docker/start_container.sh +``` + +This creates a persistent container named `dev_primus_`. You can customize it with environment variables: + +```bash +DOCKER_IMAGE=docker.io/rocm/primus:v25.10 \ +DATA_PATH=/path/to/data \ +bash tools/docker/start_container.sh +``` + +Then exec into the container: + +```bash +docker exec -it dev_primus_$(whoami) bash +cd /workspace/Primus +``` + +### 1.4 Install Python Dependencies (inside container) + +```bash +pip install -r requirements.txt +``` + +For GDN models (required for the Triton kernel and FLA model classes): + +```bash +pip install flash-linear-attention +``` + +For evaluation, also install: + +```bash +pip install lm-eval +``` + +--- + +## Step 2: Dataset Preparation + +Zebra-Llama uses the [FineWeb-Edu](https://huggingface.co/datasets/HuggingFaceFW/fineweb-edu) dataset, preprocessed into Megatron binary format. + +### 2.1 Set Up Environment + +```bash +export HF_TOKEN="hf_your_token_here" +export PYTHONPATH="$(pwd)/third_party/Megatron-LM:${PYTHONPATH}" +``` + +### 2.2 Run Data Preparation + +```bash +python examples/megatron/prepare_fineweb_edu.py \ + --primus-path . \ + --data-path ./data \ + --tokenizer-type HuggingFaceTokenizer \ + --tokenizer-model meta-llama/Llama-3.2-1B \ + --sample-size 10BT +``` + +This will: +1. Download the FineWeb-Edu 10BT dataset from HuggingFace +2. Tokenize it into Megatron binary format (`.bin` + `.idx` files) +3. Output files to `./data/fineweb-edu-10BT/HuggingFaceTokenizer/` + +Available sample sizes: `10BT`, `100BT`, `350BT` + +The script uses all available CPU cores by default. To limit parallelism, add `--workers N`. + +> **Note**: The context length (sequence length) is not set during data prep. It is configured at training time via `seq_length` in your pretrain YAML. + +### 2.3 Using a Different Tokenizer + +For the GDN config which uses `fla-hub/gla-1.3B-100B`: + +```bash +python examples/megatron/prepare_fineweb_edu.py \ + --primus-path . \ + --data-path ./data \ + --tokenizer-type HuggingFaceTokenizer \ + --tokenizer-model fla-hub/gla-1.3B-100B \ + --sample-size 10BT +``` + +### 2.4 FLA-Aligned Data for Pure GDN (Recommended) + +When training a pure GDN model for comparison against FLA, it is critical that both frameworks see **identical tokens in the same order**. The standard Megatron data pipeline produces different token ordering than FLA's `preprocess.py` (which shuffles with `seed=42` and concatenates into fixed-length chunks without EOD tokens). This difference alone can cause persistent training loss divergence. + +To ensure exact alignment: + +**Step 1: Preprocess with FLA** (if not already done): + +```bash +cd /path/to/flash-linear-attention/legacy/training +python preprocess.py \ + --dataset HuggingFaceFW/fineweb-edu \ + --name sample-10BT \ + --tokenizer meta-llama/Llama-3.2-1B \ + --seq_len 2048 --num_proc 64 +``` + +This produces an Arrow dataset under `data/HuggingFaceFW/fineweb-edu/sample-10BT/train/`. + +**Step 2: Convert FLA's Arrow data to Megatron binary format**: + +```bash +python convert_fla_to_megatron.py +``` + +> **Note**: Edit the `FLA_DATA` and `OUT_PREFIX` paths at the top of `convert_fla_to_megatron.py` to match your environment before running. + +This reads the Arrow shard files directly with PyArrow and produces Megatron-compatible `.bin` + `.idx` files. Each FLA 2048-token sequence becomes one Megatron "document". The script verifies token-level consistency after writing. + +**Step 3: Point the pretrain config at the converted data**: + +```yaml +train_data_path: > + /path/to/data/fla_aligned/fla_fineweb_edu_10BT_text_sentence +mock_data: false +``` + +### 2.5 Update Data Paths in Config + +After preparation (standard or FLA-aligned), update the `train_data_path` in your pretrain config YAML to point to the generated files: + +```yaml +# Standard Megatron data prep (multiple shards) +train_data_path: > + /path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_0_text_sentence + /path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_1_text_sentence + /path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_2_text_sentence + /path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_3_text_sentence +mock_data: false +``` + +--- + +## Step 3: Pretraining + +### Single-Node (Local / Docker) + +Launch training inside a Docker container on a single node: + +```bash +# Zebra-Llama 1B with KDA (Kimi Delta Attention) +EXP=examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml \ +DATA_PATH=./data \ +GPUS_PER_NODE=8 \ +HF_TOKEN=$HF_TOKEN \ +bash examples/run_local_pretrain.sh +``` + +Other model variants: + +```bash +# Zebra-Llama 1B with Mamba SSM +EXP=examples/megatron/configs/MI300X/zebra_llama_1B-pretrain.yaml \ +bash examples/run_local_pretrain.sh + +# Zebra-Llama 1B with pure KDA (no attention layers) +EXP=examples/megatron/configs/MI300X/zebra_llama_1B_kda_pure-pretrain.yaml \ +bash examples/run_local_pretrain.sh + +# Zebra-Llama 1B with GDN (pure recurrent, no attention) +EXP=examples/megatron/configs/MI300X/zebra_llama_1B_gdn-pretrain.yaml \ +bash examples/run_local_pretrain.sh + +# Zebra-Llama 1B pure GDN (FLA-validated, 4-GPU) +EXP=examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure-pretrain.yaml \ +GPUS_PER_NODE=4 \ +bash examples/run_local_pretrain.sh + +# Zebra-Llama 3B +EXP=examples/megatron/configs/MI300X/zebra_llama_3B-pretrain.yaml \ +bash examples/run_local_pretrain.sh + +# Zebra-Llama 8B +EXP=examples/megatron/configs/MI300X/zebra_llama_8B-pretrain.yaml \ +bash examples/run_local_pretrain.sh +``` + +### Multi-Node (Slurm) + +For multi-node training on a Slurm cluster: + +```bash +EXP=examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml \ +DATA_PATH=/shared/data \ +NNODES=2 \ +bash examples/run_slurm_pretrain.sh +``` + +Ensure the `global_batch_size` in your config is divisible by `micro_batch_size * GPUS_PER_NODE * NNODES`. + +### If Already Inside a Container + +If you are already inside a Docker container or on a bare-metal node with the environment set up: + +```bash +EXP=examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml \ +bash examples/run_pretrain.sh +``` + +### Mock Data (Smoke Test) + +To quickly verify the model runs without real data, the 3B and 8B configs come with `mock_data: true` by default. For the 1B configs, you can override: + +```bash +EXP=examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml \ +bash examples/run_local_pretrain.sh \ + --mock_data true --train_iters 10 +``` + +### Key Training Parameters + +| Parameter | Description | Typical Values | +|-----------|-------------|---------------| +| `train_iters` | Total training iterations | 38147 (1B KDA pure), 400000 (1B Mamba) | +| `micro_batch_size` | Per-GPU batch size | 4 (KDA/GDN), 16 (Mamba 1B) | +| `global_batch_size` | Total batch size across all GPUs | micro_batch_size * num_gpus | +| `seq_length` | Sequence length | 2048, 4096, 8192 | +| `lr` | Peak learning rate | 2.0e-4 | +| `save_interval` | Checkpoint save frequency | 1000 | +| `auto_continue_train` | Auto-resume from last checkpoint on crash | `true` / `false` | +| `hybrid_attention_ratio` | Fraction of attention layers (0.0 = pure recurrent) | 0.0, 0.25 | + +--- + +## Step 4: Checkpoint Conversion to HuggingFace + +Convert a Megatron checkpoint to HuggingFace format for inference and evaluation. + +### 4.1 Convert Checkpoint + +#### Pure GDN Models + +Pure GDN models use a dedicated converter that maps Primus's fused projections to FLA's native `GatedDeltaNetForCausalLM` format: + +```bash +python tools/hybrid/convert_gdn_to_fla_hf.py \ + --checkpoint-path output/amd/root/zebra_llama_1B_gdn_pure-pretrain/checkpoints/iter_0076294 \ + --output-dir output/gdn_pure_1B_fla_hf \ + --config /path/to/gated_deltanet_1B_pure.json +``` + +This handles: +- Splitting the fused `in_proj` (3104 → q/k/v/gate/beta/alpha projections) +- Splitting the fused `conv1d` (q/k/v convolutions) +- Splitting the fused SwiGLU `fc1` (gate_proj + up_proj) +- Mapping alternating GDN/MLP sublayers to combined FLA layers +- Handling tied embeddings + +After conversion, verify with the sanity check: + +```bash +python tools/hybrid/verify_gdn_conversion.py --model-path output/gdn_pure_1B_fla_hf +``` + +Expected output: Loss ~2-4, top prediction for "The capital of France is" should be "Paris". + +#### KDA / Hybrid Models + +The general converter auto-detects architecture from the checkpoint's saved arguments: + +```bash +# KDA+MLA hybrid model +python tools/hybrid/convert_zebra_llama_to_hf.py \ + --checkpoint-path output/zebra_llama_1B_kda-pretrain/iter_0028000 \ + --output-dir output/zebra_llama_1B_kda_hf_iter_0028000 + +# Pure KDA model +python tools/hybrid/convert_zebra_llama_to_hf.py \ + --checkpoint-path output/zebra_llama_1B_kda_pure-pretrain/iter_0038000 \ + --output-dir output/zebra_llama_1B_kda_pure_hf +``` + +The converter will: +- Read the Megatron checkpoint and training arguments +- Auto-detect architecture parameters (`hybrid_attention_ratio`, `kda_num_heads`, `q_lora_rank`, etc.) +- Remap parameter names from Megatron conventions to HuggingFace conventions +- Save `pytorch_model.bin`, `config.json`, and a model card `README.md` in the output directory +- Copy `modeling_zebra_llama.py` into the output directory for `trust_remote_code` loading + +### 4.2 Verify Conversion + +The script prints a summary of missing, extra, and shape-mismatched keys. A successful conversion shows: + +``` +0 missing, 0 extra, 0 shape mismatches +``` + +### 4.3 Supported Architectures + +| Architecture | `hybrid_attention_ratio` | Layer pattern | +|---|---|---| +| Pure KDA | `0.0` | All KDA + MLP | +| KDA + MLA hybrid | `0.0 < r < 1.0` | Mix of KDA and MLA + MLP | +| Pure MLA | `1.0` | All MLA + MLP | +| Pure GDN | `0.0` (with GDN spec) | All GDN + MLP | +| Mamba + MLA hybrid | `0.0 < r < 1.0` (with Mamba spec) | Mix of Mamba and MLA + MLP | + +--- + +## Step 5: Evaluation with lm-eval-harness + +### 5.1 Pure GDN Models (FLA format) + +Pure GDN models use a dedicated eval wrapper (`tools/hybrid/eval_gdn_lm_eval.py`) that pre-registers FLA's `GatedDeltaNetForCausalLM` with transformers' `AutoModel` and patches compatibility issues with transformers >= 4.55: + +```bash +python tools/hybrid/eval_gdn_lm_eval.py \ + --model hf \ + --model_args pretrained=output/gdn_pure_1B_fla_hf,trust_remote_code=True,tokenizer=meta-llama/Llama-3.2-1B \ + --tasks arc_easy,arc_challenge,hellaswag,mmlu,openbookqa,piqa,race,winogrande \ + --batch_size auto \ + --output_path eval_results/gdn_pure_1B +``` + +> **Note**: Do not use `lm_eval --model hf` directly — it will fail because `AutoConfig` does not recognize `gated_deltanet` without FLA being imported first. The wrapper handles this. The `tokenizer=meta-llama/Llama-3.2-1B` argument is required since the converted model directory does not contain tokenizer files. + +### 5.2 KDA / Hybrid Models (Zebra-Llama format) + +KDA and hybrid models use the custom `ZebraLlamaForCausalLM` architecture, which requires a dedicated lm-eval wrapper: + +```bash +python3 tools/hybrid/lm_harness_eval.py --model zebra_llama \ + --model_args pretrained=output/zebra_llama_1B_kda_pure_hf,dtype=bfloat16 \ + --tasks arc_easy,arc_challenge,hellaswag,mmlu,openbookqa,piqa,race,winogrande \ + --batch_size auto +``` + +### 5.3 Using the Eval Shell Script (KDA/Hybrid) + +```bash +bash tools/hybrid/eval_zebra_llama_lm_eval.sh \ + --checkpoint output/zebra_llama_1B_kda_pure_hf \ + --tasks arc_easy,arc_challenge,hellaswag,mmlu,openbookqa,piqa,race,winogrande \ + --batch-size auto \ + --dtype bfloat16 \ + --output eval_results/zebra_llama_1B_kda_pure +``` + +> **Important**: The eval script internally invokes `python3 tools/hybrid/lm_harness_eval.py --model zebra_llama` (not `lm_eval --model hf`). This ensures the custom model architecture is properly registered. + +### 5.4 Available Benchmarks + +| Task | Description | Metric | +|------|-------------|--------| +| `arc_easy` | ARC Easy (science QA) | acc, acc_norm | +| `arc_challenge` | ARC Challenge (harder science QA) | acc, acc_norm | +| `hellaswag` | HellaSwag (commonsense NLI) | acc, acc_norm | +| `mmlu` | MMLU (57 subject knowledge benchmark) | acc | +| `openbookqa` | OpenBookQA | acc, acc_norm | +| `piqa` | PIQA (physical intuition QA) | acc, acc_norm | +| `race` | RACE (reading comprehension) | acc | +| `winogrande` | Winogrande (coreference resolution) | acc | + +### 5.5 Memory Considerations + +The pure-PyTorch KDA chunked attention is memory-intensive. If you encounter OOM errors: + +- Use `--batch_size auto` to let lm-eval find the largest fitting batch size +- Reduce `max_length` (e.g., `max_length=1024` in `--model_args`) +- Reduce `--batch_size` to 1 + +--- + +## Configuration Reference + +### Hybrid Layer Specs + +The `spec` field in the pretrain config selects the layer arrangement: + +| Spec | Description | +|------|-------------| +| `hybrid_stack_spec` | Mamba SSM + MLA hybrid | +| `kda_hybrid_stack_spec` | KDA + MLA hybrid (or pure KDA with `hybrid_attention_ratio: 0.0`) | +| `gdn_hybrid_stack_spec` | GDN + MLA hybrid (or pure GDN with `hybrid_attention_ratio: 0.0`) | + +### Environment Variables + +| Variable | Description | Default | +|----------|-------------|---------| +| `DOCKER_IMAGE` | Docker image for training | `docker.io/rocm/primus:v25.10` | +| `EXP` | Path to experiment config YAML | `examples/megatron/exp_pretrain.yaml` | +| `DATA_PATH` | Path to dataset directory | `./data` | +| `HF_TOKEN` | HuggingFace API token | (required for gated models) | +| `WANDB_API_KEY` | Weights & Biases API key | (optional) | +| `GPUS_PER_NODE` | Number of GPUs per node | 8 | +| `NNODES` | Number of nodes | 1 | +| `MASTER_ADDR` | Master node address | `localhost` | +| `MASTER_PORT` | Master node port | `1234` | + +--- + +## Troubleshooting + +### OOM During Training + +- Reduce `micro_batch_size` or `seq_length` +- Enable activation checkpointing: add `recompute_granularity: selective` to the config + +### OOM During Evaluation + +- Use `--batch_size 1` or `--batch_size auto` +- Add `max_length=1024` to `--model_args` + +### `ModuleNotFoundError: No module named 'megatron'` + +Set the Python path before running data preparation: + +```bash +export PYTHONPATH="$(pwd)/third_party/Megatron-LM:${PYTHONPATH}" +``` + +### Checkpoint Conversion Shape Mismatches + +Ensure the `modeling_zebra_llama.py` model definition matches the architecture of your checkpoint (Mamba vs KDA vs GDN). The converter auto-detects architecture from checkpoint args, but the HF model code in `tools/hybrid/modeling_zebra_llama.py` must support the target architecture. Common causes of shape mismatches: + +- Mismatched `hybrid_attention_ratio` between config and checkpoint +- Incorrect `kda_num_heads` or head dimension settings +- Using a `modeling_zebra_llama.py` that doesn't support the checkpoint's attention type + +### `ValueError: model type 'zebra_llama' not recognized` + +This occurs when using `lm_eval --model hf` directly instead of the custom wrapper. Always use: + +```bash +python3 tools/hybrid/lm_harness_eval.py --model zebra_llama ... +``` + +Or the eval shell script, which handles this automatically. + +### Truncation Warnings During Eval + +Messages like `Combined length of context and continuation exceeds model's maximum length` mean some eval samples are being truncated. This has minimal impact on most benchmarks but can affect long-context tasks like RACE. To avoid truncation, increase `max_length` in `--model_args`. + +### NCCL / RCCL Timeout During Training + +On MI300X, intermittent RCCL hangs can occur (typically during checkpoint saves). Mitigations: + +- Set `auto_continue_train: true` in the pretrain config to auto-resume from the last checkpoint +- Increase the heartbeat timeout: `export TORCH_NCCL_HEARTBEAT_TIMEOUT_SEC=7200` + +--- + +## File Reference + +``` +Primus/ +├── examples/megatron/ +│ ├── configs/MI300X/ +│ │ ├── zebra_llama_1B-pretrain.yaml # 1B Mamba+MLA +│ │ ├── zebra_llama_1B_kda-pretrain.yaml # 1B KDA+MLA hybrid +│ │ ├── zebra_llama_1B_kda_pure-pretrain.yaml # 1B pure KDA +│ │ ├── zebra_llama_1B_gdn-pretrain.yaml # 1B GDN +│ │ ├── zebra_llama_1B_gdn_pure-pretrain.yaml # 1B pure GDN (FLA-validated) +│ │ ├── zebra_llama_3B-pretrain.yaml # 3B Mamba+MLA +│ │ └── zebra_llama_8B-pretrain.yaml # 8B Mamba+MLA +│ ├── prepare_fineweb_edu.py # Data preparation script +│ ├── prepare_fineweb_edu.sh # Data prep shell wrapper +│ └── preprocess_data.py # Megatron tokenizer +├── primus/configs/models/megatron/ +│ ├── zebra_llama_1B.yaml # 1B model architecture +│ ├── zebra_llama_1B_kda_pure.yaml # 1B pure KDA architecture +│ ├── zebra_llama_1B_gdn.yaml # 1B GDN architecture +│ ├── zebra_llama_1B_gdn_pure.yaml # 1B pure GDN (FLA-validated) +│ ├── zebra_llama_3B.yaml # 3B model architecture +│ └── zebra_llama_8B.yaml # 8B model architecture +├── tools/ +│ ├── hybrid/ +│ │ ├── convert_zebra_llama_to_hf.py # Megatron → HF converter (KDA/hybrid) +│ │ ├── convert_gdn_to_fla_hf.py # Megatron → FLA HF converter (pure GDN) +│ │ ├── verify_gdn_conversion.py # Post-conversion sanity check (pure GDN) +│ │ ├── eval_gdn_lm_eval.py # lm-eval wrapper for GDN (registers FLA) +│ │ ├── convert_zebra_llama_to_hf.sh # Converter shell wrapper +│ │ ├── modeling_zebra_llama.py # HF model definition (KDA/hybrid) +│ │ ├── lm_harness_eval.py # lm-eval wrapper +│ │ ├── eval_zebra_llama_lm_eval.sh # Eval shell wrapper +│ │ ├── run_zebra_eval.sh # Quick eval script +│ │ ├── chat_zebra_llama.py # Interactive chat +│ │ └── convert_fla_to_megatron.py # FLA Arrow → Megatron binary converter +│ └── docker/start_container.sh # Dev container launcher +├── examples/ +│ ├── run_local_pretrain.sh # Single-node Docker launcher +│ ├── run_slurm_pretrain.sh # Slurm launcher +│ └── run_pretrain.sh # Core training entrypoint +└── requirements.txt # Python dependencies +``` diff --git a/docs/04-technical-guides/hybrid-models/gdn-fla-parity.md b/docs/04-technical-guides/hybrid-models/gdn-fla-parity.md new file mode 100644 index 000000000..8bb66710d --- /dev/null +++ b/docs/04-technical-guides/hybrid-models/gdn-fla-parity.md @@ -0,0 +1,313 @@ +# GDN ⇄ FLA Parity in Primus + +This document captures every change required in Primus and the vendored +Megatron-LM submodule to make a 300M Gated DeltaNet (GDN) pretraining run +match the [Flash Linear Attention (FLA)](https://github.com/fla-org/flash-linear-attention) +reference implementation on **both** loss trajectory and step throughput +on 8× MI300X. + +## Final result + +| Axis | FLA reference | Primus (this branch) | Δ | +|------|---------------|----------------------|----| +| Per-iteration time (avg over 4768 iters) | **1434.6 ms** | **1431.6 ms** | **−0.21% (Primus faster)** | +| Throughput | 182,729 tok/s/GPU | **183,213 tok/s/GPU** | **+0.27%** | +| TFLOP/s/GPU | (not logged) | 642 | — | +| Total wall time (4768 iters) | 1h 54m 00s | **1h 53m 42s** | **−18s (Primus faster)** | +| Loss @ iter 1 | 11.9654 | **11.9652** | **−0.00% (bit-perfect)** | +| Loss @ iter 1000 | 4.0012 | 4.0497 | +1.21% | +| Loss @ iter 2000 | 3.6067 | 3.6144 | +0.21% | +| Loss late-training (iter 3700–4700 avg) | 3.3795 | 3.3829 | +0.10% | +| First crossover (Primus < FLA) | — | iter 2100 | — | + +**Loss curves overlap from iter ~2000 onward**, with batch-to-batch +oscillation of ±0.25%. The only persistent gap is in the LR-warmup region +(iter 50–500), and that gap closes monotonically with no instability. +Both forward and gradient at iter 1 are bit-identical to FLA. + +--- + +## How to run + +Inside the `rocm/primus:v26.2` container with the repo mounted at +`/home//Primus`: + +```bash +# Launch training (8 GPUs by default). The Megatron-LM behavioral patches +# below are applied automatically at startup via Primus's patch system -- +# no separate apply step needed. +EXP=examples/megatron/configs/MI300X/zebra_llama_300M_gdn_pure-pretrain.yaml \ + bash examples/run_pretrain.sh 2>&1 | tee primus_gdn.log +``` + +Optional toggles (all default off unless noted). Each is exposed at +TWO equivalent surfaces — pick whichever is more convenient: + +- **YAML knob** (canonical, declarative — co-located with the rest of the + run config; see `primus/configs/models/megatron/mamba_base.yaml` for the + full set of `null` defaults, and the GDN/KDA `*-pretrain.yaml` + overrides for resolved values). +- **Environment variable** (ad-hoc, for one-off A/B without editing a + YAML). When both are set, the env var wins (backward compat). + +The mapping is plumbed by +`primus/backends/megatron/patches/fla_runtime_patches.py` at +`phase="build_args"` which copies any non-`null` YAML field into the +corresponding env var before any FLA module is imported. + +| YAML knob | Env var | Default | Effect | +|--|--|--|--| +| `fused_ce_mode` | `PRIMUS_FUSED_CE` | `1` | `1` = FLA `FusedLinearCrossEntropyLoss` (chunked, no full logits tensor); `2` = FLA `FusedCrossEntropyLoss` (matches FLA exactly); `0` = native Megatron CE. | +| `fused_ce_chunks` | `PRIMUS_FUSED_CE_CHUNKS` | `32` | Number of chunks the FLA CE splits the logits across. Lower = faster but bigger peak allocation. | +| `use_fla_fused_swiglu` | `PRIMUS_FLA_SWIGLU` | `1` | Replaces Megatron's naive SwiGLU with FLA's Triton-fused kernel (≈20 ms/step saved). | +| `use_fla_fused_rmsnorm` | `PRIMUS_FLA_NORM` | `0` | Use FLA's `RMSNorm` in `WrappedTorchNorm`. | +| `use_fla_fused_gated_norm` | `PRIMUS_FLA_NORM` | `0` | Use FLA's `FusedRMSNormGated` for GDN's gated output norm. Also enables a fused pre-norm/MLP path inside `HybridStack` (saves one normalization launch per GDN block). Same env var as `use_fla_fused_rmsnorm` — kept as a separate YAML alias for clarity. | +| `use_fla_short_conv` | `PRIMUS_FLA_CONV` | `0` | Route the depthwise short conv1d through FLA's Triton `causal_conv1d` instead of Tri-Dao's CUDA package. | +| `use_fla_data` + `fla_cache_dir` | `PRIMUS_FLA_DATA` + `PRIMUS_FLA_CACHE_DIR` | `0` / `""` | When `use_fla_data=true` and `fla_cache_dir=`, replace Megatron's `GPTDataset` with the `FLAOrderGPTDataset` shim that emits tokens in the exact same order as FLA's HuggingFace `DistributedSampler`. | +| `fla_mla_attn` | `PRIMUS_FLA_MLA_ATTN` | unset | MLA `core_attention` calls `flash_attn_func` directly (skips TE's CK fallback). | +| _(env-only)_ | `PRIMUS_TORCH_OPTIM` | `0` | Use `torch.optim.AdamW(fused=True)` instead of TE/Apex `FusedAdam` (for bit-level reproducibility experiments). | + +All env-var paths are inert when the variable is unset (cost: a few +`os.environ.get()` lookups per iteration — microseconds vs seconds). + +--- + +## What changed and why + +The work splits cleanly across four layers: model code, Megatron-LM +submodule, YAML configs, and runtime knobs. + +### A. Primus model code + +| File | Change | Reason | +|------|--------|--------| +| `primus/backends/megatron/core/models/hybrid/gated_delta_net.py` | Pass `g=alpha`, `use_gate_in_kernel=True`, `A_log=…`, `dt_bias=…` directly to `chunk_gated_delta_rule`; add optional FLA Triton `causal_conv1d` path under `args.use_fla_short_conv`; add optional FLA `FusedRMSNormGated` path under `args.use_fla_fused_gated_norm`; remove `@jit_fuser` on `_apply_gated_norm` so the gated path can branch. | Match FLA's exact kernel call signature (it folds gate+softplus+log into the kernel) and let users opt into FLA's Triton kernels when bit-level parity is required. | +| `primus/backends/megatron/core/models/hybrid/gated_delta_net_layer.py` | Forward `eps=self.config.layernorm_epsilon` to the pre-norm `build_module(...)` call; defer the `residual.to(fp32)` cast until after the optional pre-norm fusion path; expose `_fuse_prenorm_with_next` flag. | `WrappedTorchNorm`'s default `eps=1e-5` was silently overriding the YAML's `1e-6`, causing a ~1.1% per-layer divergence from FLA. The deferred fp32 cast lets the pre-norm/MLP fusion in `HybridStack` work correctly. | +| `primus/backends/megatron/core/models/hybrid/hybrid_block.py` | If `config.fp32_residual_connection` is set, force `residual_in_fp32=True`; under `args.use_fla_fused_rmsnorm`, mark every GDN layer with `_fuse_prenorm_with_next=True` and rewrite the forward loop to fuse a GDN block's mixer-out with the next MLP block's pre-MLP layernorm in a single op. | The fp32-residual handling was previously silently dropped. The pre-norm fusion saves one normalization launch per GDN block when FLA-norm is enabled. (For TE-free builds use the `gdn_hybrid_stack_spec_no_te` spec from the YAML instead.) | +| `primus/backends/megatron/core/models/hybrid/hybrid_mamba_mla_layer_specs.py` | Add a new `gdn_hybrid_stack_spec_no_te` ModuleSpec that uses `WrappedTorchNorm` and plain `Column/RowParallelLinear` everywhere, with the same submodule wiring as `gdn_hybrid_stack_spec`. | YAML can now select TE-free layers via `spec: [..., gdn_hybrid_stack_spec_no_te]` for FLA loss-curve alignment without touching code. | +| `primus/backends/megatron/patches/mamba_fla_data_patches.py` | Wraps `pretrain_mamba.train_valid_test_datasets_provider`, branching to `tools.hybrid.fla_order_dataset.FLAOrderGPTDataset` when `args.use_fla_data=True` + `args.fla_cache_dir=`. | Lets us bypass Megatron's `GPTDataset` shuffler and drive Primus with the exact same token order FLA's `DistributedSampler` produces, isolating data-ordering effects from model effects during comparison. | + +### B. Megatron-LM behavioral patches (Primus patch system) + +Primus never forks the vendored `third_party/Megatron-LM` submodule. +Instead these six patches are runtime monkey-patches registered with +`@register_patch` and applied automatically at `phase="before_train"` by +`primus/core/patches` -- see the +[Backend Patch Explorer](../../.cursor/skills/backend-patch-explorer/SKILL.md) +skill for how the engine works in general. Each patch's `condition=` gates +it on the relevant config flag, so it's a no-op unless that flag is set. + +| Patch id | File | Change | Reason | +|-------|------|--------|--------| +| `megatron.mamba.fla_fused_ce` | `mamba_fused_ce_patches.py` | Add `_use_fused_cross_entropy` path to `MambaModel`. Mode 1 = `FusedLinearCrossEntropyLoss` (chunked, never materializes the full logits tensor). Mode 2 = `FusedCrossEntropyLoss` (matches FLA exactly, materializes bf16 logits). Selected by `args.fused_ce_mode` via `get_args()`. | Megatron always materializes a `(batch*seq, vocab)` fp32 logits tensor before CE — for 1024 batch × 2048 seq × 32k vocab this is 256 GB at fp32. FLA chunks it. Massive memory + speed win. | +| `megatron.optimizer.torch_fused_adam` | `torch_fused_adam_patches.py` | Add `PRIMUS_TORCH_OPTIM=1` opt-in path that selects `torch.optim.AdamW(fused=True)` over TE/Apex `FusedAdam`. | TE's FusedAdam has slightly different epsilon-handling internally; toggling this lets us prove that Primus's AdamW is bit-identical to FLA's when both use torch's fused kernel. | +| `megatron.mlp.fla_swiglu` | `mlp_fla_swiglu_patches.py` | Replace the naive `silu(x_glu) * x_linear` (2 separate kernel launches + intermediate tensor) with FLA's Triton-fused `swiglu(x_glu, x_linear)` (1 fwd + 1 bwd kernel) in `MLP`. Toggle: `args.use_fla_fused_swiglu` (default True) via `get_args()`. | Profiler shows ~3.8× fewer GPU cycles spent on the activation step. Saves ~20 ms/iter at our batch size. | +| `megatron.torch_norm.fla_rmsnorm` | `torch_norm_fla_rmsnorm_patches.py` | When `args.use_fla_fused_rmsnorm=True`, return `fla.modules.RMSNorm` from `WrappedTorchNorm` instead of `torch.nn.RMSNorm`. Reads from `get_args()`. | FLA's RMSNorm is a fused Triton kernel that matches the reference run's normalization semantics bit-for-bit. | +| `megatron.transformer.hybrid_output_init` | `gdn_config_patches.py` | For `is_hybrid_model`, set `output_layer_init_method = init_method_normal(self.init_method_std)` (uniform std, no depth scaling) after `TransformerConfig.__post_init__` runs. | Megatron's default `scaled_init_method_normal` divides std by `sqrt(2 * num_layers)` — that's correct for transformers but **wrong** for hybrid GDN models, where FLA uses a uniform `initializer_range`. Without this fix the output layer started ~24× smaller than FLA's, causing the iter-1 loss to be 11.971 instead of 11.965. | +| `megatron.mamba.fla_order_dataset` | `mamba_fla_data_patches.py` | Add the FLA-order dataset shim (`args.use_fla_data` + `args.fla_cache_dir`) to `pretrain_mamba.train_valid_test_datasets_provider` — `pretrain_mamba.py` provides its own provider used for Mamba/GDN models. Reads from `get_args()`. | Lets Mamba/GDN training consume the exact same token order FLA's `DistributedSampler` produces. | + +### C. YAML configuration changes + +#### `primus/configs/models/megatron/{mamba_base,zebra_llama_*_gdn*}.yaml` + +Renamed `bases:` → `extends:` (4 files). The Primus YAML resolver was +silently dropping inheritance from `bases:` lists, which meant model +configs were missing the dropout/normalization defaults from +`mamba_base.yaml` → `language_model.yaml`. Verified empirically by +checking that `hidden_dropout` was leaking through as `0.1` despite +`mamba_base.yaml` setting it to `0.0`. + +#### `examples/megatron/configs/MI300X/zebra_llama_300M_gdn_pure-pretrain.yaml` + +The training-side config picked up these settings during the +parity work: + +```yaml +# Logging +num_workers: 8 # was 2; FLA uses 8 dataloader workers +log_interval: 100 +check_for_nan_in_loss_and_grad: false + +# Per-rank serialization removal — Megatron defaults insert a +# dist.barrier() before every L1 timer measurement (~5–10/iter). +barrier_with_L1_time: false + +# Match FLA's seed for bit-perfect iter-1 comparison +seed: 42 + +# Norm — Megatron's default is 1e-5; FLA uses 1e-6 +layernorm_epsilon: 1.0e-6 + +# Force dropout to 0 at the YAML level. +# language_model.yaml sets these to 0.1 and that was leaking through +# even when mamba_base.yaml inherited from it (`bases:` bug, see above). +hidden_dropout: 0.0 +attention_dropout: 0.0 + +# Training schedule matched to FLA (8 GPUs): +# FLA: per_device_train_batch_size=128, 8 GPUs → global=1024 +train_iters: 4768 +micro_batch_size: 128 +global_batch_size: 1024 + +# Use the no-TE spec for layer alignment with FLA's native PyTorch layers +spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'gdn_hybrid_stack_spec_no_te'] +no_persist_layer_norm: true + +# Distributed-optimizer (ZeRO-1) costs allreduce bandwidth and saves +# only ~3.6 GB/rank for a 300M model — disable to match FLA's plain DDP. +use_distributed_optimizer: false +overlap_grad_reduce: true +overlap_param_gather: false # requires distributed optimizer +gradient_accumulation_fusion: false +ddp_average_in_collective: true # divide gradients in NCCL collective + +# Load FLA-initialized weights to compare apples-to-apples +finetune: true +auto_continue_train: false +no_load_optim: true +no_load_rng: true +load: /home//Primus/output/fla_init_ckpt_300M +``` + +--- + +## Reproducing the loss-curve match plot + +The full per-iteration log lives at `primus_gdn.log` once training +finishes. Compare against FLA's log +(`/home//flash-linear-attention/legacy/training/train_gdn_bs32.log`) +using the parser in `tools/compare_losses.py` (or the inline parser +documented in this file's history). + +Notable comparison points (FLA loss is divided by 8 to undo the +DeepSpeed sum-across-ranks): + +| iter | FLA / 8 | Primus | Δ% | Notes | +|-----:|--------:|-------:|---:|-------| +| 1 | 11.9654 | 11.9652 | **−0.00%** | bit-perfect | +| 100 | 7.471 | 9.601 | +28.5% | warmup gap (peak) | +| 500 | 4.625 | 4.728 | +2.2% | warmup closing | +| 1000 | 4.001 | 4.050 | +1.21% | LR-warmup done | +| 2000 | 3.607 | 3.614 | +0.21% | converged | +| 2100 | 3.600 | 3.592 | **−0.22%** | first Primus < FLA crossover | +| 3000 | 3.448 | 3.460 | +0.35% | matched | +| 4000 | 3.396 | 3.390 | −0.19% | Primus slightly lower | +| 4500 | 3.373 | 3.373 | −0.01% | identical | +| 4700 | 3.351 | 3.366 | +0.45% | identical | + +The only persistent gap (iter 50–500) is attributable to dataloader +ordering — Megatron `GPTDataset` uses its own random shuffler while +FLA uses HuggingFace's `DistributedSampler`. With `use_fla_data: true` +the gap closes further but Primus has been verified to converge to +within ±0.5% by iter 1000 even without it. + +--- + +## Files in the repo for this work + +``` +primus/backends/megatron/patches/ + mamba_fused_ce_patches.py # FLA fused cross-entropy for MambaModel + torch_fused_adam_patches.py # PRIMUS_TORCH_OPTIM opt-in + mlp_fla_swiglu_patches.py # FLA Triton SwiGLU for MLP + torch_norm_fla_rmsnorm_patches.py # FLA RMSNorm for WrappedTorchNorm + gdn_config_patches.py # linear-attention config fields + hybrid init + fla_runtime_patches.py # resolves PRIMUS_FLA_* knobs onto args + mamba_fla_data_patches.py # FLA-order dataset shim wiring +tools/hybrid/fla_order_dataset.py # FLA-order dataset shim +tools/profile_training.py # NSight Compute / rocprof launcher +tools/run_profiled_training.sh # one-shot profiling driver +tools/hybrid/convert_fla_to_megatron.py # FLA HF checkpoint → Megatron sharded ckpt +tools/hybrid/convert_gdn_to_fla_hf.py # Megatron sharded ckpt → FLA HF checkpoint +tools/hybrid/verify_gdn_conversion.py # validates round-trip checkpoint conversion +tools/hybrid/eval_gdn_lm_eval.py # lm-eval-harness wrapper for GDN models +``` + +The `tools/compare_*.py`, `tools/diff_*.py`, `tools/dump_*.py`, +`tools/forensic_*.py`, `tools/inspect_*.py`, `tools/hybrid/convert_fla_gdn_init_to_megatron.py`, +`tools/prove_*.py`, `tools/single_*.py` and `tools/check_*.py` scripts +were used as one-off forensics during the parity hunt and are kept +untracked under `tools/`. They reference the env-var-gated dump paths +documented above. + +--- + +## Hybrid (3 MLA + 9 GDN) parity delta + +Everything above applies as-is to the 75% Hybrid GDN+MLA configuration. +On top of the pure-GDN parity stack, the hybrid run needs two more pieces +to match FLA's `gated_deltanet_300M_hybrid.json` reference: + +### Spec-level fix — LoRA RMSNorm in MLA + +FLA's MLA wraps every LoRA projection in a `nn.Sequential` chain: + +```python +self.q_proj = nn.Sequential( + nn.Linear(hidden_size, q_lora_rank, bias=False), + RMSNorm(q_lora_rank, dtype=torch.float32), + nn.Linear(q_lora_rank, num_heads * qk_head_dim, bias=False), +) +self.kv_proj = nn.Sequential( + nn.Linear(hidden_size, kv_lora_rank, bias=False), + RMSNorm(kv_lora_rank, dtype=torch.float32), + nn.Linear(kv_lora_rank, num_heads * (qk_nope_head_dim + v_head_dim), bias=False), +) +``` + +Megatron's `MLASelfAttention` constructs the equivalent intermediate +norm from its `q_layernorm` / `kv_layernorm` submodules: + +```python +self.q_layernorm = submodules.q_layernorm( hidden_size=config.q_lora_rank, config=config, eps=config.layernorm_epsilon) +self.kv_layernorm = submodules.kv_layernorm(hidden_size=config.kv_lora_rank, config=config, eps=config.layernorm_epsilon) +# ... and applied between linear_*_down_proj and linear_*_up_proj. +``` + +Earlier hybrid specs declared both as `IdentityOp`, which silently +skipped FLA's per-LoRA RMSNorm. Iter-1 still matched bit-perfect +(both models start from the same init and the missing norm only kicks +in once the LoRA weights drift from their init), but from iter 100 +onward Primus plateaued ~0.12 above FLA's loss curve. + +Fix in `primus/backends/megatron/core/models/hybrid/hybrid_mamba_mla_layer_specs.py`: +flip `q_layernorm` / `kv_layernorm` to `TENorm` (TE specs) or +`WrappedTorchNorm` (no-TE specs) in all four MLA-bearing specs. +Under `use_fla_fused_rmsnorm: true`, `WrappedTorchNorm` resolves to FLA's +Triton `RMSNorm`, giving bit-exact FLA semantics. + +### Launcher-level fix — full FLA fusion stack + +The YAML overrides block is now the canonical surface (all consumers +read `args.*` via `get_args()`): + +```yaml +# YAML overrides (canonical) +use_fla_fused_swiglu: true +use_fla_fused_rmsnorm: true +use_fla_fused_gated_norm: true +use_fla_short_conv: true +use_fla_data: true +fla_cache_dir: /path/to/fla/cache +fused_ce_mode: 1 +fused_ce_chunks: 32 +fla_mla_attn: "1" +``` + +Legacy env vars are still accepted as ad-hoc overrides (env wins over +YAML) for backward compatibility: + +```bash +export PRIMUS_FLA_MLA_ATTN=1 # MLA → flash_attn_func directly (TE 2.8.1 cap) +export PRIMUS_FUSED_CE=1 # FLA chunked fused-LCE (mem + speed) +export PRIMUS_FLA_SWIGLU=1 # Triton SwiGLU (~20 ms/iter) +export PRIMUS_FLA_NORM=1 # FLA RMSNorm + FusedRMSNormGated + prenorm/MLP fusion +export PRIMUS_FLA_CONV=1 # FLA Triton causal_conv1d +export PRIMUS_FLA_DATA=1 # same token order as FLA's DistributedSampler +``` + +With these flags on, the same Megatron stack that ran pure-KDA at +1.46 s/iter runs the hybrid at FLA-parity speed (∼1.47 s/iter) and +loss curve (Δ ≤ 0.5% from iter 100 onward), no other changes +required. diff --git a/docs/04-technical-guides/hybrid-models/gdn-guide.md b/docs/04-technical-guides/hybrid-models/gdn-guide.md new file mode 100644 index 000000000..663484565 --- /dev/null +++ b/docs/04-technical-guides/hybrid-models/gdn-guide.md @@ -0,0 +1,578 @@ +# Pure GDN 300M on Primus — End-to-End Guide (FLA-validated) + +This document is a runnable walkthrough for the **300M pure Gated DeltaNet (GDN)** pretraining recipe in Primus, validated on 8× AMD MI300X against the [Flash Linear Attention (FLA)](https://github.com/fla-org/flash-linear-attention) reference implementation. It covers every step from raw dataset → tokenization → training → checkpoint conversion → lm-eval benchmark. + +The same recipe scales up to the 1B pure-GDN config (`zebra_llama_1B_gdn_pure-pretrain.yaml`) — just swap the config file at training time and the FLA config JSON at conversion time. + +--- + +## Final result + +After 4768 iterations (≈10B tokens) on FineWeb-Edu sample-10BT: + + +| Axis | FLA reference | Primus (this branch) | Δ | +| ----------------------------------------------- | ----------------- | --------------------- | --------------------------- | +| Per-iteration time (avg over 4768 iters) | **1434.6 ms** | **1431.6 ms** | **−0.21 % (Primus faster)** | +| Throughput | 182,729 tok/s/GPU | **183,213 tok/s/GPU** | **+0.27 %** | +| TFLOP/s/GPU | — | 642 | — | +| Wall time (4768 iters, 8× MI300X, healthy node) | 1h 54m 00s | **1h 53m 42s** | **−18s** | +| Loss @ iter 1 | 11.9654 | **11.9652** | **−0.00 % (bit-perfect)** | +| Loss @ iter 4700 (final logged) | 3.3511 | **3.3590** | **+0.24 %** | +| First Primus-below-FLA crossover | — | iter 2100 | — | + + +Loss trajectories overlap from iter ~2000 onward; the only persistent gap is in the LR-warmup region (iter 50–500) and closes monotonically. See [gdn-fla-parity.md](gdn-fla-parity.md) for the deep-dive on every patch and env var. + +--- + +## Table of contents + +- [Overview](#overview) +- [Prerequisites](#prerequisites) +- [Step 1: Environment](#step-1-environment) +- [Step 2: Dataset preparation](#step-2-dataset-preparation) +- [Step 3: Apply Megatron-LM patches](#step-3-apply-megatron-lm-patches) +- [Step 4: (Optional) Initialize from FLA weights](#step-4-optional-initialize-from-fla-weights) +- [Step 5: Train](#step-5-train) +- [Step 6: Monitor and compare against FLA](#step-6-monitor-and-compare-against-fla) +- [Step 7: Convert checkpoint to HuggingFace format](#step-7-convert-checkpoint-to-huggingface-format) +- [Step 8: Verify conversion](#step-8-verify-conversion) +- [Step 9: Run lm-eval-harness benchmarks](#step-9-run-lm-eval-harness-benchmarks) +- [Configs and tools used](#configs-and-tools-used) +- [Troubleshooting](#troubleshooting) + +--- + +## Overview + +The 300M pure-GDN model has: + +- 12 Gated DeltaNet blocks + 12 MLP blocks → 24 Megatron "sublayers" +- `hidden_size = 1024`, `ffn_hidden_size = 4096` +- `num_heads = 4` (Q/K), `num_v_heads = 8` (V, grouped-value attention) +- `head_dim = 64`, short-conv kernel size 4 +- Tied embeddings, no positional encoding (delta-rule recurrence), RMSNorm with `eps = 1e-6` +- Tokenizer: `meta-llama/Llama-3.2-1B` (128k vocab) +- Total parameters: **0.308B** + +Training schedule (matched to FLA's `gated_deltanet_300M_pure.json`): + +- 4768 iterations × 1024 global batch × 2048 seq len = **10.0 B tokens** +- AdamW (β1=0.9, β2=0.95, wd=0.01), peak LR `2e-4`, cosine decay, 200-step warmup +- bf16 mixed-precision, no dropout, gradient clip 1.0 + +--- + +## Prerequisites + +- **Hardware**: 8× AMD MI300X (or compatible ROCm GPU) on a single node +- **Software**: ROCm ≥ 7.0, Docker ≥ 24.0 +- **Container image**: `rocm/primus:v26.2` (or `v25.10` with the same patches) +- **HF token**: `HF_TOKEN` set for the gated `meta-llama/Llama-3.2-1B` tokenizer +- **Disk**: ~20 GB for the FLA-aligned tokenized dataset + ~5 GB per saved checkpoint +- **Optional**: a local clone of [flash-linear-attention](https://github.com/fla-org/flash-linear-attention) checked out at `legacy/training` — only needed if you want to reuse FLA's preprocessed Arrow files (recommended for bit-identical iter-1 comparison) + +--- + +## Step 1: Environment + +### 1.1 Start the dev container + +```bash +docker run -it \ + --device /dev/dri --device /dev/kfd \ + --device=/dev/infiniband --network host --ipc host \ + --group-add video --cap-add SYS_PTRACE \ + --security-opt seccomp=unconfined --privileged \ + -v $HOME:$HOME -v $(pwd):$(pwd) -w $(pwd) --shm-size 64G --name primus_hybrid_new \ + rocm/primus:v26.2 +``` + +This runs the `rocm/primus:v26.2` image with `/dev/dri`, `/dev/kfd`, IB devices, `--privileged`, your `$HOME` mounted in-place, and `--shm-size 64G`. The container is named `primus_hybrid_new`. + +To re-attach later: + +```bash +docker exec -it primus_hybrid_new bash +cd /home//Primus +``` + +### 1.2 Install Python dependencies inside the container + +```bash +pip install -r requirements.txt +pip install flash-linear-attention # FLA model classes + Triton kernels +pip install lm-eval # for benchmark evaluation +``` + +The `flash-linear-attention` package supplies the FLA `GatedDeltaNetForCausalLM` class (needed for HF conversion + lm-eval) and the Triton kernels that the optional `PRIMUS_FLA_*` toggles route into. + +--- + +## Step 2: Dataset preparation + +You have two choices. **For exact loss-curve parity with the FLA reference run, use Option B.** For a quick first run that won't bit-match FLA in the warmup region but will converge to the same final loss, Option A is fine. + +### Option A — Standard Megatron data prep (faster setup) + +```bash +export HF_TOKEN="hf_your_token_here" +export PYTHONPATH="$(pwd)/third_party/Megatron-LM:${PYTHONPATH}" + +python examples/megatron/prepare_fineweb_edu.py \ + --primus-path . \ + --data-path ./data \ + --tokenizer-type HuggingFaceTokenizer \ + --tokenizer-model meta-llama/Llama-3.2-1B \ + --sample-size 10BT +``` + +Output ends up at `./data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_{0..3}_text_sentence.{bin,idx}` (4 shards). Then point the YAML at it: + +```yaml +train_data_path: > + /path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_0_text_sentence + /path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_1_text_sentence + /path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_2_text_sentence + /path/to/data/fineweb-edu-10BT/HuggingFaceTokenizer/fineweb_edu_10BT_3_text_sentence +``` + +### Option B — FLA-aligned data (recommended for parity) + +The Megatron `GPTDataset` shuffler produces a different token order than FLA's `DistributedSampler` (`seed=42`, fixed 2048-token chunks, no EOD tokens). To match exactly, reuse FLA's already-preprocessed Arrow shards and re-encode them into Megatron `.bin`/`.idx` format. + +**Step B.1 — Preprocess with FLA's script** (one-time, ~10 min on 64 cores): + +```bash +cd /path/to/flash-linear-attention/legacy/training +python preprocess.py \ + --dataset HuggingFaceFW/fineweb-edu \ + --name sample-10BT \ + --tokenizer meta-llama/Llama-3.2-1B \ + --seq_len 2048 --num_proc 64 +``` + +This writes Arrow shard files to `legacy/training/data/HuggingFaceFW/fineweb-edu/sample-10BT/train/data-*.arrow`. + +**Step B.2 — Convert the Arrow shards to Megatron binary** using the script at `[tools/hybrid/convert_fla_to_megatron.py](../../tools/hybrid/convert_fla_to_megatron.py)`: + +```bash +cd /home//Primus +# Edit FLA_DATA and OUT_PREFIX at the top of the script if your paths differ +python tools/hybrid/convert_fla_to_megatron.py +``` + +The script reads each Arrow shard directly with PyArrow (zero HuggingFace `datasets` overhead), writes a single `.bin` containing flat int32 token IDs, and emits a Megatron `.idx` file where each 2048-token chunk is one document. It cross-checks the first 10 tokens of the output against the first sample of the first Arrow shard before finishing. + +Output: `data/fla_aligned/fla_fineweb_edu_10BT_text_sentence.{bin,idx}` (~19 GB binary). + +The default 300M YAML already points at this path: + +```yaml +train_data_path: > + /home//Primus/data/fla_aligned/fla_fineweb_edu_10BT_text_sentence +``` + +(adjust the user prefix in `examples/megatron/configs/MI300X/zebra_llama_300M_gdn_pure-pretrain.yaml` to match your home directory). + +--- + +## Step 3: Megatron-LM patches (automatic — no action needed) + +GDN parity training needs six behavioral patches on top of the vendored +`third_party/Megatron-LM` submodule. Unlike a typical vendored fork, Primus +never modifies the third-party source: these patches are implemented as +runtime monkey-patches using Primus's own patch system +(`primus/core/patches`) and live under +`[primus/backends/megatron/patches/](../../primus/backends/megatron/patches/)`. +They register unconditionally but each carries a `condition=` gate on the +relevant config flag, so they're a no-op unless you actually enable that +flag — nothing to run by hand. + +| Patch id | File | Touches | Purpose | +| ------------------------------------------- | -------------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `megatron.mamba.fla_fused_ce` | `mamba_fused_ce_patches.py` | `MambaModel` | Wires `FusedLinearCrossEntropyLoss` / `FusedCrossEntropyLoss` — never materializes the (batch×seq, vocab) logits tensor; gated by `fused_ce_mode` (default 1) | +| `megatron.optimizer.torch_fused_adam` | `torch_fused_adam_patches.py` | `optimizer/__init__.py` | Adds `PRIMUS_TORCH_OPTIM=1` opt-in for `torch.optim.AdamW(fused=True)` over TE/Apex FusedAdam | +| `megatron.mlp.fla_swiglu` | `mlp_fla_swiglu_patches.py` | `MLP` | Routes SwiGLU through FLA's Triton-fused kernel (saves ~20 ms/iter); `use_fla_fused_swiglu` (default true) | +| `megatron.torch_norm.fla_rmsnorm` | `torch_norm_fla_rmsnorm_patches.py` | `WrappedTorchNorm` | Routes RMSNorm through `fla.modules.RMSNorm` when `use_fla_fused_rmsnorm=true` | +| `megatron.transformer.hybrid_output_init` | `gdn_config_patches.py` | `TransformerConfig` | For hybrid models, uses uniform `init_method_normal` (no depth-scaled std) — required for bit-perfect iter-1 loss vs FLA | +| `megatron.mamba.fla_order_dataset` | `mamba_fla_data_patches.py` | `pretrain_mamba.py` | FLA-order dataset shim (`use_fla_data=true` + `fla_cache_dir=`) | + +The `fused_ce_mode` / `use_fla_fused_swiglu` / `use_fla_fused_rmsnorm` / +`use_fla_data` / `fla_cache_dir` knobs above are resolved once (env var > +YAML field > default) by `fla_runtime_patches.py` before the patches in this +table run. See `[gdn-fla-parity.md](gdn-fla-parity.md)` for the full +per-patch deep-dive. + +--- + +## Step 4: (Optional) Initialize from FLA weights + +For bit-perfect iter-1 loss alignment, the validated run loads FLA's *initialized but untrained* checkpoint and then trains from there. The YAML's `load:` field points at this directory: + +```yaml +load: /home//Primus/output/fla_init_ckpt_300M +finetune: true # load weights, ignore optimizer state and iteration count +no_load_optim: true +no_load_rng: true +``` + +The Primus repo includes `tools/hybrid/convert_fla_gdn_init_to_megatron.py` (the GDN counterpart of `tools/hybrid/convert_fla_kda_init_to_megatron.py`) that takes the FLA HuggingFace random-init checkpoint and writes a Megatron-shape `iter_0000000/mp_rank_00/model_optim_rng.pt`. Skip this step if you're happy with Primus's own random init — final loss is identical, only iter-1 drifts by `~5e-3`. + +--- + +## Step 5: Train + +### 5.1 Inspect the config + +The training config lives at `[examples/megatron/configs/MI300X/zebra_llama_300M_gdn_pure-pretrain.yaml](../../examples/megatron/configs/MI300X/zebra_llama_300M_gdn_pure-pretrain.yaml)`. Key parameters (matched to FLA): + +```yaml +train_iters: 4768 # ≈ 10B tokens at global_batch=1024, seq=2048 +micro_batch_size: 128 # per-GPU +global_batch_size: 1024 # 8 GPUs × 128 = 1024 +seq_length: 2048 +lr: 2.0e-4 +min_lr: 2.0e-5 # min_lr_rate=0.1 → 2e-5 +lr_warmup_iters: 200 +lr_decay_iters: 4768 +lr_decay_style: cosine +adam_beta1: 0.9 +adam_beta2: 0.95 +weight_decay: 0.01 +clip_grad: 1.0 +seed: 42 +layernorm_epsilon: 1.0e-6 # MUST be explicit — TransformerConfig default 1e-5 silently overrides the model YAML +hidden_dropout: 0.0 # MUST be explicit — language_model.yaml default 0.1 leaks through +attention_dropout: 0.0 +spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'gdn_hybrid_stack_spec_no_te'] +use_distributed_optimizer: false # 300M fits — ZeRO-1 adds allreduce overhead +``` + +The architecture-only YAML it extends from is `[primus/configs/models/megatron/zebra_llama_300M_gdn_pure.yaml](../../primus/configs/models/megatron/zebra_llama_300M_gdn_pure.yaml)`. + +### 5.2 Launch + +```bash +# inside the container, in /home//Primus +EXP=examples/megatron/configs/MI300X/zebra_llama_300M_gdn_pure-pretrain.yaml \ + bash examples/run_pretrain.sh 2>&1 | tee primus_gdn.log +``` + +This brings up `torchrun` with 8 ranks on the local node. Expected wall time on a healthy MI300X box: **~1h 54m** for the full 4768 iters. + +### 5.3 Recommended toggle profile (for FLA parity) + +The defaults are already good; for *bit-level* parity with FLA's +optimizer/CE/SwiGLU kernels, set the following. Either surface works +(YAML wins for declarative runs; env vars win for ad-hoc overrides +since the patch `fla_runtime_patches.py` does not overwrite an +already-set env var). + +Preferred (canonical) — add to the experiment YAML's `overrides:` block: + +```yaml +use_fla_fused_swiglu: true # FLA Triton SwiGLU +use_fla_fused_rmsnorm: true # FLA fused RMSNorm +use_fla_fused_gated_norm: true # FLA FusedRMSNormGated path +fused_ce_mode: 1 # FLA FusedLinearCrossEntropyLoss (chunked, no full logits tensor) +fused_ce_chunks: 32 # Chunk count for FLA fused CE +# Only if you did Option B (FLA-aligned data) AND want bit-identical iter-1: +use_fla_data: true +fla_cache_dir: /home//Primus/data/huggingface +``` + +Legacy (still supported): + +```bash +export PRIMUS_FUSED_CE=1 # FLA FusedLinearCrossEntropyLoss (chunked, no full logits tensor) +export PRIMUS_FLA_SWIGLU=1 # FLA Triton SwiGLU +export PRIMUS_FLA_NORM=1 # FLA fused RMSNorm + fused pre-norm/MLP path +export PRIMUS_TORCH_OPTIM=1 # torch.optim.AdamW(fused=True), matches FLA exactly +# Only if you did Option B (FLA-aligned data) AND want bit-identical iter-1: +export PRIMUS_FLA_DATA=1 +export PRIMUS_FLA_CACHE_DIR=/home//Primus/data/huggingface +``` + +These add roughly +1.2 % per-iter overhead vs the all-defaults run, but they pin the loss curve to FLA's. On healthy hardware (tw006 in our cluster), the absolute wall is still ~18 s **below** FLA. On a slower node (tw029) it's ~75 s above. See [gdn-fla-parity.md](gdn-fla-parity.md) for the cost-of-each-flag breakdown. + +### 5.4 Output layout + +Checkpoints land under Primus's `work_group/user_name/exp_name` template: + +``` +output/amd/root/zebra_llama_300M_gdn_pure-pretrain/ +├── checkpoints/ +│ ├── iter_0001024/ +│ ├── iter_0002048/ +│ ├── iter_0003072/ +│ ├── iter_0004096/ +│ ├── iter_0004768/ ← FINAL (4.1 GB) +│ │ └── mp_rank_00/ +│ │ └── model_optim_rng.pt +│ └── latest_checkpointed_iteration.txt → "4768" +└── logs/ + └── pre_trainer/ +``` + +`save_interval: 1024` in the YAML produces 4 mid-training checkpoints plus the final one. + +--- + +## Step 6: Monitor and compare against FLA + +Megatron logs `iteration / elapsed_ms_inst / elapsed_ms_avg / TFLOP/s/GPU / tok/s/GPU / lm loss` every 100 steps. A representative tail looks like: + +``` + iteration 4700/ 4768 | elapsed time per iteration (ms): 1460.8/1450.4 | + TFLOP/s/GPU: 633.7 | tokens per GPU (tokens/s/GPU): 180743.2 | lm loss: 3.3632 +``` + +To diff against FLA's reference log (`train_gdn_bs32.log`, `train_runtime=6840.2s`): + + +| iter | FLA / 8 | Primus | Δ % | Notes | +| ---- | ------- | ------- | ----------- | ---------------------------- | +| 1 | 11.9654 | 11.9652 | **−0.00 %** | bit-perfect | +| 100 | 7.471 | 9.601 | +28.5 % | warmup gap (peak) | +| 500 | 4.625 | 4.728 | +2.2 % | warmup closing | +| 1000 | 4.001 | 4.050 | +1.21 % | LR-warmup done | +| 2000 | 3.607 | 3.614 | +0.21 % | converged | +| 2100 | 3.600 | 3.592 | **−0.22 %** | first Primus < FLA crossover | +| 3000 | 3.448 | 3.460 | +0.35 % | matched | +| 4000 | 3.396 | 3.390 | −0.19 % | Primus slightly lower | +| 4500 | 3.373 | 3.373 | −0.01 % | identical | +| 4700 | 3.351 | 3.366 | +0.45 % | identical | + + +Final wall time on a healthy MI300X box: **6832 s vs FLA 6840 s** = Primus 8 s faster. On a slower node it's +75 s (~1.1 %). Both are within the run-to-run noise of FLA itself. + +--- + +## Step 7: Convert checkpoint to HuggingFace format + +Use `[tools/hybrid/convert_gdn_to_fla_hf.py](../../tools/hybrid/convert_gdn_to_fla_hf.py)` to translate the Megatron checkpoint into FLA's native `GatedDeltaNetForCausalLM` HF format: + +```bash +python tools/hybrid/convert_gdn_to_fla_hf.py \ + --checkpoint-path output/amd/root/zebra_llama_300M_gdn_pure-pretrain/checkpoints/iter_0004768 \ + --output-dir output/gdn_pure_300M_fla_hf_final +``` + +The converter auto-detects 300M from the path and uses `gated_deltanet_300M_pure.json`. What it does: + +- Reads `mp_rank_00/model_optim_rng.pt` and pulls the `model` state dict +- For each of the 12 FLA layers, pairs the alternating Megatron sublayers: + - GDN sublayer (even index) → FLA `model.layers..attn.*` + - MLP sublayer (odd index) → FLA `model.layers..mlp.*` +- Splits Primus's **fused** projections into FLA's separate ones: + - `mixer.in_proj.weight` (rows = `2·key_dim + 2·value_dim + 2·num_v_heads`) → `q_proj / k_proj / v_proj / g_proj / b_proj / a_proj` + - `mixer.conv1d.weight` → `q_conv1d / k_conv1d / v_conv1d` + - `mlp.linear_fc1.weight` (rows = `2·intermediate_size`) → `gate_proj / up_proj` +- Handles **both** layer-spec variants: + - TE spec (`gdn_hybrid_stack_spec`): norm fused into linear (`mixer.in_proj.layer_norm_weight`, `mlp.linear_fc1.layer_norm_weight`) + - No-TE spec (`gdn_hybrid_stack_spec_no_te`, **used by the validated run**): separate `WrappedTorchNorm` modules (`norm.weight`, `pre_mlp_layernorm.weight`) +- Preserves `A_log`, `dt_bias`, per-head `out_norm`, `out_proj`, embeddings, tied `lm_head`, final norm + +Output: + +``` +output/gdn_pure_300M_fla_hf_final/ +├── config.json # GatedDeltaNetConfig, architectures=["GatedDeltaNetForCausalLM"] +├── model.safetensors # ~870 MB +└── tokenizer_config.json # placeholder — point to meta-llama/Llama-3.2-1B at load time +``` + +For the 1B pure-GDN model, same command but use the 1B checkpoint path — the converter auto-selects `gated_deltanet_1B_pure.json`. + +--- + +## Step 8: Verify conversion + +Run the sanity check at `[tools/hybrid/verify_gdn_conversion.py](../../tools/hybrid/verify_gdn_conversion.py)`: + +```bash +python tools/hybrid/verify_gdn_conversion.py \ + --model-path output/gdn_pure_300M_fla_hf_final +``` + +It loads the converted model in bf16 on GPU, runs three test prompts, and reports per-prompt loss, top-5 next-token IDs, and a 40-token greedy continuation. **Expected output** for a healthy 300M-on-10B model: + + +| Prompt | Loss | Top-1 | Verdict | +| ------------------------------------------- | ---- | ------------------------------------------- | ---------------- | +| "The capital of France is" | ~3.7 | `Paris` | knows the answer | +| "Machine learning is a field of" | ~2.8 | `artificial` | knows the domain | +| "The largest planet in our solar system is" | ~2.3 | one of `[the, Jupiter, a, called, located]` | knows the topic | + + +Loss <6.0 = PASS. Greedy decoding will produce *grammatical but repetitive* English (e.g. *"Paris. The capital is Paris. The capital is Paris..."*) — this is the canonical small-undertrained-LM failure mode with no repetition penalty and is **not** a sign of conversion error. + +### Optional — logit parity vs the FLA reference checkpoint + +If you have the FLA reference HF checkpoint locally (e.g. trained by FLA itself, or downloaded from `fla-hub`), compare per-token logits: + +```bash +python - <<'PY' +import torch, fla +from fla.models.gated_deltanet import GatedDeltaNetForCausalLM + +ids = torch.tensor([[1, 791, 6864, 315, 9822, 374]]) # "The capital of France is" + +hf = GatedDeltaNetForCausalLM.from_pretrained("output/gdn_pure_300M_fla_hf_final", + torch_dtype=torch.bfloat16).cuda().eval() +ref = GatedDeltaNetForCausalLM.from_pretrained("/path/to/fla/checkpoints/gdn_pure_300M_10BT", + torch_dtype=torch.bfloat16).cuda().eval() + +with torch.no_grad(): + h = hf (ids.cuda()).logits[0, -1].float().cpu() + r = ref(ids.cuda()).logits[0, -1].float().cpu() + +print(f"Cosine sim: {torch.nn.functional.cosine_similarity(h, r, dim=0).item():.4f}") +print(f"Top-1 (converted): {h.argmax().item()} Top-1 (FLA ref): {r.argmax().item()}") +print(f"Top-5 (converted): {h.topk(5).indices.tolist()}") +print(f"Top-5 (FLA ref): {r.topk(5).indices.tolist()}") +PY +``` + +**Expected:** + + +| Metric | Value | Interpretation | +| ----------------- | ----------- | ------------------------------------------------------------------ | +| Cosine similarity | **≥ 0.95** | conversion is correct | +| Top-5 set overlap | **≥ 3 / 5** | distributions agree | +| Top-1 exact match | optional | a single-token disagreement is within 0.24 % loss divergence noise | + + +If cosine < 0.5 → permutation bug. If 0.5–0.95 → likely missing-key issue (check that all 12 layers got `A_log`, `dt_bias`, `out_norm`). + +--- + +## Step 9: Run lm-eval-harness benchmarks + +Use `[tools/hybrid/eval_gdn_lm_eval.py](../../tools/hybrid/eval_gdn_lm_eval.py)`, which imports `fla` first (so `AutoConfig` recognizes the `gated_deltanet` model type) and patches the FLA model `__init__` to accept the `dtype` kwarg that `transformers ≥ 4.55` passes internally. + +**Do not** invoke `lm_eval --model hf ...` directly — `AutoConfig.from_pretrained` will fail with `model type gated_deltanet not recognized`. + +### 9.1 Standard six-task suite (~15–30 min on one MI300X) + +```bash +python tools/hybrid/eval_gdn_lm_eval.py \ + --model hf \ + --model_args pretrained=output/gdn_pure_300M_fla_hf_final,dtype=bfloat16,trust_remote_code=True,tokenizer=meta-llama/Llama-3.2-1B \ + --tasks arc_easy,arc_challenge,hellaswag,openbookqa,piqa,winogrande,mmlu,race \ + --batch_size auto \ + --output_path output/gdn_pure_300M_eval_results_final +``` + +### 9.2 Full FLA-paper suite (adds MMLU + RACE, ~1–2 h) + +```bash +python tools/hybrid/eval_gdn_lm_eval.py \ + --model hf \ + --model_args pretrained=output/gdn_pure_300M_fla_hf_final,dtype=bfloat16,trust_remote_code=True,tokenizer=meta-llama/Llama-3.2-1B \ + --tasks arc_easy,arc_challenge,hellaswag,mmlu,openbookqa,piqa,race,winogrande \ + --batch_size auto \ + --output_path output/gdn_pure_300M_eval_results_final +``` + +### 9.3 Diff against the FLA reference run + +If you also evaluated FLA's own checkpoint (`output/gdn_pure_300M_fla_eval_results/`), compare the JSONs: + +```bash +python - <<'PY' +import json, glob +def load_latest(d): return json.load(open(sorted(glob.glob(f"{d}/**/results_*.json", recursive=True))[-1])) +fla = load_latest("output/gdn_pure_300M_fla_eval_results") +primus = load_latest("output/gdn_pure_300M_eval_results_final") +print(f"{'task':<18} {'FLA':>8} {'Primus':>8} {'Δ':>+8}") +for task in sorted(set(fla['results']) & set(primus['results'])): + for k in ('acc,none', 'acc_norm,none'): + if k in fla['results'][task]: + f, p = fla['results'][task][k], primus['results'][task][k] + print(f"{task[:17]:<18} {f:>8.4f} {p:>8.4f} {p-f:>+8.4f} ({k})") +PY +``` + +**Expected:** each task within ±1.5 absolute accuracy points (consistent with the 0.24 % loss delta at the end of training). + +--- + +## Configs and tools used + +``` +docs/04-technical-guides/hybrid-models/ +├── gdn-guide.md ← this file +└── gdn-fla-parity.md ← deep-dive on every patch & env var +primus/backends/megatron/patches/ +├── mamba_fused_ce_patches.py ← FLA fused cross-entropy for MambaModel +├── torch_fused_adam_patches.py ← PRIMUS_TORCH_OPTIM opt-in +├── mlp_fla_swiglu_patches.py ← FLA Triton SwiGLU for MLP +├── torch_norm_fla_rmsnorm_patches.py ← FLA RMSNorm for WrappedTorchNorm +├── gdn_config_patches.py ← linear-attention config fields + hybrid init +├── fla_runtime_patches.py ← resolves PRIMUS_FLA_* knobs onto args +└── mamba_fla_data_patches.py ← FLA-order dataset shim wiring +examples/megatron/configs/MI300X/ +└── zebra_llama_300M_gdn_pure-pretrain.yaml ← training config +primus/configs/models/megatron/ +└── zebra_llama_300M_gdn_pure.yaml ← architecture-only config +primus/backends/megatron/core/models/hybrid/ +├── gated_delta_net.py ← FLA-aligned mixer (FLA Triton paths) +├── gated_delta_net_layer.py ← eps propagation, pre-norm fusion +├── hybrid_block.py ← HybridStack, fp32-residual + fusion +└── hybrid_mamba_mla_layer_specs.py ← gdn_hybrid_stack_spec_no_te +tools/hybrid/ +├── patch_fla_triton_autotune_hang.sh ← MI300X FLA Triton autotune-hang workaround +├── convert_fla_to_megatron.py ← FLA Arrow → Megatron .bin/.idx +├── fla_order_dataset.py ← FLA-order dataset shim +├── convert_gdn_to_fla_hf.py ← Megatron → FLA HF (handles TE + no-TE) +├── verify_gdn_conversion.py ← loss + greedy generation sanity check +└── eval_gdn_lm_eval.py ← lm-eval wrapper (registers FLA) +``` + +--- + +## Troubleshooting + +### `KeyError: 'decoder.layers.0.mixer.in_proj.layer_norm_weight'` during conversion + +You trained with `gdn_hybrid_stack_spec_no_te` (separate `WrappedTorchNorm`) but are running an old version of `convert_gdn_to_fla_hf.py` that only knew the TE spec. Pull the latest converter — it now tries TE keys first and falls back to `norm.weight` / `pre_mlp_layernorm.weight`. + +### Loss is flat near 11.97 for many iterations + +LR warmup misconfigured. Confirm `lr_warmup_iters: 200` matches your `train_iters`, and verify the YAML override block resolved correctly by checking the logged config near the top of the training log. + +### Iter 1 loss ~12.1 instead of ~11.97 + +The `layernorm_epsilon: 1.0e-6` override is being silently overwritten by the TransformerConfig default of `1e-5`. Confirm it's in the *training* YAML's `overrides:` block (not just the model YAML) — see `[zebra_llama_300M_gdn_pure-pretrain.yaml](../../examples/megatron/configs/MI300X/zebra_llama_300M_gdn_pure-pretrain.yaml)` for the canonical placement. + +### Iter 1 loss not bit-matching FLA but converges fine + +You probably didn't enable `PRIMUS_FLA_DATA=1` — the Megatron `GPTDataset` shuffler is producing a different first batch than FLA's `DistributedSampler`. Either enable that env var (with `PRIMUS_FLA_CACHE_DIR` set) or accept the ~0.0002 loss delta at iter 1 (it disappears by iter ~2000). + +### Iter 1 takes ~60 seconds, subsequent iters are fast + +Cold MIOpen + Triton autotune caches. Normal on a freshly-rebooted node. The run-averaged ms/iter takes ~1500 iters to fully wash out this cold-start tax; the instantaneous ms/iter is at steady state by iter 200. + +### Eval fails with `model type gated_deltanet not recognized` + +You ran `lm_eval --model hf` directly instead of the wrapper. Use `python tools/hybrid/eval_gdn_lm_eval.py --model hf ...` — it imports `fla` first to register the model class. + +### Eval truncation warnings + +Some samples exceed the model's `max_position_embeddings = 2048`. Add `max_length=1024` to `--model_args` if it bothers you; it only meaningfully affects RACE. + +### Per-iter time +1–2 % above FLA on healthy hardware + +Expected with all four `PRIMUS_FLA_`* env vars set. The biggest single cost is `PRIMUS_TORCH_OPTIM=1` (torch fused AdamW vs Apex FusedAdam). Drop it if you don't need bit-level optimizer parity; you keep the loss-curve match and recover ~1 % perf. + +--- + +## See also + +- `[docs/04-technical-guides/hybrid-models/README.md](README.md)` — full Zebra-Llama family overview (1B / 3B / 8B Mamba+MLA, KDA variants) +- `[gdn-fla-parity.md](gdn-fla-parity.md)` — exhaustive list of code/config/runtime changes that made parity possible +- FLA upstream: [https://github.com/fla-org/flash-linear-attention](https://github.com/fla-org/flash-linear-attention) diff --git a/docs/04-technical-guides/hybrid-models/kda-fla-parity.md b/docs/04-technical-guides/hybrid-models/kda-fla-parity.md new file mode 100644 index 000000000..d365215c1 --- /dev/null +++ b/docs/04-technical-guides/hybrid-models/kda-fla-parity.md @@ -0,0 +1,272 @@ +# KDA ⇄ FLA Parity in Primus + +This document captures every change required in Primus and the vendored +Megatron-LM submodule to make a 300M Kimi Delta Attention (KDA) pretraining +run match the [Flash Linear Attention (FLA)](https://github.com/fla-org/flash-linear-attention) +reference implementation on **loss trajectory, step throughput, and +downstream lm-eval accuracy** on 8× MI300X. + +This is the KDA-side companion to [`gdn-fla-parity.md`](gdn-fla-parity.md); +because KDA shares Megatron-LM submodule patches with GDN, the architecture +and tooling sections below focus on the KDA-specific deltas. + +## Final result + +| Axis | FLA reference | Primus (this branch) | Δ | +|------|---------------|----------------------|----| +| Per-iteration time (steady state, iter > 200) | **1493 ms** | **1466.8 ms** | **−1.8% (Primus faster)** | +| Throughput (tok/s/GPU) | 175,617 | **178,810** | **+1.8%** | +| TFLOP/s/GPU | — | 626.9 | — | +| Total wall time (4768 iters) | 1h 58m 39s (7119.2 s) | **1h 56m 33s** (~6993 s) | **−126 s (Primus faster)** | +| Loss @ iter 1 | 11.9673 | **11.9669** | **−0.00% (bit-perfect)** | +| Loss @ iter 1000 | 4.0357 | 4.0720 | +0.90% | +| Loss @ iter 2000 | 3.6009 | 3.6141 | +0.37% | +| Loss late-training (iter 3700–4700 avg) | 3.3681 | 3.3846 | +0.49% | +| First crossover (Primus < FLA) | — | iter 2600 (and 3600) | — | + +**Loss curves overlap from iter ~2000 onward**, with batch-to-batch +oscillation of ±0.5%. The only persistent gap is in the LR-warmup region +(iter 50–500), and that gap closes monotonically with no instability. +Iter-1 forward at fp32 is bit-identical to FLA when the FLA-init checkpoint +is loaded. + +### Downstream lm-eval parity + +After full training (4768 iters / ~10B tokens), both the Primus-trained +KDA-300M and the FLA-trained KDA-300M were converted to HuggingFace +`KDAForCausalLM` and evaluated with `lm-eval-harness` on the FLA-paper +8-task suite. Every task is within ±1.4 absolute accuracy points, well +inside the ±1.5 pp tolerance set by the 0.49% loss delta. + +The `Random` column is `100 / num_choices` for the task (25 % for +4-choice tasks, 50 % for 2-choice tasks) — anything above it means the +model has learned something. arc_easy / hellaswag / openbookqa / piqa +clearly clear the bar; mmlu / race / arc_challenge sit at random for +*both* training stacks (a 300 M model on 10 B tokens is below those +benchmarks' lift-off threshold), which is exactly the regime the FLA +paper reports. + +| Task | Metric | Random | FLA | Primus | Δ (Primus − FLA) | +|--------------------------|------------|-------:|-------:|-------:|-----------------:| +| arc_challenge | acc_norm | 25.00 | 25.17 | 25.00 | −0.17 pp | +| arc_easy | acc | 25.00 | 48.78 | 47.94 | −0.84 pp | +| arc_easy | acc_norm | 25.00 | 42.76 | 43.39 | +0.63 pp | +| hellaswag | acc_norm | 25.00 | 29.16 | 29.18 | +0.02 pp | +| openbookqa | acc_norm | 25.00 | 30.40 | 29.00 | −1.40 pp | +| piqa | acc_norm | 50.00 | 60.99 | 60.34 | −0.65 pp | +| winogrande | acc | 50.00 | 51.85 | 52.72 | **+0.87 pp** | +| mmlu (aggregate) | acc | 25.00 | 22.88 | 23.12 | +0.24 pp | +| race | acc | 25.00 | 25.07 | 25.45 | +0.38 pp | +| **mean absolute Δ** | | | | | **0.58 pp** | + +See [`kda-guide.md`](kda-guide.md) for +the exact `lm_eval` invocation that produced both rows. + +--- + +## How to run + +Inside the `rocm/primus:v26.2` container with the repo mounted at +`/home//Primus`: + +```bash +# 1. (one time) build the FLA-init KDA-300M checkpoint +python tools/hybrid/convert_fla_kda_init_to_megatron.py +# → output/fla_init_kda_300M/iter_0000000/mp_rank_00/model_optim_rng.pt + +# 2. Launch training (8 GPUs by default). The Megatron-LM behavioral +# patches (same set as GDN) are applied automatically at startup via +# Primus's patch system -- no separate apply step needed. +EXP=examples/megatron/configs/MI300X/zebra_llama_300M_kda_pure-pretrain.yaml \ + bash examples/run_pretrain.sh 2>&1 | tee primus_kda.log +``` + +### Recommended toggle profile (YAML or env var) + +KDA uses the same toggle set as GDN. Each knob is exposed at two +equivalent surfaces — the YAML knob (canonical, declarative; co-located +with the rest of the run config) and the legacy env var (ad-hoc, for +one-off A/B without editing a YAML). When both are set, the env var +wins (backward compat); see +`primus/backends/megatron/patches/fla_runtime_patches.py` for the +precedence rules. Defaults below match FLA's numerics on MI300X: + +| YAML knob | Env var | Default | Effect | +|--|--|--|--| +| `fused_ce_mode` | `PRIMUS_FUSED_CE` | `1` | `1` = FLA `FusedLinearCrossEntropyLoss` (chunked, no full logits tensor); `2` = FLA `FusedCrossEntropyLoss` (matches FLA exactly); `0` = native Megatron CE. | +| `fused_ce_chunks` | `PRIMUS_FUSED_CE_CHUNKS` | `32` | Number of chunks the FLA CE splits the logits across. Lower = faster but bigger peak allocation. | +| `use_fla_fused_swiglu` | `PRIMUS_FLA_SWIGLU` | `1` | Replaces Megatron's naive SwiGLU with FLA's Triton-fused kernel (≈20 ms/step saved). | +| `use_fla_fused_rmsnorm` | `PRIMUS_FLA_NORM` | `1` | Use FLA's `RMSNorm` Triton kernel via `WrappedTorchNorm`. KDA's gated output norm is selected separately via `use_fla_fused_norm_gated` in the model YAML. | +| `use_fla_short_conv` | `PRIMUS_FLA_CONV` | `1` | Route KDA's depthwise short conv1d through FLA's Triton `causal_conv1d` (saves ~35 ms/iter by accepting `[B, T, D]` directly — no `transpose+contiguous` round-trip). | +| _(env-only)_ | `PRIMUS_TORCH_OPTIM` | `1` | Use `torch.optim.AdamW(fused=True)` instead of TE/Apex `FusedAdam` (matches FLA bit-for-bit). | +| `use_fla_data` + `fla_cache_dir` | `PRIMUS_FLA_DATA` + `PRIMUS_FLA_CACHE_DIR` | `0` / `""` | When `use_fla_data=true` and `fla_cache_dir=`, replace Megatron's `GPTDataset` with the `FLAOrderGPTDataset` shim that emits tokens in the exact same order as FLA's HuggingFace `DistributedSampler`. | + +KDA's TE/no-TE selection is done by the `spec:` line in the YAML +(`kda_hybrid_stack_spec_no_te` for no-TE, which is the default). + +--- + +## What changed and why + +The work splits into three layers: KDA-specific model code, KDA-specific +runtime config flags, and shared Megatron-LM patches (already documented +in `gdn-fla-parity.md`). + +### A. Primus model code (KDA-specific) + +| File | Change | Reason | +|------|--------|--------| +| `primus/backends/megatron/core/models/hybrid/kimi_delta_attention.py` | Replace six separate `hidden_states → X` projections (q, k, v, beta, f_a, g_a) with a single fused `in_proj: ColumnParallelLinear` of width `2·qk_dim + v_dim + 2·head_v_dim + num_v_heads`. Split downstream into `[qkv | f_a | g_a | beta]`. The two low-rank-bottleneck expansion projections (`f_b`, `g_b`) stay separate because their input is the 64-dim bottleneck output, not `hidden_states`. | Matches GDN's fusion recipe. On ROCm each separate matmul pays ~3-5 ms of HIP dispatch + autograd overhead; the GDN parity work measured this same fusion at ~250 ms/iter saved. KDA was originally launching 6 matmuls/layer × 12 layers = 72 dispatches; now it's 12. | +| same file | Add optional `FusedRMSNormGated` (RMSNorm + sigmoid-gate + multiply in ONE Triton kernel) for the per-head output gate, gated by `use_fla_fused_norm_gated` (default `True` when `use_fla_triton_kda=True`). | Avoids materializing the post-norm tensor and the fp32-upcast gate for backward — saves ~6.4 GiB activation memory per rank at micro_batch=128. Matches `fla/layers/kda.py` exactly. | +| same file | Add optional in-kernel gate fusion path: when `use_fla_kda_in_kernel_gate=True`, call `chunk_kda(..., A_log=…, dt_bias=…, use_gate_in_kernel=True)` and let the kernel fuse `−exp(A_log) · softplus(g + dt_bias) + cumsum` internally (recomputed in backward). The pre-fusion `fused_kda_gate()` path is kept under `use_fla_kda_in_kernel_gate=False` for bit-identical comparison with FLA's old code. | Smallest activation footprint. The bf16 in-kernel accumulator drifts ~+0.2 lm-loss vs the explicit-gate path on ROCm at 12 layers depth; the FLA-init checkpoint cancels the drift, giving GDN-style parity. | +| same file | Add optional FLA Triton `causal_conv1d` path under `args.use_fla_short_conv` (was `PRIMUS_FLA_CONV`). The FLA kernel accepts `[B, T, D]` directly (no `transpose+contiguous` round-trip). | Matches the conv backend FLA's `ShortConvolution` uses. Saves ~35 ms/iter (two avoided full-qkv buffer copies × ~17 ms each). | +| same file | `g_b_proj.bias=True` and `dt_bias` initialised by FLA's log-uniform + inverse-softplus recipe (was `nn.init.ones_` → `dt ≈ 1.31`, ~20× larger than FLA's intended range). `beta = b_proj(h).float().sigmoid()` (fp32 sigmoid stops bf16 drift across 12 layers). Removed the `@torch.compiler.disable` decorator on `forward()`. | (a) `g_b_proj` bias matches `fla/layers/kda.py:189`. (b) `dt_bias` init matches `fla/layers/kda.py:180-184`; without it the gate's initial decay step is ~20× too large and the loss curve drifts visibly by iter 100. (c) fp32 sigmoid eliminates ~+0.2 lm-loss bf16 drift. (d) the compiler-disable was a leftover from debugging and cost ~25 ms/iter in dispatch overhead. | +| same file | Materialize `q.contiguous() / k.contiguous() / v.contiguous()` after the `torch.split` on the fused in_proj output. | The `torch.split` along `dim=-1` returns non-contiguous views; passing them into `chunk_kda` as views makes the Triton kernel allocate a second internal contiguous copy while autograd still pins the original views. Net 2× activation memory for Q/K/V (~29 GiB extra at micro_batch=128). The explicit `.contiguous()` here gives autograd a single canonical buffer to save. Tested: 184 GiB → 155 GiB at iter 1. | +| `primus/backends/megatron/core/models/hybrid/kimi_delta_attention_layer.py` | Add `KimiDeltaAttentionLayerSubmodules.norm` field (default `IdentityOp`). When set to `WrappedTorchNorm`, the layer applies an explicit pre-norm matching `fla/models/kda/modeling_kda.py:113` `hidden_states = self.attn_norm(...)`. `eps` is forwarded explicitly because `WrappedTorchNorm` defaults to `1e-5` while KDA configs (and FLA) use `1e-6`. | Required for the no-TE spec (which uses plain `ColumnParallelLinear` for `in_proj`) to apply the pre-norm separately. Without this fix the no-TE path skipped the pre-norm entirely, producing nonsense at iter 1. | +| `primus/backends/megatron/core/models/hybrid/hybrid_mamba_mla_layer_specs.py` | Add a new `kda_hybrid_stack_spec_no_te` ModuleSpec — plain `WrappedTorchNorm`, plain `ColumnParallelLinear`, plain `RowParallelLinear`, mixer `gate_norm=IdentityOp` (FLA has no re-norm for the gate path). | YAML can now select TE-free KDA layers via `spec: [..., kda_hybrid_stack_spec_no_te]` for FLA loss-curve alignment without touching code. Mirrors `gdn_hybrid_stack_spec_no_te`. | +| `primus/backends/megatron/patches/gdn_config_patches.py` | Register `use_fla_kda_in_kernel_gate` (default `True`) and `use_fla_fused_norm_gated` (default `None` → auto when `use_fla_triton_kda=True`) as `TransformerConfig` fields. | Lets the YAML `overrides:` block toggle the two KDA-specific fusion paths without touching code. | + +### B. Megatron-LM behavioral patches (shared with GDN, Primus patch system) + +KDA reuses the **exact same six patches** that GDN uses; no KDA-specific +megatron-LM patch is required. These are runtime monkey-patches registered +with `@register_patch` under `primus/backends/megatron/patches/` and applied +automatically at `phase="before_train"` -- see `gdn-fla-parity.md` section B +for the patch-by-patch breakdown. Nothing needs to be run by hand. + +### C. YAML configuration changes + +#### `primus/configs/models/megatron/zebra_llama_300M_kda_pure.yaml` (new) + +300M architecture-only YAML matched to FLA's `kda_300M_pure.json`: + +```yaml +extends: [mamba_base.yaml] + +num_layers: 24 # 12 KDA + 12 MLP sublayers +hidden_size: 1024 +ffn_hidden_size: 4096 + +# Pure KDA — no attention layers +is_hybrid_model: true +hybrid_attention_ratio: 0.0 + +# KDA params (match FLA exactly) +linear_conv_kernel_dim: 4 +linear_key_head_dim: 32 # 8 heads × 32 = 256 qk_dim +linear_value_head_dim: 64 # 8 heads × 64 = 512 v_dim (expand_v=2.0) +linear_num_key_heads: 8 +linear_num_value_heads: 8 + +# Tied embeddings, all linear bias=False, RMSNorm eps=1e-6 +untie_embeddings_and_output_weights: false +add_bias_linear: false +normalization: RMSNorm +norm_epsilon: 1.0e-6 +position_embedding_type: none +``` + +#### `examples/megatron/configs/MI300X/zebra_llama_300M_kda_pure-pretrain.yaml` (new) + +The training-side config sets: + +```yaml +# Training schedule matched to FLA (8 GPUs) +train_iters: 4768 # ≈10B tokens at 1024×2048 = 2.1M tok/iter +micro_batch_size: 128 +global_batch_size: 1024 + +# FLA optimizer / LR schedule +lr: 2.0e-4 +min_lr: 2.0e-5 # min_lr_rate=0.1 +lr_warmup_iters: 200 +lr_decay_iters: 4768 +lr_decay_style: cosine +adam_beta1: 0.9; adam_beta2: 0.95 +weight_decay: 0.01; clip_grad: 1.0 +seed: 42 + +# Norm — Megatron default is 1e-5; FLA uses 1e-6 +layernorm_epsilon: 1.0e-6 +hidden_dropout: 0.0; attention_dropout: 0.0 + +# Pure KDA, no-TE spec (matches FLA KDABlock layout exactly) +spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', + 'kda_hybrid_stack_spec_no_te'] +use_fla_triton_kda: true +use_fla_kda_in_kernel_gate: true +use_fla_fused_norm_gated: true + +# Plain DDP, matches FLA — distributed optimizer (ZeRO-1) costs allreduce +# bandwidth and saves only ~3.6 GiB/rank for a 300M model +use_distributed_optimizer: false +overlap_grad_reduce: true +ddp_average_in_collective: true + +# FLA-init checkpoint — bit-perfect iter-1 forward +finetune: true; no_load_optim: true; no_load_rng: true +load: /home//Primus/output/fla_init_kda_300M +``` + +--- + +## Reproducing the loss-curve match plot + +The full per-iteration log lives at `primus_kda.log` once training +finishes. Compare against FLA's `trainer_state.json` log_history +(`/home//checkpoints/kda_pure_300M_10B/trainer_state.json`). + +Notable comparison points (FLA loss is divided by 8 to undo the +DeepSpeed sum-across-ranks): + +| iter | FLA / 8 | Primus | Δ% | Notes | +|-----:|--------:|-------:|---:|-------| +| 1 | 11.9673 | 11.9669 | **−0.00%** | bit-perfect (forward fp32) | +| 100 | 7.7171 | 9.6903 | +25.6% | warmup gap (peak) | +| 500 | 4.7349 | 4.8390 | +2.20% | warmup closing | +| 1000 | 4.0357 | 4.0720 | +0.90% | LR-warmup done | +| 2000 | 3.6009 | 3.6141 | +0.37% | converged | +| 2600 | 3.5056 | 3.5047 | **−0.03%** | first Primus < FLA crossover | +| 3000 | 3.4356 | 3.4571 | +0.63% | matched | +| 3600 | 3.4107 | 3.4075 | **−0.09%** | Primus slightly lower | +| 4000 | 3.3831 | 3.3861 | +0.09% | identical | +| 4500 | 3.3603 | 3.3694 | +0.27% | identical | +| 4700 | 3.3388 | 3.3624 | +0.71% | identical | + +The persistent gap (iter 50–500) is attributable to dataloader ordering — +Megatron `GPTDataset` uses its own random shuffler while FLA uses +HuggingFace's `DistributedSampler`. With `use_fla_data: true` the gap +closes further but Primus has been verified to converge to within ±1% by +iter 1000 even without it. + +--- + +## Files in the repo for this work + +``` +primus/backends/megatron/core/models/hybrid/ + kimi_delta_attention.py # FLA-aligned mixer + kimi_delta_attention_layer.py # wrapper w/ pre-norm + hybrid_mamba_mla_layer_specs.py # kda_hybrid_stack_spec_no_te +primus/backends/megatron/patches/ # 6 patches (same as GDN), Primus patch system + gdn_config_patches.py # registers KDA fusion flags + hybrid init + mamba_fused_ce_patches.py # FLA fused cross-entropy for MambaModel + torch_fused_adam_patches.py # PRIMUS_TORCH_OPTIM opt-in + mlp_fla_swiglu_patches.py # FLA Triton SwiGLU for MLP + torch_norm_fla_rmsnorm_patches.py # FLA RMSNorm for WrappedTorchNorm + fla_runtime_patches.py # resolves PRIMUS_FLA_* knobs onto args + mamba_fla_data_patches.py # FLA-order dataset shim wiring +primus/configs/models/megatron/ + zebra_llama_300M_kda_pure.yaml # architecture-only +examples/megatron/configs/MI300X/ + zebra_llama_300M_kda_pure-pretrain.yaml # training config +tools/hybrid/ + convert_fla_to_megatron.py # FLA Arrow → Megatron .bin/.idx (shared) + fla_order_dataset.py # FLA-order dataset shim (shared) + convert_fla_kda_init_to_megatron.py # FLA HF init → Megatron sharded ckpt + convert_kda_to_fla_hf.py # Megatron sharded ckpt → FLA HF + eval_kda_lm_eval.py # lm-eval wrapper (registers KDA) +docs/04-technical-guides/hybrid-models/ + kda-guide.md # step-by-step recipe + kda-fla-parity.md # this file +``` diff --git a/docs/04-technical-guides/hybrid-models/kda-guide.md b/docs/04-technical-guides/hybrid-models/kda-guide.md new file mode 100644 index 000000000..54b2e6f59 --- /dev/null +++ b/docs/04-technical-guides/hybrid-models/kda-guide.md @@ -0,0 +1,646 @@ +# Pure KDA 300M on Primus — End-to-End Guide (FLA-validated) + +This document is a runnable walkthrough for the **300M pure Kimi Delta +Attention (KDA)** pretraining recipe in Primus, validated on 8× AMD MI300X +against the [Flash Linear Attention (FLA)](https://github.com/fla-org/flash-linear-attention) +reference implementation. It covers every step from raw dataset → +tokenization → training → checkpoint conversion → lm-eval benchmark. + +The same recipe scales up to the 1B pure-KDA config (`zebra_llama_1B_kda_pure-pretrain.yaml`). + +It mirrors [`gdn-guide.md`](gdn-guide.md) and reuses the same Megatron-LM +patches, dataset shim, FLA-init flow, and lm-eval wrapper pattern. + +--- + +## Final result + +After 4768 iterations (≈10B tokens) on FineWeb-Edu sample-10BT: + +| Axis | FLA reference | Primus (this branch) | Δ | +| ----------------------------------------------- | ----------------- | --------------------- | --------------------------- | +| Per-iteration time (steady state, iter > 200) | **1493 ms** | **1466.8 ms** | **−1.8 % (Primus faster)** | +| Throughput | 175,617 tok/s/GPU | **178,810 tok/s/GPU** | **+1.8 %** | +| TFLOP/s/GPU | — | 626.9 | — | +| Wall time (4768 iters, 8× MI300X, healthy node) | 1h 58m 39s | **1h 56m 33s** | **−126 s** | +| Loss @ iter 1 | 11.9673 | **11.9669** | **−0.00 % (bit-perfect)** | +| Loss @ iter 4700 (final logged) | 3.3388 | **3.3624** | **+0.71 %** | +| First Primus-below-FLA crossover | — | iter 2600 | — | + +Loss trajectories overlap from iter ~2000 onward; the only persistent gap +is in the LR-warmup region (iter 50–500) and closes monotonically. See +[`kda-fla-parity.md`](kda-fla-parity.md) for the deep-dive on every +patch and env var. + +### lm-eval-harness (FLA-paper 8-task suite) + +Random chance is `100 / num_choices` — 25 % for the 4-choice tasks +(arc, hellaswag, openbookqa, mmlu, race) and 50 % for the 2-choice tasks +(piqa, winogrande). Any score above random shows the model has learned +*something*; the FLA and Primus rows show how closely the two training +stacks track each other on the same 10 B-token diet. + +| Task | Metric | Random | FLA | Primus | Δ (Primus − FLA) | +|--------------------------|------------|-------:|-------:|-------:|-----------------:| +| arc_challenge | acc_norm | 25.00 | 25.17 | 25.00 | −0.17 pp | +| arc_easy | acc | 25.00 | 48.78 | 47.94 | −0.84 pp | +| arc_easy | acc_norm | 25.00 | 42.76 | 43.39 | +0.63 pp | +| hellaswag | acc_norm | 25.00 | 29.16 | 29.18 | +0.02 pp | +| openbookqa | acc_norm | 25.00 | 30.40 | 29.00 | −1.40 pp | +| piqa | acc_norm | 50.00 | 60.99 | 60.34 | −0.65 pp | +| winogrande | acc | 50.00 | 51.85 | 52.72 | **+0.87 pp** | +| mmlu (aggregate) | acc | 25.00 | 22.88 | 23.12 | +0.24 pp | +| race | acc | 25.00 | 25.07 | 25.45 | +0.38 pp | +| **mean absolute Δ** | | | | | **0.58 pp** | + +Every task within ±1.4 pp — well inside the ±1.5 pp tolerance set by the +0.49% mid-training loss delta. Both stacks comfortably beat random on +arc_easy, hellaswag, openbookqa and piqa; mmlu/race/arc_challenge are at +random-chance for *both* training stacks (expected for a 300 M model on +only 10 B tokens — those benchmarks need 7 B+ parameters and/or +trillion-token training to lift above 25 %). + +--- + +## Table of contents + +- [Overview](#overview) +- [Prerequisites](#prerequisites) +- [Step 1: Environment](#step-1-environment) +- [Step 2: Dataset preparation](#step-2-dataset-preparation) +- [Step 3: Apply Megatron-LM patches](#step-3-apply-megatron-lm-patches) +- [Step 4: (Optional) Initialize from FLA weights](#step-4-optional-initialize-from-fla-weights) +- [Step 5: Train](#step-5-train) +- [Step 6: Monitor and compare against FLA](#step-6-monitor-and-compare-against-fla) +- [Step 7: Convert checkpoint to HuggingFace format](#step-7-convert-checkpoint-to-huggingface-format) +- [Step 8: Verify conversion](#step-8-verify-conversion) +- [Step 9: Run lm-eval-harness benchmarks](#step-9-run-lm-eval-harness-benchmarks) +- [Configs and tools used](#configs-and-tools-used) +- [Troubleshooting](#troubleshooting) + +--- + +## Overview + +The 300M pure-KDA model has: + +- 12 Kimi-Delta-Attention blocks + 12 MLP blocks → 24 Megatron "sublayers" +- `hidden_size = 1024`, `ffn_hidden_size = 4096` +- `num_heads = num_v_heads = 8` (Q, K, V all share head count) +- `head_k_dim = 32`, `head_v_dim = 64` (expand_v = 2.0) +- Short-conv kernel size 4 (depthwise, on the concatenated QKV) +- Per-head output gate (`g_a → g_b`) + per-head decay gate (`f_a → f_b`, + combined with learnable `A_log` and `dt_bias` via `softplus`) +- Tied embeddings, no positional encoding (delta-rule recurrence), + RMSNorm with `eps = 1e-6` +- Tokenizer: `meta-llama/Llama-3.2-1B` (128k vocab) +- Total parameters: **0.302 B** + +Training schedule (matched to FLA's `kda_300M_pure.json`): + +- 4768 iterations × 1024 global batch × 2048 seq len = **10.0 B tokens** +- AdamW (β1=0.9, β2=0.95, wd=0.01), peak LR `2e-4`, cosine decay, 200-step warmup +- bf16 mixed-precision, no dropout, gradient clip 1.0 + +--- + +## Prerequisites + +- **Hardware**: 8× AMD MI300X (or compatible ROCm GPU) on a single node +- **Software**: ROCm ≥ 7.0, Docker ≥ 24.0 +- **Container image**: `rocm/primus:v26.2` (or `v25.10` with the same patches) +- **HF token**: `HF_TOKEN` set for the gated `meta-llama/Llama-3.2-1B` tokenizer +- **Disk**: ~20 GB for the FLA-aligned tokenized dataset + ~5 GB per saved checkpoint +- **flash-linear-attention** checked out at + `/home//flash-linear-attention` (or installed via + `pip install -e .`) — provides the FLA `KDAForCausalLM` class for + HF conversion + lm-eval, plus the Triton kernels that the `PRIMUS_FLA_*` + toggles route into. + +--- + +## Step 1: Environment + +### 1.1 Start the dev container + +```bash +docker run -it \ + --device /dev/dri --device /dev/kfd \ + --device=/dev/infiniband --network host --ipc host \ + --group-add video --cap-add SYS_PTRACE \ + --security-opt seccomp=unconfined --privileged \ + -v $HOME:$HOME -v $(pwd):$(pwd) -w $(pwd) --shm-size 64G --name primus_hybrid_new \ + rocm/primus:v26.2 +``` + +This runs the `rocm/primus:v26.2` image with `/dev/dri`, `/dev/kfd`, IB +devices, `--privileged`, your `$HOME` mounted in-place, and `--shm-size 64G`. +The container is named `primus_hybrid_new`. + +To re-attach later: + +```bash +docker exec -it primus_hybrid_new bash +cd /home//Primus +``` + +### 1.2 Install Python dependencies inside the container + +```bash +pip install -r requirements.txt +pip install -e /home//flash-linear-attention # FLA model classes + Triton kernels +pip install lm-eval # for benchmark evaluation +``` + +The editable FLA install removes the need to set `PYTHONPATH` for every +later command. + +--- + +## Step 2: Dataset preparation + +Identical to the GDN recipe — see +[`gdn-guide.md`](gdn-guide.md#step-2-dataset-preparation). KDA reuses the +same FineWeb-Edu sample-10BT preprocessed Arrow shards and the same +Llama-3.2-1B tokenizer. + +The default 300M YAML already points at the FLA-aligned binary: + +```yaml +train_data_path: > + /home//Primus/data/fla_aligned/fla_fineweb_edu_10BT_text_sentence +``` + +(adjust the user prefix in +`examples/megatron/configs/MI300X/zebra_llama_300M_kda_pure-pretrain.yaml` +to match your home directory). + +--- + +## Step 3: Megatron-LM patches (automatic — no action needed) + +KDA uses the **same six patches** as GDN — no KDA-specific Megatron patch +is required. They're implemented as runtime monkey-patches using Primus's +own patch system (`primus/core/patches`), living under +`[primus/backends/megatron/patches/](../../primus/backends/megatron/patches/)`. +Each patch registers unconditionally but is gated behind a `condition=` on +the relevant config flag, so nothing needs to be run by hand. + +See [`gdn-guide.md`](gdn-guide.md#step-3-megatron-lm-patches-automatic--no-action-needed) +§3 for the patch-by-patch breakdown. + +--- + +## Step 4: (Optional) Initialize from FLA weights + +For bit-perfect iter-1 loss alignment, the validated run loads FLA's +*initialized but untrained* KDA-300M checkpoint and then trains from +there. The YAML's `load:` field points at this directory: + +```yaml +load: /home//Primus/output/fla_init_kda_300M +finetune: true # load weights, ignore optimizer state and iteration count +no_load_optim: true +no_load_rng: true +``` + +Generate it once with: + +```bash +python tools/hybrid/convert_fla_kda_init_to_megatron.py +# → output/fla_init_kda_300M/iter_0000000/mp_rank_00/model_optim_rng.pt +``` + +The script instantiates FLA's `KDAForCausalLM` with `seed=42`, harvests +its randomly-initialized weights, concatenates the six FLA `hidden_states +→ X` projections into Primus's single fused `in_proj`, and writes a +Megatron-shape checkpoint. Skip this step if you're happy with Primus's +own random init — final loss is identical, only iter-1 drifts by `~5e-3`. + +--- + +## Step 5: Train + +### 5.1 Inspect the config + +The training config lives at +[`examples/megatron/configs/MI300X/zebra_llama_300M_kda_pure-pretrain.yaml`](../../examples/megatron/configs/MI300X/zebra_llama_300M_kda_pure-pretrain.yaml). +Key parameters (matched to FLA): + +```yaml +train_iters: 4768 # ≈ 10B tokens at global_batch=1024, seq=2048 +micro_batch_size: 128 # per-GPU +global_batch_size: 1024 # 8 GPUs × 128 = 1024 +seq_length: 2048 +lr: 2.0e-4 +min_lr: 2.0e-5 # min_lr_rate=0.1 → 2e-5 +lr_warmup_iters: 200 +lr_decay_iters: 4768 +lr_decay_style: cosine +adam_beta1: 0.9 +adam_beta2: 0.95 +weight_decay: 0.01 +clip_grad: 1.0 +seed: 42 +layernorm_epsilon: 1.0e-6 # MUST be explicit — TransformerConfig default 1e-5 silently overrides the model YAML +hidden_dropout: 0.0 # MUST be explicit — language_model.yaml default 0.1 leaks through +attention_dropout: 0.0 +spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'kda_hybrid_stack_spec_no_te'] +use_fla_triton_kda: true +use_fla_kda_in_kernel_gate: true +use_fla_fused_norm_gated: true +use_distributed_optimizer: false # 300M fits — ZeRO-1 adds allreduce overhead +finetune: true +load: /home//Primus/output/fla_init_kda_300M +no_load_optim: true +no_load_rng: true +``` + +The architecture-only YAML it extends from is +[`primus/configs/models/megatron/zebra_llama_300M_kda_pure.yaml`](../../primus/configs/models/megatron/zebra_llama_300M_kda_pure.yaml). + +### 5.2 Launch + +```bash +# inside the container, in /home//Primus +EXP=examples/megatron/configs/MI300X/zebra_llama_300M_kda_pure-pretrain.yaml \ + bash examples/run_pretrain.sh 2>&1 | tee primus_kda.log +``` + +Expected wall time on a healthy MI300X box: **~1h 56m** for the full 4768 +iters (about 2 min faster than FLA's HF-Trainer reference run). + +### 5.3 Recommended toggle profile (for FLA parity) + +Preferred (canonical) — add to the experiment YAML's `overrides:` block: + +```yaml +# FLA runtime knobs (consumed by primus.backends.megatron.patches.fla_runtime_patches) +use_fla_fused_swiglu: true # FLA Triton SwiGLU +use_fla_fused_rmsnorm: true # FLA fused RMSNorm +use_fla_fused_gated_norm: true # FLA FusedRMSNormGated for KDA gated output norm +use_fla_short_conv: true # FLA Triton causal_conv1d (no transpose round-trip) +fused_ce_mode: 1 # FLA FusedLinearCrossEntropyLoss (chunked, no full logits tensor) +fused_ce_chunks: 32 # Chunk count for FLA fused CE +# Only if you want bit-identical iter-1 batch ordering: +use_fla_data: true +fla_cache_dir: /home//Primus/data/huggingface +``` + +Legacy (still supported, env-var wins over YAML when set): + +```bash +export PRIMUS_FUSED_CE=1 # FLA FusedLinearCrossEntropyLoss (chunked, no full logits tensor) +export PRIMUS_FLA_SWIGLU=1 # FLA Triton SwiGLU +export PRIMUS_FLA_NORM=1 # FLA fused RMSNorm +export PRIMUS_FLA_CONV=1 # FLA Triton causal_conv1d (no transpose round-trip) +export PRIMUS_TORCH_OPTIM=1 # torch.optim.AdamW(fused=True), matches FLA exactly +# Only if you want bit-identical iter-1 batch ordering: +export PRIMUS_FLA_DATA=1 +export PRIMUS_FLA_CACHE_DIR=/home//Primus/data/huggingface +``` + +See [`kda-fla-parity.md`](kda-fla-parity.md) for the cost-of-each-flag +breakdown. + +### 5.4 Output layout + +Checkpoints land under Primus's `work_group/user_name/exp_name` template: + +``` +output/amd/root/zebra_llama_300M_kda_pure-pretrain/ +├── checkpoints/ +│ ├── iter_0001024/ +│ ├── iter_0002048/ +│ ├── iter_0003072/ +│ ├── iter_0004096/ +│ ├── iter_0004768/ ← FINAL (~4.5 GB) +│ │ └── mp_rank_00/ +│ │ └── model_optim_rng.pt +│ └── latest_checkpointed_iteration.txt → "4768" +└── logs/ + └── pre_trainer/ +``` + +`save_interval: 1024` in the YAML produces 4 mid-training checkpoints plus +the final one. + +--- + +## Step 6: Monitor and compare against FLA + +Megatron logs `iteration / elapsed_ms_inst / elapsed_ms_avg / TFLOP/s/GPU +/ tok/s/GPU / lm loss` every 100 steps. A representative tail looks like: + +``` +iteration 4700/ 4768 | elapsed time per iteration (ms): 1467.8/1466.1 | + TFLOP/s/GPU: 626.1 | tokens per GPU (tokens/s/GPU): 178596.5 | lm loss: 3.362445E+00 +``` + +To diff against FLA's reference log +(`/home//checkpoints/kda_pure_300M_10B/trainer_state.json`), divide +the FLA `loss` field by 8 (DeepSpeed reports sum-across-ranks): + +| iter | FLA / 8 | Primus | Δ % | Notes | +| ---- | ------- | ------- | ----------- | -------------------------------- | +| 1 | 11.9673 | 11.9669 | **−0.00 %** | bit-perfect | +| 100 | 7.7171 | 9.6903 | +25.6 % | warmup gap (peak) | +| 500 | 4.7349 | 4.8390 | +2.20 % | warmup closing | +| 1000 | 4.0357 | 4.0720 | +0.90 % | LR-warmup done | +| 2000 | 3.6009 | 3.6141 | +0.37 % | converged | +| 2600 | 3.5056 | 3.5047 | **−0.03 %** | first Primus < FLA crossover | +| 3000 | 3.4356 | 3.4571 | +0.63 % | matched | +| 3600 | 3.4107 | 3.4075 | **−0.09 %** | Primus slightly lower | +| 4000 | 3.3831 | 3.3861 | +0.09 % | identical | +| 4500 | 3.3603 | 3.3694 | +0.27 % | identical | +| 4700 | 3.3388 | 3.3624 | +0.71 % | identical | + +Final wall time on a healthy MI300X box: **6993 s vs FLA 7119 s** = +Primus 126 s faster. + +--- + +## Step 7: Convert checkpoint to HuggingFace format + +Use [`tools/hybrid/convert_kda_to_fla_hf.py`](../../tools/hybrid/convert_kda_to_fla_hf.py) +to translate the Megatron checkpoint into FLA's native +`KDAForCausalLM` HF format: + +```bash +python tools/hybrid/convert_kda_to_fla_hf.py \ + --checkpoint-path output/amd/root/zebra_llama_300M_kda_pure-pretrain/checkpoints/iter_0004768 \ + --output-dir output/kda_pure_300M_fla_hf \ + --config /home//flash-linear-attention/legacy/training/configs/kda_300M_pure.json \ + --tokenizer-src /home//checkpoints/kda_pure_300M_10B +``` + +What it does: + +- Reads `mp_rank_00/model_optim_rng.pt` and pulls the `model` state dict +- For each of the 12 FLA layers, pairs the alternating Megatron sublayers: + - KDA sublayer (even index) → FLA `model.layers..attn.*` + - MLP sublayer (odd index) → FLA `model.layers..mlp.*` +- Splits Primus's **fused** projections into FLA's separate ones: + - `mixer.in_proj.weight` (rows = `2·qk_dim + v_dim + 2·head_v_dim + + num_v_heads`) → `q_proj / k_proj / v_proj / f_proj.0 / g_proj.0 / b_proj` + - `mlp.linear_fc1.weight` (rows = `2·intermediate_size`) → + `gate_proj / up_proj` +- Preserves `A_log`, `dt_bias`, per-head `g_norm` (FLA's + `FusedRMSNormGated`), `o_proj`, `f_proj.1`, `g_proj.1`, embeddings, + tied `lm_head`, final norm +- Copies tokenizer files from `--tokenizer-src` into the output dir + +Output: + +``` +output/kda_pure_300M_fla_hf/ +├── config.json # KDAConfig, architectures=["KDAForCausalLM"] +├── model.safetensors # ~870 MB +└── tokenizer{,_config}.json + special_tokens_map.json +``` + +--- + +## Step 8: Verify conversion + +Quick smoke test in the container (with FLA importable): + +```bash +PYTHONPATH=/home//flash-linear-attention \ +python - <<'PY' +import torch +import fla # auto-registers "kda" with transformers.AutoConfig + +from transformers import AutoModelForCausalLM, AutoTokenizer + +ckpt = "output/kda_pure_300M_fla_hf" +tok = AutoTokenizer.from_pretrained(ckpt) +model = AutoModelForCausalLM.from_pretrained( + ckpt, trust_remote_code=True, torch_dtype=torch.bfloat16 +).cuda().eval() + +for prompt in [ + "The capital of France is", + "Once upon a time, there was a small", + "The first law of thermodynamics states that", +]: + inp = tok(prompt, return_tensors="pt").to("cuda") + with torch.no_grad(): + out = model.generate(**inp, max_new_tokens=40, do_sample=False) + print("---"); print(tok.decode(out[0], skip_special_tokens=True)) +PY +``` + +**Expected output** for a healthy 300 M-on-10 B model: grammatical but +repetitive English (canonical small-undertrained-LM failure mode under +greedy decoding with no repetition penalty). Knowing "capital of France" +→ "Paris" is the standard sanity-check pass. + +If `AutoConfig` raises `model type kda not recognized`, FLA was not +imported before `AutoModelForCausalLM`. Either prepend +`PYTHONPATH=/home//flash-linear-attention` or run +`pip install -e /home//flash-linear-attention` so the +auto-registration in `fla/models/kda/__init__.py` fires on import. + +--- + +## Step 9: Run lm-eval-harness benchmarks + +Use [`tools/hybrid/eval_kda_lm_eval.py`](../../tools/hybrid/eval_kda_lm_eval.py), which +imports `fla` first (so `AutoConfig` recognizes the `kda` model type) and +patches `KDAForCausalLM.__init__` / `KDAModel.__init__` to accept the +`dtype` kwarg that `transformers ≥ 4.55` passes internally. + +**Do not** invoke `lm_eval --model hf ...` directly — `AutoConfig.from_pretrained` +will fail with `model type kda not recognized`. + +### 9.1 Evaluate the Primus checkpoint (~15–30 min on one MI300X) + +```bash +mkdir -p output/kda_pure_300M_eval_results_primus + +PYTHONPATH=/home//flash-linear-attention \ +HIP_VISIBLE_DEVICES=0 \ +TOKENIZERS_PARALLELISM=false \ +python tools/hybrid/eval_kda_lm_eval.py \ + --model hf \ + --model_args pretrained=output/kda_pure_300M_fla_hf,dtype=bfloat16,trust_remote_code=True,tokenizer=meta-llama/Llama-3.2-1B \ + --tasks arc_easy,arc_challenge,hellaswag,openbookqa,piqa,winogrande,mmlu,race \ + --batch_size auto \ + --output_path output/kda_pure_300M_eval_results_primus \ + 2>&1 | tee output/kda_pure_300M_eval_results_primus/lm_eval.log +``` + +### 9.2 Evaluate the FLA reference checkpoint (apples-to-apples) + +```bash +mkdir -p output/kda_pure_300M_eval_results_fla + +PYTHONPATH=/home//flash-linear-attention \ +HIP_VISIBLE_DEVICES=1 \ +TOKENIZERS_PARALLELISM=false \ +python tools/hybrid/eval_kda_lm_eval.py \ + --model hf \ + --model_args pretrained=/home//checkpoints/kda_pure_300M_10B,dtype=bfloat16,trust_remote_code=True,tokenizer=meta-llama/Llama-3.2-1B \ + --tasks arc_easy,arc_challenge,hellaswag,openbookqa,piqa,winogrande,mmlu,race \ + --batch_size auto \ + --output_path output/kda_pure_300M_eval_results_fla \ + 2>&1 | tee output/kda_pure_300M_eval_results_fla/lm_eval.log +``` + +### 9.3 Diff the two result JSONs + +```bash +python - <<'PY' +import json, glob +def load_latest(d): + return json.load(open(sorted(glob.glob(f"{d}/**/results_*.json", recursive=True))[-1])) +fla = load_latest("output/kda_pure_300M_eval_results_fla") +primus = load_latest("output/kda_pure_300M_eval_results_primus") +print(f"{'task':<18} {'FLA':>8} {'Primus':>8} {'Δ':>+8}") +for task in sorted(set(fla['results']) & set(primus['results'])): + for k in ('acc,none', 'acc_norm,none'): + if k in fla['results'][task] and k in primus['results'][task]: + f, p = fla['results'][task][k], primus['results'][task][k] + print(f"{task[:17]:<18} {f:>8.4f} {p:>8.4f} {p-f:>+8.4f} ({k})") +PY +``` + +**Measured result** (validated on `tw006`, this branch). The `Random` +column is `100 / num_choices` for the lm-eval task — anything above it +means the model learned something: + +| Task | Metric | Random | FLA | Primus | Δ (Primus − FLA) | +|--------------------------|------------|-------:|-------:|-------:|-----------------:| +| arc_challenge | acc_norm | 25.00 | 25.17 | 25.00 | −0.17 pp | +| arc_easy | acc | 25.00 | 48.78 | 47.94 | −0.84 pp | +| arc_easy | acc_norm | 25.00 | 42.76 | 43.39 | +0.63 pp | +| hellaswag | acc_norm | 25.00 | 29.16 | 29.18 | +0.02 pp | +| openbookqa | acc_norm | 25.00 | 30.40 | 29.00 | −1.40 pp | +| piqa | acc_norm | 50.00 | 60.99 | 60.34 | −0.65 pp | +| winogrande | acc | 50.00 | 51.85 | 52.72 | **+0.87 pp** | +| mmlu (aggregate) | acc | 25.00 | 22.88 | 23.12 | +0.24 pp | +| race | acc | 25.00 | 25.07 | 25.45 | +0.38 pp | +| **mean absolute Δ** | | | | | **0.58 pp** | + +Every task within ±1.4 pp — consistent with the 0.49% loss delta at the +end of training. mmlu / race / arc_challenge are at random-chance for +*both* stacks (300 M params + 10 B tokens is below the threshold those +benchmarks need to lift above noise). + +--- + +## Configs and tools used + +``` +docs/04-technical-guides/hybrid-models/ +├── kda-guide.md ← this file +└── kda-fla-parity.md ← deep-dive on every change +examples/megatron/configs/MI300X/ +└── zebra_llama_300M_kda_pure-pretrain.yaml ← training config +primus/configs/models/megatron/ +└── zebra_llama_300M_kda_pure.yaml ← architecture-only config +primus/backends/megatron/core/models/hybrid/ +├── kimi_delta_attention.py ← FLA-aligned mixer (fused in_proj, FLA Triton paths) +├── kimi_delta_attention_layer.py ← eps propagation, optional pre-norm +└── hybrid_mamba_mla_layer_specs.py ← kda_hybrid_stack_spec_no_te +primus/backends/megatron/patches/ ← same 6 patches as GDN (Primus patch system, shared) +├── gdn_config_patches.py ← registers use_fla_triton_kda + fusion flags + hybrid init +├── mamba_fused_ce_patches.py ← FLA fused cross-entropy for MambaModel +├── torch_fused_adam_patches.py ← PRIMUS_TORCH_OPTIM opt-in +├── mlp_fla_swiglu_patches.py ← FLA Triton SwiGLU for MLP +├── torch_norm_fla_rmsnorm_patches.py ← FLA RMSNorm for WrappedTorchNorm +├── fla_runtime_patches.py ← resolves PRIMUS_FLA_* knobs onto args +└── mamba_fla_data_patches.py ← FLA-order dataset shim wiring +tools/hybrid/ +├── patch_fla_triton_autotune_hang.sh ← MI300X FLA Triton autotune-hang workaround +├── convert_fla_to_megatron.py ← FLA Arrow → Megatron .bin/.idx (shared) +├── fla_order_dataset.py ← FLA-order dataset shim (shared) +├── convert_fla_kda_init_to_megatron.py ← FLA HF init → Megatron sharded ckpt +├── convert_kda_to_fla_hf.py ← Megatron sharded ckpt → FLA HF +└── eval_kda_lm_eval.py ← lm-eval wrapper (registers KDA) +``` + +--- + +## Troubleshooting + +### `KeyError: 'kda'` at `AutoModelForCausalLM.from_pretrained` + +You imported `transformers` before `fla` (or didn't import `fla` at all). +`fla/models/kda/__init__.py` runs +`AutoConfig.register(KDAConfig.model_type, KDAConfig, exist_ok=True)` +on import. Either: + +- Prepend `PYTHONPATH=/home//flash-linear-attention` and `import fla` + in your script BEFORE the `transformers` import, OR +- `pip install -e /home//flash-linear-attention` once and forget + about `PYTHONPATH`, OR +- Use the wrapper: `python tools/hybrid/eval_kda_lm_eval.py ...` + +### Conversion: `KeyError: 'decoder.layers.0.mixer.in_proj.weight'` + +You trained with an older code branch that still had six separate +projections. Either re-train with the current fused-in_proj branch or +patch the converter to read the unfused `q_proj_weight`/`k_proj_weight`/… +keys (see git history of `tools/hybrid/convert_kda_to_fla_hf.py`). + +### Iter 1 loss ~12.05 instead of ~11.97 + +The `layernorm_epsilon: 1.0e-6` override is being silently overwritten by +the `TransformerConfig` default of `1e-5`. Confirm it's in the *training* +YAML's `overrides:` block (not just the model YAML). + +### Iter 1 loss not bit-matching FLA but converges fine + +You probably didn't load the FLA-init checkpoint (Step 4) or didn't set +`PRIMUS_FLA_DATA=1`. Without either, the first batch differs (Megatron +shuffler vs HF `DistributedSampler`) and the per-parameter `nn.init.normal_` +draw order differs (Megatron traverses Primus's fused `in_proj`, FLA +traverses 6 separate `nn.Linear` modules). The gap disappears by iter +~2000 even without either fix. + +### Loss is +0.2–0.4 above FLA across the whole run (with FLA-init loaded) + +You probably have `use_fla_kda_in_kernel_gate: false` or +`use_fla_fused_norm_gated: false`. Those toggles select the bit-identical- +to-old-FLA `fused_kda_gate` + `_apply_gated_norm` paths, which run the +gate compute in fp32 (slightly different rounding than the in-kernel bf16 +accumulator). Set both to `true` to match the current FLA reference. + +### Per-iter time ≫ 1500 ms + +Most likely you have `PRIMUS_FLA_CONV=0`. The Tri-Dao `causal_conv1d_fn` +on ROCm requires `[B, D, T]` layout, so each iteration pays two +`transpose+contiguous` copies of the (B, qk_dim·2 + v_dim, T) tensor — +about 35 ms wasted per iter at micro_batch=128. Set `PRIMUS_FLA_CONV=1` +to switch to FLA's Triton `causal_conv1d` (accepts `[B, T, D]` natively). + +### Out-of-memory at iter 1 + +Two common culprits: + +1. `PYTORCH_ALLOC_CONF=expandable_segments:True` is unset — set it. +2. `q.contiguous()/k.contiguous()/v.contiguous()` removed from KDA forward + — the Triton kernel will allocate its own copies while autograd still + pins the original views, doubling Q/K/V activation memory. Restore + the explicit contiguous calls (see `kimi_delta_attention.py` around + the `chunk_kda` call site). + +### Eval truncation warnings + +Some samples exceed the model's `max_position_embeddings = 2048`. Add +`max_length=1024` to `--model_args` if it bothers you; it only +meaningfully affects RACE. + +--- + +## See also + +- [`docs/04-technical-guides/hybrid-models/README.md`](README.md) — full Zebra-Llama family + overview (1 B / 3 B / 8 B Mamba+MLA, KDA variants) +- [`docs/04-technical-guides/hybrid-models/gdn-guide.md`](gdn-guide.md) — the GDN companion + recipe (shares Megatron patches and dataset shim with this one) +- [`kda-fla-parity.md`](kda-fla-parity.md) — exhaustive list of + code/config/runtime changes that made KDA parity possible +- FLA upstream: [https://github.com/fla-org/flash-linear-attention](https://github.com/fla-org/flash-linear-attention) diff --git a/docs/04-technical-guides/logging-and-experiment-tracking.md b/docs/04-technical-guides/logging-and-experiment-tracking.md new file mode 100644 index 000000000..fd88ca9e6 --- /dev/null +++ b/docs/04-technical-guides/logging-and-experiment-tracking.md @@ -0,0 +1,163 @@ +# Logging and experiment tracking + +This guide covers how Primus emits training metrics and logs, and how to wire up the supported experiment trackers—**TensorBoard**, **Weights & Biases (WandB)**, and **MLflow** (including Databricks-hosted MLflow)—across the Megatron and TorchTitan backends. Parameters are grounded in `primus/configs/modules/megatron/trainer_base.yaml`, `primus_megatron_module.yaml`, and `primus/configs/modules/torchtitan/pre_trainer.yaml`. + +For an operations-oriented overview, see [Monitoring and logging](../05-operations/monitoring-logging.md). For required credentials/keys, see [Environment variables](../03-configuration-reference/environment-variables.md). + +--- + +## 1. Tracker toggles at a glance (Megatron) + +All three trackers are **opt-in** and disabled by default (`primus/configs/modules/megatron/primus_megatron_module.yaml`): + +```yaml +disable_tensorboard: true +disable_wandb: true +disable_mlflow: true +``` + +Set the relevant `disable_*` to `false` to enable a tracker. Primus performs validation checks at startup—e.g. it warns if WandB is enabled but `WANDB_API_KEY` is unset (`primus/backends/megatron/patches/args/wandb_config_patches.py`). MLflow logging is initialized in `primus/backends/megatron/training/global_vars.py`; Databricks-hosted MLflow additionally requires `DATABRICKS_HOST` (read by the `mlflow` client). + +--- + +## 2. Console and step logging (Megatron) + +Core logging cadence and content (`trainer_base.yaml`, overridden by `pre_trainer.yaml`): + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `log_interval` | `100` (`pre_trainer.yaml` sets `1`) | Steps between log lines. | +| `log_throughput` | `false` (`pre_trainer.yaml` sets `true`) | Log tokens/s and TFLOP/s throughput. | +| `log_progress` | `false` | Log progress/ETA. | +| `log_params_norm` | `false` | Log parameter L2 norm. | +| `log_num_zeros_in_grad` | `false` | Log gradient sparsity. | +| `log_avg_skip_iterations` | `2` | Warmup iterations excluded from averages. | +| `log_avg_reset_interval` | `10` | Reset window for running averages. | +| `timing_log_level` | `0` | Verbosity of timer breakdowns. | +| `timing_log_option` | `minmax` | Timer aggregation across ranks. | +| `logging_level` | `null` | Python logging level override. | + +--- + +## 3. TensorBoard (Megatron) + +Enable with `disable_tensorboard: false` and set an output directory: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `tensorboard_dir` | `null` | Output directory for event files (**required** when enabled). | +| `tensorboard_log_interval` | `1` | Steps between TensorBoard writes. | +| `tensorboard_queue_size` | `1000` | Event queue size before flush. | +| `log_learning_rate_to_tensorboard` | `true` | Log LR. | +| `log_loss_scale_to_tensorboard` | `true` | Log loss scale (mixed precision). | +| `log_timers_to_tensorboard` | `false` | Log per-stage timers. | +| `log_batch_size_to_tensorboard` | `false` | Log batch size. | +| `log_memory_to_tensorboard` | `false` | Log GPU memory. | +| `log_world_size_to_tensorboard` | `false` | Log world size. | +| `log_validation_ppl_to_tensorboard` | `false` | Log validation perplexity. | + +--- + +## 4. Weights and biases (Megatron) + +Enable with `disable_wandb: false`. Configuration: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `wandb_project` | `null` | WandB project name. | +| `wandb_exp_name` | `null` | Run/experiment name. | +| `wandb_save_dir` | `null` | Local directory for WandB files. | +| `wandb_entity` | `null` | Team/entity. | + +**Credentials** (see [Environment variables](../03-configuration-reference/environment-variables.md)): + +```bash +export WANDB_API_KEY=... # required when WandB is enabled +export WANDB_PROJECT=... # optional +export WANDB_RUN_NAME=... # optional +export WANDB_TEAM=... # optional (entity) +``` + +`WANDB_API_KEY` is on the container passthrough allowlist (`runner/.primus.yaml`), so it propagates into the training container. + +--- + +## 5. MLflow (Megatron) + +Enable with `disable_mlflow: false`. Run identification and upload behavior: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `mlflow_run_name` | `null` | MLflow run name. | +| `mlflow_experiment_name` | `null` | MLflow experiment name. | +| `mlflow_upload_traces` | `false` | Upload profiler trace files. | +| `mlflow_upload_logs` | `false` | Upload training log files. | +| `mlflow_upload_performance_metrics` | `false` | Upload the comprehensive perf/memory/utilization metric set (implicitly enables throughput calc). | +| `mlflow_upload_tracelens_report` | `false` | Generate + upload TraceLens reports (see [Profiling & observability](./profiling-and-observability.md)). | + +**Credentials and endpoints:** + +```bash +export MLFLOW_TRACKING_URI=... # tracking server URI +export MLFLOW_REGISTRY_URI=... # optional model registry +# Databricks-hosted MLflow: +export DATABRICKS_HOST=... # checked at startup when MLflow is enabled +export DATABRICKS_TOKEN=... +``` + +> The `mlflow_upload_*` flags are designed so MLflow stays opt-in: they only take effect when `disable_mlflow: false`. + +--- + +## 6. One-logger (Megatron) + +NVIDIA One-Logger telemetry is enabled by default in `trainer_base.yaml`: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `enable_one_logger` | `true` | Enable One-Logger collection. | +| `one_logger_project` | `megatron-lm` | Project tag. | +| `one_logger_run_name` | `null` | Run name. | +| `one_logger_async` | `false` | Async upload. | +| `app_tag_run_name` / `app_tag_run_version` | `null` / `0.0.0` | Application tags. | + +--- + +## 7. Metrics and logging (TorchTitan) + +Configured under `metrics:` in `primus/configs/modules/torchtitan/pre_trainer.yaml`: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `enable_tensorboard` | `false` | Enable TensorBoard logging. | +| `enable_wandb` | `false` | Enable WandB logging. | +| `log_freq` | `10` | Steps between metric logs. | +| `save_tb_folder` | `tb` | TensorBoard output subfolder. | +| `save_for_all_ranks` | `false` | Write metrics from every rank (default: rank 0 only). | +| `disable_color_printing` | `false` | Disable ANSI colors in console output. | + +TorchTitan reads WandB settings from the environment (`WANDB_PROJECT`, `WANDB_RUN_NAME`, `WANDB_TEAM`) via `primus/backends/torchtitan/patches/wandb_patches.py` and `third_party/torchtitan/torchtitan/components/metrics.py`. + +--- + +## 8. Logging (MaxText) + +MaxText logging cadence is controlled by `log_period` (`primus/configs/modules/maxtext/pre_trainer.yaml`, default `100`). See [MaxText parameters](../03-configuration-reference/maxtext-parameters.md). + +--- + +## 9. Recommended setup + +1. **Local-only:** enable TensorBoard (`disable_tensorboard: false`, set `tensorboard_dir`)—no credentials required. +2. **Team tracking:** enable WandB (`disable_wandb: false`) + export `WANDB_API_KEY` and `wandb_project`/`wandb_entity`. +3. **Enterprise / scaling studies:** enable MLflow (`disable_mlflow: false`) + `MLFLOW_TRACKING_URI` (or Databricks host/token), and turn on `mlflow_upload_performance_metrics` for throughput/memory/utilization dashboards. +4. **Keep `WANDB_API_KEY` and tokens out of YAML**—pass them as environment variables (allowlisted for container passthrough). See [Security](../05-operations/security.md). + +--- + +## Related documentation + +- [Monitoring and logging](../05-operations/monitoring-logging.md)—operational view of trackers. +- [Profiling & observability](./profiling-and-observability.md)—traces, TraceLens, perf metrics. +- [Environment variables](../03-configuration-reference/environment-variables.md)—credentials and passthrough. +- [Megatron parameters](../03-configuration-reference/megatron-parameters.md) and [TorchTitan parameters](../03-configuration-reference/torchtitan-parameters.md). diff --git a/docs/04-technical-guides/mega-moe.md b/docs/04-technical-guides/mega-moe.md new file mode 100644 index 000000000..2b5ec8c5e --- /dev/null +++ b/docs/04-technical-guides/mega-moe.md @@ -0,0 +1,238 @@ +# MegaMoE + +MegaMoE is a **FlyDSL**-based fused MoE layer that replaces Megatron's native `MoELayer`. It fuses +the expert-parallel all-to-all communication into the grouped GEMMs via two fused kernels: + +- **dispatch grouped GEMM** (`dispatch_grouped_gemm`): fuses token dispatch (all-to-all) into the + L1 grouped GEMM. +- **grouped GEMM combine** (`grouped_gemm_combine`): fuses the L2 grouped GEMM into combine + (all-to-all) + weighted reduce. + +Together with a **fused router** (score function + group-limited top-k + aux score) and the +intermediate SwiGLU, the full expert path is `dispatch_grouped_gemm → SwiGLU → grouped_gemm_combine`; +the load-balancing aux loss is computed internally and returned. Runtime target is **EP-only +(TP=1) + bf16**. + +## Prerequisites + +- **Runtime**: ROCm ≥ 7.0, Python ≥ 3.10, PyTorch ≥ 2.6.0 (ROCm build); gfx950. The DeepEP + baseline additionally needs the optional **rocSHMEM**. Image `rocm/primus:v26.3` is recommended. +- **Primus-Turbo with MegaMoE**: MegaMoE requires Primus-Turbo + (`https://github.com/AMD-AGI/Primus-Turbo.git`) at commit + **`9b5d3092efcbc087657b233d8e9ae662cee6ec6b` or newer** on `main`. The default image does not ship + this kernel, so Primus-Turbo must be rebuilt from source. See upstream + [main README](https://github.com/AMD-AGI/Primus-Turbo/blob/main/README.md) and + [MegaMoE doc](https://github.com/AMD-AGI/Primus-Turbo/blob/main/docs/README_Mega_MoE.md) + for build details. Install it via the rebuild hook: + +**Primus rebuild hook (build from source).** The system hook +`runner/helpers/hooks/00_rebuild_primus_turbo.sh` clones + builds + installs the given ref before +the training command; each node builds in a node-local dir so multi-node runs avoid shared-fs +conflicts. Point it at a commit for reproducibility, or at `main` to track the latest code: + +```bash +export REBUILD_PRIMUS_TURBO=1 # trigger the hook +export PRIMUS_TURBO_REF=9b5d3092efcbc087657b233d8e9ae662cee6ec6b # min required commit (or main) +export GPU_ARCHS="gfx950" # build only target arch (multiple: semicolon-separated) +# Optional: custom build dir (default /tmp/primus_turbo_) +# export PRIMUS_TURBO_BUILD_DIR=/tmp/primus_turbo_build +``` + +## Design: two stages + weight modules (for DDP overlap) + +The expert path could be exposed as a *single* fused op taking both `w1` and `w2` +(`fused_mega_moe(x, topk_idx, topk_weights, w1, w2, group)` still exists in Primus-Turbo). Primus +instead drives it as **two stages**, each owning one weight, wrapped in **two tiny weight modules** +(`primus/backends/megatron/core/extensions/mega_moe.py`): + +``` +MegaMoEExperts +├── fc1_weight : MegaMoEWeightModule # w1 [g, 2I, H] gate+up ; forward() -> w1 +└── fc2_weight : MegaMoEWeightModule # w2 [g, H, I] down ; forward() -> w2 + + +FORWARD (in order) BACKWARD (in order) +────────────────────────────────────── ────────────────────────────────────── +w1 = fc1_weight() stage2.backward -> dW2 + hook: all-gather(w1), wait + hook on fc2_weight +stage1: dispatch + GEMM1 <──┐ -> reduce-scatter(dW2) ──┐ + │ overlap │ overlap +w2 = fc2_weight() │ stage1.backward -> dW1 ──┘ + hook: all-gather(w2) ───────────┘ + hook on fc1_weight +stage2: SwiGLU + GEMM2 + combine -> reduce-scatter(dW1) + (waits for w2 here) (overlaps the next layer) +``` + +`MegaMoEWeightModule` holds a single `torch.nn.Parameter` and its `forward()` just returns it. It +computes nothing — its only job is to be a **hook site** for the two collectives Megatron's +distributed optimizer wants to overlap, both of which are driven at module / parameter granularity: + +- **Parameter all-gather** (`overlap_param_gather`) rides the *forward pre-hook*, which is per + module. With a single call site taking `(w1, w2)` both gathers must land before any compute; split, + `w2`'s gather is issued at `fc2_weight` and overlaps stage1. +- **Gradient reduce-scatter** (`overlap_grad_reduce`) rides the *grad hook*, which fires when a + parameter's `.grad` appears. One fused autograd node emits `dW1` and `dW2` together at the end of + the layer backward; split, `dW2` lands early and its reduce-scatter hides under stage1's backward. + +The split is purely at the Python/autograd level — the kernels are unchanged. + +## Configuration + +Enable the fused MegaMoE layer in the training config: + +```yaml +enable_primus_turbo: true +use_turbo_mega_moe: true # MegaMoE layer replacement (EP-only / TP=1 / bf16) +``` + +The patch is applied only when **all** of these hold: `enable_primus_turbo=True`, +`use_turbo_mega_moe=True`, `tensor_model_parallel_size==1`, `params_dtype==bf16`, and an EP process +group exists. + +The following model settings are **required** — MegaMoE asserts on anything else: + +```yaml +tensor_model_parallel_size: 1 # EP-only, TP=1 +add_bias_linear: false # no bias in linear layers +# gated SwiGLU + SiLU activation +``` + +Unsupported (each raises an error): sequence-level / global aux loss, z-loss, sinkhorn, and input +jitter (only the standard `aux_loss` is supported); aux-loss-free expert bias +(`enable_expert_bias=True` raises `NotImplementedError`). + +### Router force load balancing (`moe_router_force_load_balancing_type`) + +The benchmark config sets `moe_router_force_load_balancing: true`, which discards the real router +decision and forces every expert to receive a similar number of tokens — this removes run-to-run +expert-imbalance noise so throughput numbers are comparable. `moe_router_force_load_balancing_type` +selects *how* the balancing is done (it only has an effect when force load balancing is on): + +| value | behavior | +| --- | --- | +| `even` (Primus default) | Deterministic round-robin `(token_idx * topk + k) % num_experts`. Per-expert token counts are exactly equal **and identical every step**, so the grouped-GEMM shapes (`M_total`, per-expert `M`) never change. | +| `uniform` | Megatron-LM's original behavior: the logits are replaced with random values before routing, so token counts are balanced only *statistically* and fluctuate step to step, like real routing. | + +**Use `uniform` for benchmarking.** `even` produces perfectly constant, aligned per-expert shapes +that the non-fused (DeepEP / grouped-GEMM) baseline benefits from disproportionately — no autotune +or shape-recompile churn, no padding waste — while MegaMoE is designed to absorb ragged, varying +token counts. Measuring under `even` therefore understates MegaMoE's gain; `uniform` keeps the +balancing (for reproducibility) but preserves the step-to-step shape variation of real training. + +The examples below use the rebuild hook (`REBUILD_PRIMUS_TURBO=1 +PRIMUS_TURBO_REF=9b5d3092efcbc087657b233d8e9ae662cee6ec6b`) to build Primus-Turbo from source. + +### Example 1 — single-node EP8, 4 layers (`run_pretrain_cli.sh`) + +1 node × 8 GPUs, `TP=1 / PP=1 / EP=8`, DeepSeek-V3 BF16, `GBS = MBS*GPUS*GA = 2*8*64 = 1024`. +Minimal fused-MegaMoE run from `Primus/`: + +```bash +#!/bin/bash +set -e + +# Model config +export EXP=examples/megatron/configs/MI355X/deepseek_v3-BF16-pretrain.yaml +# Build Primus-Turbo from source before training (hook) +export REBUILD_PRIMUS_TURBO=1 +export PRIMUS_TURBO_REF=9b5d3092efcbc087657b233d8e9ae662cee6ec6b +export GPU_ARCHS=gfx950 + +# Parallelism (EP-only) + fused MegaMoE +bash examples/run_pretrain_cli.sh \ + --num_layers 4 \ + --micro_batch_size 2 \ + --global_batch_size 1024 \ + --tensor_model_parallel_size 1 \ + --pipeline_model_parallel_size 1 \ + --expert_model_parallel_size 8 \ + --moe_layer_freq 1 \ + --moe_shared_expert_intermediate_size None \ + --pipeline_model_parallel_layout null \ + --recompute_granularity null \ + --recompute_num_layers 0 \ + --recompute_layer_ids null \ + --moe_router_force_load_balancing_type uniform \ + --enable_primus_turbo True \ + --use_turbo_mega_moe True \ + --mock_data True +``` + + +### Example 2 — single-node EP8 with full CUDA graph + +Same geometry as Example 1, plus TE full-scope CUDA graph capture +(`--external_cuda_graph True` maps to `cuda_graph_impl="transformer_engine"`; scope `full` is +normalized to `[]` by Megatron, i.e. capture the whole layer). MegaMoE is fully sync-free — no +device-to-host sync or CPU-side wait in the expert path — so it captures cleanly and is +`torch.compile`-friendly. + +```bash +#!/bin/bash +set -e + +export EXP=examples/megatron/configs/MI355X/deepseek_v3-BF16-pretrain.yaml +export REBUILD_PRIMUS_TURBO=1 +export PRIMUS_TURBO_REF=9b5d3092efcbc087657b233d8e9ae662cee6ec6b +export GPU_ARCHS=gfx950 + +bash examples/run_pretrain_cli.sh \ + --num_layers 4 \ + --micro_batch_size 2 \ + --global_batch_size 1024 \ + --train_iters 15 \ + --tensor_model_parallel_size 1 \ + --pipeline_model_parallel_size 1 \ + --expert_model_parallel_size 8 \ + --moe_layer_freq 1 \ + --moe_shared_expert_intermediate_size None \ + --pipeline_model_parallel_layout null \ + --recompute_granularity null \ + --recompute_num_layers 0 \ + --recompute_layer_ids null \ + --moe_router_force_load_balancing_type uniform \ + --external_cuda_graph True \ + --cuda_graph_scope full \ + --cuda_graph_warmup_steps 3 \ + --enable_primus_turbo True \ + --use_turbo_mega_moe True \ + --mock_data True +``` + +### Example 3 — 8-node EP8/PP8 + +8 nodes × 8 GPUs = 64 GPU, `TP=1 / PP=8 / EP=8` → `DP = 64/(TP*PP) = 8`, +`GBS = MBS*DP*GA = 2*8*64 = 1024`. + +```bash +set -e + +# Cluster geometry (8 nodes x 8 GPUs = 64 GPUs) +export NNODES=8 + +export USING_AINIC=1 + +# Toggle the fused MegaMoE layer + model config +export EXP=examples/megatron/configs/MI355X/deepseek_v3-BF16-pretrain.yaml + +bash examples/run_slurm_pretrain_cli.sh \ + --train_iters 15 \ + --micro_batch_size 2 \ + --global_batch_size 1024 \ + --tensor_model_parallel_size 1 \ + --pipeline_model_parallel_size 8 \ + --pipeline_model_parallel_layout "Ett|tttt|tttt|tttt|tttt|tttt|tttt|tttt|tttt|tttt|tttt|tttt|tttt|tttt|tttt|tttL" \ + --expert_model_parallel_size 8 \ + --moe_shared_expert_intermediate_size None \ + --mtp_num_layers 0 \ + --recompute_granularity full \ + --recompute_method uniform \ + --recompute_num_layers 1 \ + --recompute_layer_ids null \ + --moe_router_force_load_balancing_type uniform \ + --enable_primus_turbo True \ + --use_turbo_mega_moe True \ + --mock_data True +``` diff --git a/docs/04-technical-guides/moe-training.md b/docs/04-technical-guides/moe-training.md new file mode 100644 index 000000000..b31af0b2c --- /dev/null +++ b/docs/04-technical-guides/moe-training.md @@ -0,0 +1,193 @@ +# MoE training deep-dive + +This guide covers Mixture-of-Experts (MoE) training in Primus on AMD Instinct GPUs: the bottlenecks unique to sparse models, the Primus/Primus-Turbo optimizations that address them, and a model-by-model tuning walkthrough. It is adapted from the AMD blog [MoE Training Best Practices on AMD GPU](https://rocm.blogs.amd.com/software-tools-optimization/primus-moe-package/README.html) (`examples/moe_package/README.md`) and grounded in the actual Primus configs and run scripts. + +All flags shown here are the **real CLI/YAML keys** used by `examples/moe_package/run_*_pretrain_mi355x.sh` and the Megatron module configs (`primus/configs/modules/megatron/`). The Primus-Turbo MoE optimizations in this guide (DeepEP, sync-free MoE, Turbo grouped GEMM) are **Megatron-backend** features. TorchTitan also supports MoE via expert parallelism (`expert_parallel_degree`, `expert_tensor_parallel_degree`), but its tuning is out of scope here. + +--- + +## 1. Why MoE training is different + +MoE scales model capacity by routing each token through a small subset of "expert" sub-networks instead of activating the whole network. A gating/router picks the top-k experts per token, so a model can hold many billions of parameters while only a fraction are active per token. + +This sparsity creates performance challenges that dense models do not have: + +- **Grouped GEMM overhead**—each expert is a separate GEMM; naive multi-stream execution leaves scheduling gaps. +- **All-to-all (A2A) communication**—token dispatch/combine across expert-parallel ranks can dominate runtime, especially with `EP >= 8` and multi-node. +- **CPU sync & launch delays**—dynamic shapes (token counts per expert) force device-to-host syncs that stall the kernel launch queue. +- **Too many small kernels**—fine-grained MoE ops stress the CPU launch path. +- **Pipeline load imbalance**—uneven layer distribution across pipeline stages quietly degrades throughput. +- **Memory pressure**—activations dominate memory at large scale, forcing recomputation. + +--- + +## 2. Representative model configs + +Primus ships Megatron model presets for DeepSeek-style MoE models plus two ultra-large research configs: + +| Model | Total / Active params | Model config (`primus/configs/models/megatron/`) | +|-------|-----------------------|--------------------------------------------------| +| DeepSeek-V2-Lite | 16B / 2.4B | `deepseek_v2_lite.yaml` | +| DeepSeek-V2 | 236B / 21B | `deepseek_v2.yaml` | +| DeepSeek-V3 | 671B / 37B | `deepseek_v3.yaml` | +| MoE-1T | 1T / 44B | `moe_1T.yaml` | +| MoE-2T | 2T / 80B | `moe_2T.yaml` | + +Ready-to-run pretrain scripts live in `examples/moe_package/`, e.g.: + +- `examples/moe_package/run_deepseek_v2_lite_pretrain_mi355x.sh` +- `examples/moe_package/run_deepseek_v2_pretrain_mi355x.sh` +- `examples/moe_package/run_deepseek_v3_pretrain_mi355x.sh` + +Each example script is a convenience wrapper: it sets environment + parallelism and selects an experiment YAML under `examples/moe_package/configs/`, then launches training. You can run the same training directly with the unified CLI, passing the experiment YAML with `--config` and the MoE feature toggles (Section 4) as overrides: + +```bash +# DeepSeek-V2-Lite baseline + DeepEP + sync-free + loss fusion + manual GC, via primus-cli +export ENABLE_NUMA_BINDING=1 HSA_KERNARG_POOL_SIZE=12582912 # feature 6 (env, not CLI flags) +./runner/primus-cli direct -- train pretrain \ + --config examples/moe_package/configs/MI355X/deepseek_v2_lite-pretrain-baseline.yaml \ + --enable_primus_turbo True \ + --use_turbo_deepep True --turbo_deepep_num_cu 64 --moe_router_dtype fp32 \ + --turbo_sync_free_moe_stage 1 \ + --cross_entropy_fusion_impl te --cross_entropy_loss_fusion True \ + --manual_gc True --manual_gc_interval 1 +``` + +Use `./runner/primus-cli slurm srun -N -- train pretrain --config ...` for multi-node. The feature tables below list the exact flags so you can compose your own command. (Optimizations that are environment variables—NUMA binding, `HSA_KERNARG_POOL_SIZE`, UCCL-EP—are exported before the command rather than passed as `--flags`.) + +--- + +## 3. Profiling and analysis workflow + +Diagnose before optimizing. The recommended order: + +1. **Torch Profiler**—capture operator times, memory, and GPU utilization. Enable through the Megatron profiling flags (`--profile`, `--use_pytorch_profiler`, `--profile_step_start`, `--profile_step_end`, `--disable_profiler_activity_cpu`). Load the trace in [Perfetto](https://ui.perfetto.dev/) to inspect CPU/GPU overlap, launch delays, and idle gaps. See [Profiling & observability](./profiling-and-observability.md). +2. **TraceLens**—AMD's automated trace analyzer for hierarchical breakdowns, roofline/efficiency, communication-vs-sync separation, and trace diffing. Wired into Primus via `generate_tracelens_report` / `mlflow_upload_tracelens_report` (see the profiling guide). +3. **Memory projection**—model VRAM across params, gradients, activations, and optimizer state *before* launching, via `./primus-cli direct -- projection memory --config .yaml`. See [Projection](../02-user-guide/projection.md). +4. **Pipeline visualization**—dump pipeline schedule data (`--dump_pp_data true`) and render stage utilization with `tools/visualization/pp_vis/vis.py` to find bubbles and stage imbalance. + +--- + +## 4. Primus MoE optimizations + +The `examples/moe_package/run_*` scripts expose these as composable "MoE features." The table maps each feature to the **actual `--flags` (or environment variables)** you pass to `train pretrain`—the same toggles the example scripts set. + +| Feature | Flags (real keys) | What it does | +|---------|-------------------|--------------| +| Turbo attention | `--enable_primus_turbo True --use_turbo_attention True` | Optimized attention kernels (Primus-Turbo). | +| Turbo grouped GEMM | `--enable_primus_turbo True --use_turbo_grouped_gemm True` | Fused CK grouped GEMM processes all experts in one launch instead of multi-stream. | +| Loss fusion | `--cross_entropy_fusion_impl te --cross_entropy_loss_fusion True` | Fuses large-vocab loss into one kernel to cut memory + launch overhead. | +| DeepEP acceleration | `--enable_primus_turbo True --use_turbo_deepep True --turbo_deepep_num_cu 64 --turbo_deepep_use_comm_stream False --moe_shared_expert_overlap False --moe_router_dtype fp32` | GPU-side index calc + sync-free dispatch to cut redundant cross-node A2A traffic. | +| Sync-free MoE | `--enable_primus_turbo True --turbo_sync_free_moe_stage ` | Removes CPU D2H syncs across Router → Dispatcher → Permutation → GroupMLP. | +| NUMA binding | `export ENABLE_NUMA_BINDING=1` | Pins each GPU process to its NUMA socket for better memory bandwidth/stability. | +| HIP kernarg pool | `export HSA_KERNARG_POOL_SIZE=12582912` | Enlarges the kernel-argument pool (12 MB) to avoid launch stalls under many small kernels. | +| Manual GC | `--manual_gc True --manual_gc_interval 1` | Periodic host GC to remove iteration-time jitter on long runs. | +| UCCL-EP | `export USING_UEP=1` | Use the UCCL transport for DeepEP dispatch/combine (sets `PRIMUS_TURBO_MOE_DISPATCH_COMBINE_BACKEND=DEEP_EP` + UCCL network env). Requires the `uccl` and `deep_ep` packages. | + +> **Grouped GEMM keys.** Enable Turbo grouped GEMM for MoE with `use_turbo_grouped_gemm` (`--use_turbo_grouped_gemm True`). The older `use_turbo_grouped_mlp` alias has been **removed**—passing it now raises an assertion error (`use_turbo_grouped_mlp has been removed; please use use_turbo_grouped_gemm instead`). +> +> **Legacy path.** The legacy multi-stream grouped GEMM path is selected with `--moe_use_legacy_grouped_gemm True` (the scripts default `LEGACY_GG=True`). Turbo grouped GEMM is **incompatible** with the legacy path—set `--moe_use_legacy_grouped_gemm False` whenever `use_turbo_grouped_gemm` is enabled (Primus raises an error otherwise). + +### Sync-free MoE stages + +`turbo_sync_free_moe_stage` is a single knob with four levels (`0`–`3`, validated in `primus/backends/megatron/patches/args/rocm_arg_validation.py`). Each stage **auto-enables** a set of fusion flags: + +| Level | Auto-enabled flags | Behavior | +|-------|--------------------|----------| +| `0` (default) | — | Disabled—standard baseline. | +| `1` | `moe_use_fused_router_with_aux_score`, `moe_permute_fusion` | Sync-free **Router** + **Permutation** fusion. | +| `2` | stage 1 + `use_turbo_deepep`, `use_turbo_grouped_gemm` | Adds sync-free **DeepEP** dispatch and **Turbo grouped GEMM**. | +| `3` | stage 2 + `use_turbo_fused_act_with_probs` | Full sync-free pipeline (adds fused activation). Per the blog this **uses significantly more GPU memory**—only enable with headroom. | + +Requirements (enforced at startup): + +- All stages require `--enable_primus_turbo True`. +- Stages `2` and `3` require Turbo grouped GEMM and are therefore **incompatible with `--moe_use_legacy_grouped_gemm True`**. + +Practical guidance from the run scripts: + +- **MI355X:** `--turbo_sync_free_moe_stage 1` (compatible with the default legacy grouped GEMM path, since stage 1 does not enable Turbo grouped GEMM). +- **MI300X / MI325X:** the example scripts suggest stage `2` (with `--moe_shared_expert_overlap False --moe_router_dtype fp32`). Because stage 2 auto-enables Turbo grouped GEMM, you must also set `--moe_use_legacy_grouped_gemm False`. + +### Scheduling and memory features + +- **1F1B A2A overlap**—interleaves micro-batch N's expert communication with micro-batch N-1's backward compute on top of interleaved-1F1B pipeline parallelism, hiding A2A behind compute while preserving the bubble rate and roughly the same peak memory. +- **Arbitrary pipeline partition**—manual stage layout instead of automatic even splits, to balance per-stage memory/compute. Use the Megatron-core `--pipeline_model_parallel_layout` flag (as the DeepSeek-V3 script does) or the Primus `decoder_pipeline_manual_split_list` config key (`primus/configs/modules/megatron/primus_megatron_module.yaml`). +- **Selective layer recompute**—recompute specific transformer layers with `--recompute_layer_ids 0,1,2,3` (keep `RECOMPUTE_LAYERS=0` so this is the only recompute control), or full block recompute via `--recompute_granularity full --recompute_method block --recompute_num_layers N`. +- **MoE expert-parallel comm overlap**—`overlap_moe_expert_parallel_comm: true` (`trainer_base.yaml`). + +See [Performance tuning](./performance-tuning.md) for the full Primus-Turbo flag reference. + +--- + +## 5. Model-specific tuning + +### DeepSeek-V2-Lite (16B / 2.4B, 27 layers) + +A compute/memory-efficient variant ideal for high-throughput pretraining. AMD Instinct's large HBM (192 GB on MI300X, 288 GB on MI355X) lets you push **micro-batch size (MBS)** high to maximize throughput. + +Recommended optimization stack (matches `run_deepseek_v2_lite_pretrain_mi355x.sh`, where `MoE_Features=(3 4 5 6 7 8)`): + +1. Manual GC for stable iteration time. +2. Loss fusion for the large vocabulary. +3. DeepEP for A2A. +4. Sync-free mode (stage 1 on MI355X) to remove D2H syncs. +5. NUMA binding for CPU affinity (`ENABLE_NUMA_BINDING=1` + `HSA_KERNARG_POOL_SIZE`). +6. MBS scaling using the memory freed by the above (the blog reports peak memory dropping from ~99.8% to ~84.3% at MBS=12, enabling MBS=14). + +The script's default `MoE_Features=(3 4 5 6 7 8)` also enables feature `8` = UCCL-EP (see the feature table above). Default parallelism in the script: `TP=1 ETP=1 PP=1 EP=8 CP=1`, `MBS=14 GBS=896 SEQ=4096`. + +### DeepSeek-V2 (236B / 21B, 60 layers) + +Scale up with parallelism for max throughput across nodes. Recommended stack: + +1. Manual GC, 2) Loss fusion, 3) DeepEP, 4) NUMA binding, 5) Sync-free mode, plus **interleaved pipeline parallelism (VPP)** to cut the pipeline bubble ratio. Enabling VPP (`--num_virtual_stages_per_pipeline_rank > 1`) also improves sync-free mode effectiveness. + +Default parallelism in the script: `TP=1 ETP=1 PP=4 VPP=5 EP=8 CP=1` (interleaved PP), `SEQ=4096`. + +### 1T+ parameter models (MoE-1T / MoE-2T, 96 layers) + +Ultra-large training combines every advanced technique. Use **memory projection first**—at this scale **activations dominate memory**, not parameters/optimizer state. + +Findings from the blog's projections (768–1024 GPUs): + +- **Context parallelism (CP2)** roughly halves activation memory (~76 GB/GPU saved for 1T, ~131 GB/GPU for 2T)—the most effective single lever. +- **Increasing EP** (8→16) barely reduces memory but adds A2A time. +- **Increasing PP** (24→48) doesn't materially cut memory and raises pipeline bubbles + activation memory. + +Suggested configs: + +| Model | MI300X | MI355X | +|-------|--------|--------| +| MoE-1T | PP24 EP8 CP2 | PP24 EP8 (no checkpointing) | +| MoE-2T | PP24 EP16 CP2 | PP24 EP8 CP2 (benefits from larger DP) | + +**Pipeline bubble at scale.** When the global batch is constrained, gradient-accumulation (GA) per iteration drops and the bubble ratio rises. Interleaved PP (VPP) mitigates this: + +$$ +\text{bubble ratio} = \frac{PP-1}{(PP-1) + GA \times VPP} +$$ + +For PP=16, GA=16: VPP=1 gives ~48% bubble; VPP=6 gives ~14%—a large efficiency win verified on a 64-node setup. + +**Inter-node dispatch.** Profiling on a 2T/1024-GPU run showed A2A consuming **25–30%** of step time; DeepEP delivered roughly **1.05×–7.66×** end-to-end speedup over plain A2A and kept EP scaling nearly flat. + +--- + +## 6. Quick checklist + +1. **Profile first**—Torch Profiler + TraceLens; project memory before launching ultra-large runs. +2. **Turn on Turbo**—`--enable_primus_turbo True`, then grouped GEMM, attention, DeepEP as needed (requires the external `primus_turbo` package). +3. **Kill CPU syncs**—`--turbo_sync_free_moe_stage` (1 on MI355X, 2 on MI300X/MI325X). +4. **Stabilize + bind**—`--manual_gc True`, `ENABLE_NUMA_BINDING=1`, `HSA_KERNARG_POOL_SIZE=12582912`. +5. **Scale memory headroom into throughput**—raise MBS; use CP2 + selective recompute for 1T+; use VPP to cut pipeline bubbles. + +--- + +## Related documentation + +- [Performance tuning](./performance-tuning.md)—Primus-Turbo flags, HipBLASLt, precision, recompute. +- [Parallelism strategies](./parallelism-strategies.md) and [Parallelism configuration](./parallelism-configuration.md)—EP, PP, CP, VPP. +- [Collective operations](./collective-operations.md)—A2A and DeepEP context. +- [Profiling & observability](./profiling-and-observability.md) and [Projection](../02-user-guide/projection.md). +- Source blog: `examples/moe_package/README.md`. diff --git a/docs/04-technical-guides/multi-node-networking.md b/docs/04-technical-guides/multi-node-networking.md new file mode 100644 index 000000000..ffc61d5ab --- /dev/null +++ b/docs/04-technical-guides/multi-node-networking.md @@ -0,0 +1,203 @@ +# Multi-node networking guide + +Multi-node training depends on **high-bandwidth, low-latency** communication between GPUs. On AMD systems, **RCCL** (ROCm Collective Communications Library) provides GPU collectives with an API aligned to **NCCL**, so most **NCCL-prefixed** environment variables apply to RCCL as well. + +This guide summarizes how Primus configures networking, how **InfiniBand**, **RoCE**, and **AINIC (AMD AI NIC)** fit in, and how to validate and troubleshoot cluster connectivity. + +**Primary sources in this repository** + +| Topic | File | +|-------|------| +| Default NCCL/RCCL and socket setup | `runner/helpers/envs/base_env.sh` | +| IB HCA detection | `runner/helpers/envs/get_nccl_ib_hca.sh` | +| Socket / interface detection | `runner/helpers/envs/get_ip_interface.sh` | +| AINIC hook (container/CLI integration) | `runner/helpers/hooks/03_enable_ainic.sh` | +| AINIC CLI defaults | `runner/use_ainic.yaml` | +| ANP / `NCCL_NET_PLUGIN` example | `examples/run_pretrain.sh` | + +--- + +## 1. Overview + +- **Goal:** Keep gradient and parameter exchanges from becoming the bottleneck when scaling across nodes. +- **Stack:** PyTorch distributed uses the ROCm **NCCL** backend name in many configs; the implementation is **RCCL** on AMD GPUs. +- **Transports:** Common fabrics include **InfiniBand (IB)**, **RoCE** (RDMA over Converged Ethernet), and **AINIC** on supported AMD platforms. Primus scripts set or detect **HCAs**, **socket interfaces**, and optional **AINIC** tuning. + +--- + +## 2. InfiniBand configuration + +These variables are standard in NCCL/RCCL deployments. Primus seeds several from `runner/helpers/envs/base_env.sh` when that script is sourced. + +| Variable | Role | +|----------|------| +| `NCCL_IB_HCA` | Selects **InfiniBand Host Channel Adapters** (device:port list). | +| `NCCL_IB_GID_INDEX` | **GID index** for the active port (RoCE and IB differ; see vendor docs). | +| `NCCL_IB_TC` | **Traffic class** for InfiniBand. | +| `NCCL_IB_FIFO_TC` | Traffic class for FIFO traffic. | +| `NCCL_IB_RETRY_CNT` | Retry count for IB operations (tune with vendor guidance). | +| `NCCL_IB_TIMEOUT` | Timeout for IB operations. | +| `NCCL_IB_QPS_PER_CONNECTION` | Queue pairs per connection. | +| `NCCL_NET_GDR_LEVEL` | **GPUDirect RDMA** level for NIC/GPU transfers. | +| `NCCL_DMABUF_ENABLE` | Use **DMA-BUF** path where supported. | + +### Auto-detection in Primus + +If `NCCL_IB_HCA` is **unset**, `base_env.sh` runs `runner/helpers/envs/get_nccl_ib_hca.sh`, which enumerates `/sys/class/infiniband/`, skips bonded/storage-style devices, and builds a comma-separated `device:port` list for `NCCL_IB_HCA`. + +Default in `base_env.sh`: + +```bash +export NCCL_IB_GID_INDEX=${NCCL_IB_GID_INDEX:-3} +``` + +AINIC-oriented configs often override `NCCL_IB_GID_INDEX` to `1` (see `runner/use_ainic.yaml` and `03_enable_ainic.sh`). + +--- + +## 3. RoCE (RDMA over converged ethernet) + +RoCE reuses much of the **IB verb** stack; the same **`NCCL_IB_*`** knobs apply. + +| Variable | Typical use | +|----------|-------------| +| `NCCL_IB_ROCE_VERSION_NUM` | RoCE version (commonly **2** for RoCE v2). | + +GID selection (`NCCL_IB_GID_INDEX`) and traffic classes (`NCCL_IB_TC`, `NCCL_IB_FIFO_TC`) remain important on RoCE fabrics. Follow your network team’s mapping (often **GID index 1** for RoCE v2 vs **3** for some IB fabrics—your site might differ). + +--- + +## 4. AINIC (AMD AI NIC) + +**AINIC** refers to AMD’s AI-optimized NIC path (for example, the **AMD Pensando™ Pollara 400 AI NIC**) used in some clusters. Enabling it is a combination of **environment**, **container image**, and **device pass-through**. + +### Enable AINIC + +- Set **`USING_AINIC=1`**. The hook `runner/helpers/hooks/03_enable_ainic.sh` runs when this is set and exports AINIC-related variables back to the caller (`env.VAR=VALUE` lines). +- Use container images built for AINIC when required by your site. Examples in this repository use tags such as `docker.io/tasimage/primus:-ainic` (see `examples/customer_package/` and `.github/workflows/ci.yaml`). Match the image to your ROCm and ANP bundle. + +### `runner/use_ainic.yaml` + +Primus CLI system defaults for AINIC-oriented runs include: + +- Container **`device`** mounts: `/dev/kfd`, `/dev/dri`, `/dev/infiniband` (required for GPU and IB access in the container). +- Environment entries such as `USING_AINIC=1`, `NCCL_PXN_DISABLE=0`, and `NCCL_IB_GID_INDEX=1`. + +Adjust **`NCCL_IB_GID_INDEX`** and **`container.options.image`** to match your cluster; comments in `runner/use_ainic.yaml` call this out explicitly. + +### AINIC hook + +**`runner/helpers/hooks/03_enable_ainic.sh`** is the supported hook path: it sets ANP/RCCL/MPI home directories, IB QoS, RoCE version, P2P channel counts, GDR flush behavior, `LD_LIBRARY_PATH` (including `libibverbs` and RCCL/ANP/MPI build paths), and related flags. Default `NCCL_IB_FIFO_TC` in the hook is **192**; align this value with your fabric. + +### RCCL network plugin (ANP) + +For ANP-based networking, clusters often set **`NCCL_NET_PLUGIN`** to **`librccl-anp.so`** when that library is present under `ANP_HOME_DIR`, falling back to `librccl-net.so` otherwise—see the logic in `examples/run_pretrain.sh`. This complements the library paths from `03_enable_ainic.sh`. + +### Variables commonly set for AINIC + +From `03_enable_ainic.sh` (non-exhaustive): + +| Variable | Purpose | +|----------|---------| +| `ANP_HOME_DIR`, `RCCL_HOME_DIR`, `MPI_HOME_DIR` | Install roots for ANP, RCCL, and Open MPI. | +| `NCCL_IB_TC`, `NCCL_IB_FIFO_TC` | Traffic classes for IB/RoCE. | +| `NCCL_IB_GID_INDEX` | Often **1** for AINIC-oriented configs in Primus examples. | +| `NCCL_IB_ROCE_VERSION_NUM` | RoCE v2. | +| `RCCL_GDR_FLUSH_GPU_MEM_NO_RELAXED_ORDERING` | Stricter GDR flush ordering (set to `0` in these scripts). | +| `LD_LIBRARY_PATH` | Prepends `libibverbs`, RCCL, ANP, and MPI library paths. | + +--- + +## 5. Socket configuration + +CPU-side and fallback socket traffic uses interface selection: + +| Variable | Role | +|----------|------| +| `NCCL_SOCKET_IFNAME` | Interface name or pattern for NCCL socket transport (e.g. `eth0`, or `^docker0,lo` to **exclude** virtual interfaces). | +| `GLOO_SOCKET_IFNAME` | Interface for **Gloo** process groups (CPU barriers and related). | + +**Primus behavior:** `base_env.sh` sets `IP_INTERFACE` via `runner/helpers/envs/get_ip_interface.sh` (fallback: first address from `hostname -I`). Both `NCCL_SOCKET_IFNAME` and `GLOO_SOCKET_IFNAME` default to **`IP_INTERFACE`** when unset. + +**Requirement:** All nodes must agree on a **reachable** address family and interface choice; mismatched bindings are a frequent source of hangs. + +--- + +## 6. PCIe cross-NIC (PXN) + +| Variable | Default in `base_env.sh` | Meaning | +|----------|--------------------------|---------| +| `NCCL_PXN_DISABLE` | `1` | **PXN disabled** by default (saves GPU memory per comment in `base_env.sh`). | + +When **`NCCL_PXN_DISABLE=0`**, **PCIe cross-NIC** is enabled: GPUs might use NICs attached to **other** PCIe switches, which can improve **multi-rail** bandwidth at the cost of **higher GPU memory** use. `runner/use_ainic.yaml` sets `NCCL_PXN_DISABLE=0` for AINIC-oriented runs. + +--- + +## 7. Network diagnostics + +### Preflight + +```bash +primus-cli direct -- preflight --network +``` + +For multi-node (Slurm example): + +```bash +primus-cli slurm srun -N 4 -- preflight --host --gpu --network +``` + +See `docs/02-user-guide/preflight.md` for flags, output locations (`output/preflight` by default), and interpretation. + +Set **`PRIMUS_EXPECT_IB=1`** when InfiniBand is **required** for validation; preflight uses this in `primus/tools/preflight/network/network_standard.py`. + +### RCCL benchmark + +```bash +primus-cli slurm srun -N 4 -- benchmark rccl --op all_reduce --min-bytes 1M --max-bytes 128M +``` + +This exercises collective bandwidth and latency across a message-size sweep. See `docs/02-user-guide/benchmarking.md` and `primus/tools/benchmark/rccl_bench_args.py` for options (dtypes, operations, output files). + +### Verbose RCCL logs + +```bash +export NCCL_DEBUG=INFO +``` + +Use for short, controlled runs; **TRACE** can be extremely verbose. + +--- + +## 8. Multi-node setup checklist + +- **ROCm version** matches across all nodes (driver and container image). +- **`NCCL_SOCKET_IFNAME` / `GLOO_SOCKET_IFNAME`** (or auto-detected `IP_INTERFACE`) identify the **same logical network** on every node. +- **InfiniBand or RoCE** is up (`ibstat`, `/dev/infiniband`, kernel modules such as `ib_core` / `mlx5_core` as appropriate). +- **Firewall** allows ports required by your launcher and collective tests (`MASTER_ADDR` / `MASTER_PORT` reachable). +- **`MASTER_ADDR`** resolves and is reachable from **all** nodes. +- **`GPUS_PER_NODE`** matches physical GPUs per node. +- **Containers** mount `/dev/kfd`, `/dev/dri`, and `/dev/infiniband` when using IB/RoCE/AINIC (see `runner/use_ainic.yaml`). + +--- + +## 9. Troubleshooting network issues + +| Symptom | What to check | +|---------|----------------| +| **Timeout or hang** at init | `MASTER_ADDR` / `MASTER_PORT`, firewall, VPN, wrong `NCCL_SOCKET_IFNAME`, or inconsistent interface across nodes. | +| **Slow** collectives | IB vs Ethernet path, `NCCL_NET_GDR_LEVEL`, fabric errors, or contention; compare **`benchmark rccl`** to baseline. | +| **IB not detected** | `/dev/infiniband` missing, modules not loaded, or wrong container devices. | +| **Wrong interface** | Restrict with `NCCL_SOCKET_IFNAME=^docker0,lo` (exclude loopback and Docker bridges). | +| **GID / RoCE issues** | `NCCL_IB_GID_INDEX` vs site documentation; RoCE v2 settings (`NCCL_IB_ROCE_VERSION_NUM`). | + +For a consolidated list of `NCCL_*` / `RCCL_*` variables, see `docs/03-configuration-reference/environment-variables.md` and the upstream [RCCL environment variables](https://rocm.docs.amd.com/projects/rccl/en/develop/api-reference/env-variables.html) documentation. + +--- + +## Related documentation + +- [Preflight diagnostics](../02-user-guide/preflight.md) +- [Benchmark suite](../02-user-guide/benchmarking.md) +- [NCCL/RCCL collective operations](./collective-operations.md) +- [Environment variables](../03-configuration-reference/environment-variables.md) diff --git a/docs/README_NATIVE_SFT_LORA_EN.md b/docs/04-technical-guides/native-sft-lora.md similarity index 95% rename from docs/README_NATIVE_SFT_LORA_EN.md rename to docs/04-technical-guides/native-sft-lora.md index 9ff918858..ec160614b 100644 --- a/docs/README_NATIVE_SFT_LORA_EN.md +++ b/docs/04-technical-guides/native-sft-lora.md @@ -1,4 +1,4 @@ -# Primus Native SFT LoRA — Quick Start +# Primus native SFT LoRA—quick start > **Branch**: `feat/megatron/support-sft-native` (PR701) > **Backend**: Megatron-LM **native** (no Megatron-Bridge runtime dependency) @@ -38,13 +38,13 @@ Entry point: `primus/backends/megatron/megatron_sft_trainer.py` (`MegatronSFTTra --- -## 2. Runtime Environment +## 2. Runtime environment ### 2.1 Docker container | Container | Image | Notes | |---|---|---| -| **`sft_primus_0507_native`** | `rocm/primus:v26.2` | Recommended; verified | +| **`sft_primus_0507_native`** | `rocm/primus:v26.3` | Recommended; verified | Container mounts (set once at container start): @@ -63,13 +63,13 @@ export EXP_NAME="llama2_70b_native_$(date +%Y%m%d_%H%M%S)" ``` Set automatically inside the container by `examples/run_pretrain.sh` (you don't need to touch these): -- `TRITON_CACHE_DIR`, `MIOPEN_USER_DB_PATH`, `PRIMUS_CACHE_ROOT` — persistent JIT cache -- `NCCL_*` / `RCCL_*` — communication tuning -- `HSA_*` / `GPU_MAX_HW_QUEUES` — AMD GPU performance tuning +- `TRITON_CACHE_DIR`, `MIOPEN_USER_DB_PATH`, `PRIMUS_CACHE_ROOT`—persistent JIT cache +- `NCCL_*` / `RCCL_*`—communication tuning +- `HSA_*` / `GPU_MAX_HW_QUEUES`—AMD GPU performance tuning --- -## 3. Launch Commands (verified) +## 3. Launch commands (verified) ### 3.1 BF16 / FP8 (existing yaml configs, ready to run) @@ -130,7 +130,7 @@ The plumbing is already in place: Hard constraints: -1. **TransformerEngine ≥ 2.7.0.dev0** required (the `rocm/primus:v26.2` image already satisfies this) +1. **TransformerEngine ≥ 2.7.0.dev0** required (the `rocm/primus:v26.3` image already satisfies this) 2. **FP4 and FP8 are mutually exclusive**: `args.fp4 and args.fp8` raises in Megatron (`arguments.py:885-887`) 3. **`fp4_param` must be paired with `fp4`**: enabling `fp4_param` alone raises (`arguments.py:889-891`) @@ -310,7 +310,7 @@ modules: # ===================================================================== enable_primus_turbo: true use_turbo_attention: false - use_turbo_grouped_mlp: false + use_turbo_grouped_gemm: false use_turbo_rms_norm: false # ---------- Cross-entropy fusion ---- @@ -403,11 +403,11 @@ grep -E "throughput per GPU" "$RANK0" | head -5 ### Q1: `--fp4-format requires Transformer Engine >= 2.7.0.dev0` -Upgrade TE inside the container, or switch to image `rocm/primus:v26.2`+. +Upgrade TE inside the container, or switch to image `rocm/primus:v26.3`+. ### Q2: `--fp4-format and --fp8-format cannot be used simultaneously` -Leftover `fp8: hybrid` / `fp8: e4m3` in the yaml — must be removed. +Leftover `fp8: hybrid` / `fp8: e4m3` in the yaml—must be removed. ### Q3: `--fp4-param-gather must be used together with --fp4-format` @@ -437,9 +437,10 @@ done --- -## 6. References / Further Reading +## 6. References / further reading -- **PR #701** — Full implementation of this native SFT stack: +- **Post-training overview**: [Post-Training (SFT / LoRA / DPO)](../02-user-guide/posttraining.md)—how this native SFT LoRA path fits into the broader fine-tuning workflow. +- **PR #701**—Full implementation of this native SFT stack: https://github.com/AMD-AGI/Primus/pull/701 - **Megatron-LM FP4 design**: `third_party/Megatron-LM/megatron/core/fp4_utils.py` + @@ -454,6 +455,6 @@ done ## 7. Maintainers -- @wenxie-amd — PR #701 main author -- @Xiaoming-AMD — co-author (trainer + dataset core) -- @botaohu001 — packing / mlperf-aligned recipe / diagnostic tools +- @wenxie-amd—PR #701 main author +- @Xiaoming-AMD—co-author (trainer + dataset core) +- @botaohu001—packing / mlperf-aligned recipe / diagnostic tools diff --git a/docs/04-technical-guides/parallelism-configuration.md b/docs/04-technical-guides/parallelism-configuration.md new file mode 100644 index 000000000..0bd455360 --- /dev/null +++ b/docs/04-technical-guides/parallelism-configuration.md @@ -0,0 +1,255 @@ +# Parallelism configuration guide + +Primus is a YAML-driven training framework for AMD GPUs. Megatron-LM, TorchTitan, and MaxText each expose parallelism through different configuration namespaces. This guide explains how to set parallelism and batch-related parameters, how global batch size relates to micro batch size and data parallel width, and how to choose a parallel strategy for common model sizes. + +Default values cited below come from Primus module presets: + +- Megatron trainer: `primus/configs/modules/megatron/trainer_base.yaml` +- Megatron model (tensor/pipeline/expert/context parallel): `primus/configs/models/megatron/language_model.yaml` +- TorchTitan: `primus/configs/modules/torchtitan/pre_trainer.yaml` + +Experiment YAMLs in `examples/` often override these defaults for specific models and hardware. + +--- + +## 1. Megatron parallelism configuration + +Model-parallel degrees live on the **model** config (for example under `model:` in your experiment YAML, merged from `language_model.yaml`). Training batch and overlap settings live on the **trainer** module (`trainer_base.yaml`). + +### Core parallel degrees + +| Parameter | Default (Primus `language_model.yaml`) | Description | +|-----------|------------------------------------------|-------------| +| `tensor_model_parallel_size` | `1` | Tensor parallelism (TP): shards attention and MLP across this many GPUs. | +| `pipeline_model_parallel_size` | `1` | Pipeline parallelism (PP): number of pipeline stages. | +| `expert_model_parallel_size` | `1` | Expert parallelism (EP) for MoE: shards experts across this many GPUs. | +| `context_parallel_size` | `1` | Context parallelism (CP) for long sequences. | +| `sequence_parallel` | `true` | Sequence parallelism (SP); typically used with TP greater than 1. | + +### Virtual pipeline (VPP) and pipeline communication + +| Parameter | Description | +|-----------|-------------| +| `virtual_pipeline_model_parallel_size` | Interleaved pipeline depth (null disables VPP). | +| `num_layers_per_virtual_pipeline_stage` | Layers per virtual stage when using VPP. | +| `overlap_p2p_comm` | Overlap pipeline P2P with compute (default `true` in `trainer_base.yaml`). | + +### Optimizer, FSDP, and overlap (trainer module) + +| Parameter | Default (`trainer_base.yaml`) | Description | +|-----------|-------------------------------|-------------| +| `use_distributed_optimizer` | `false` | ZeRO-1 style optimizer state sharding when enabled. | +| `use_torch_fsdp2` | `false` | Full FSDP2 integration. | +| `overlap_grad_reduce` | `false` | Overlap gradient all-reduce with backward. | +| `overlap_param_gather` | `false` | Overlap parameter gathering with forward. | + +Set these to `true` in your experiment when you want communication/compute overlap; many production configs enable `use_distributed_optimizer` and overlap flags for large runs. + +### Data parallel size (implicit) + +For Megatron, data parallel size is not a single YAML key; it is implied by the world size and the product of parallel degrees: + +\[ +\text{DP} = \frac{\text{world\_size}}{\text{TP} \times \text{PP} \times \text{EP}} +\] + +(Adjust if you also use context parallelism or other groupings; your job’s process layout must match the configured degrees.) + +### Batch parameters + +| Parameter | Default (`trainer_base.yaml`) | Description | +|-----------|-------------------------------|-------------| +| `micro_batch_size` | `2` | Micro batch size per data-parallel rank (MBS). | +| `global_batch_size` | `128` | Target global batch size (GBS) across the data parallel group. | + +Megatron derives **gradient accumulation** from `global_batch_size`, `micro_batch_size`, and the effective data parallel size so that: + +\[ +\text{GBS} = \text{MBS} \times \text{DP} \times \text{gradient\_accumulation\_steps} +\] + +Equivalently: + +\[ +\text{gradient\_accumulation\_steps} = \frac{\text{GBS}}{\text{MBS} \times \text{DP}} +\] + +You normally set `global_batch_size` and `micro_batch_size` in YAML; Megatron computes the number of accumulation steps automatically. + +--- + +## 2. TorchTitan parallelism configuration + +TorchTitan parallelism is grouped under the `parallelism:` key in the TorchTitan module (see `primus/configs/modules/torchtitan/pre_trainer.yaml`). + +### `parallelism.*` parameters + +| Key | Default | Description | +|-----|---------|-------------| +| `parallelism.tensor_parallel_degree` | `1` | Tensor parallelism degree. | +| `parallelism.pipeline_parallel_degree` | `1` | Pipeline parallelism degree. | +| `parallelism.data_parallel_shard_degree` | `-1` | FSDP shard degree; `-1` lets the framework choose. | +| `parallelism.data_parallel_replicate_degree` | `1` | DDP-style replication degree. | +| `parallelism.expert_parallel_degree` | `1` | Expert parallelism for MoE. | +| `parallelism.context_parallel_degree` | `1` | Context parallelism. | +| `parallelism.fsdp_reshard_after_forward` | `default` | FSDP reshard policy (`default` uses TorchTitan’s default behavior). | +| `parallelism.enable_async_tensor_parallel` | `false` | Async tensor-parallel communication. | +| `parallelism.pipeline_parallel_schedule` | `1F1B` | Pipeline schedule (for example `1F1B`). | +| `parallelism.pipeline_parallel_microbatch_size` | `1` | Microbatch size for pipeline stages. | + +### Batch parameters under `training.*` + +| Key | Default | Description | +|-----|---------|-------------| +| `training.global_batch_size` | `-1` | Global batch size; `-1` typically means unset or derived. | +| `training.local_batch_size` | `8` | Per-rank local (micro) batch size. | + +### Global batch relationship + +For TorchTitan, a useful relationship when using replicate and shard degrees explicitly is: + +\[ +\text{global\_batch\_size} \approx \text{local\_batch\_size} \times \text{data\_parallel\_replicate\_degree} \times \text{data\_parallel\_shard\_degree} +\] + +Exact semantics follow TorchTitan’s distributed layout; set `training.global_batch_size` and parallelism degrees consistently with your launcher’s world size. + +--- + +## 3. MaxText parallelism configuration + +MaxText (JAX) uses a **device mesh** with **ICI** (intra-node / “in-cluster interconnect”) and **DCN** (inter-node / “data center network”) axes for parallelism. Defaults and parameter names come from upstream MaxText, for example `third_party/maxtext/src/MaxText/configs/base.yml`, not from Primus presets alone. + +### Common parallelism keys (from `base.yml`) + +Examples include: + +- `ici_tensor_parallelism`—tensor parallelism within a node +- `ici_fsdp_parallelism`—FSDP-style sharding on ICI (default `-1` for auto in many layouts) +- `dcn_data_parallelism`—data parallelism across nodes (default `-1` for auto) +- `dcn_fsdp_parallelism`—FSDP across DCN + +### Batch sizing + +- `per_device_batch_size`—primary knob for per-device batch (see `base.yml`). + +Consult MaxText’s mesh documentation and your chosen model YAML for valid combinations of ICI/DCN axes. + +--- + +## 4. Batch size relationships + +### Megatron-style identity + +\[ +\text{GBS} = \text{MBS} \times \text{DP} \times \text{grad\_accum} +\] + +\[ +\text{DP} = \frac{\text{world\_size}}{\text{TP} \times \text{PP} \times \text{EP}} +\] + +(Subject to your exact parallel groups; CP and custom layouts can introduce additional groups.) + +### How GBS, MBS, and DP interact + +| Goal | What to change | +|------|----------------| +| Increase global batch without more per-GPU memory | Increase `gradient_accumulation_steps` (Megatron) or increase accumulation / GBS while keeping MBS fixed. | +| Increase throughput per step | Increase `micro_batch_size` if memory allows; might require lowering accumulation to keep GBS fixed. | +| Scale to more GPUs | Increase world size; often increase DP; keep GBS stable by adjusting accumulation. | + +### Memory and convergence + +| Factor | Effect | +|--------|--------| +| **MBS** | Strongly affects per-GPU activation memory; larger MBS often improves GPU utilization but can OOM. | +| **GBS** | Affects effective noise in the gradient and optimal learning rate scaling; many recipes scale LR with GBS. | + +**Practical recommendation:** start with `micro_batch_size` of `1` or `2`, verify stability and memory. Increase `global_batch_size` (via accumulation or more DP ranks) gradually while monitoring loss and adjusting learning rate per your recipe. + +### Example numeric table (Megatron-style) + +Assume TP=1, PP=1, EP=1, so DP equals world size. + +| World size (DP) | MBS | Grad accum | GBS | +|-----------------|-----|------------|-----| +| 8 | 1 | 16 | 128 | +| 8 | 2 | 8 | 128 | +| 16 | 1 | 8 | 128 | +| 16 | 2 | 4 | 128 | + +--- + +## 5. Decision guide: Choosing parallelism + +| Situation | Suggested direction | +|-----------|----------------------| +| Model fits on **one GPU** | Use DP and/or FSDP only; TP=1, PP=1. | +| Model fits on **one node** but not one GPU | **TP** within the node; **DP** across any remaining replicas. | +| Model needs **multiple nodes** | **TP** within node where possible; **PP** across nodes for very large depth; **DP** for remaining width. | +| **MoE** | Add **EP**; align expert count and routing with `expert_model_parallel_size` / `parallelism.expert_parallel_degree`. | +| **Very long sequences** | Increase **CP** (`context_parallel_size` / `context_parallel_degree`) as supported by the backend. | + +### Example configurations (illustrative) + +These are representative topologies; always validate with your checkpoint format, memory profile, and hardware interconnect. + +| Profile | GPUs | TP | PP | EP | DP (illustrative) | +|---------|------|----|----|----|---------------------| +| ~7B | 8 | 1 | 1 | 1 | 8 | +| ~70B | 64 | 8 | 2 | 1 | 4 | +| Large MoE (~671B class) | many | 8 | 4 | 8 | remainder | + +--- + +## 6. Common parallelism recipes (YAML snippets) + +### Megatron: 8-GPU data parallel only + +```yaml +# model (or merged language_model section) +tensor_model_parallel_size: 1 +pipeline_model_parallel_size: 1 +expert_model_parallel_size: 1 +context_parallel_size: 1 +sequence_parallel: false + +# trainer +micro_batch_size: 2 +global_batch_size: 128 +``` + +### Megatron: Tensor + pipeline + data parallel + +```yaml +tensor_model_parallel_size: 8 +pipeline_model_parallel_size: 2 +expert_model_parallel_size: 1 +context_parallel_size: 1 +sequence_parallel: true + +micro_batch_size: 1 +global_batch_size: 512 +``` + +### TorchTitan: TP + PP with explicit schedule + +```yaml +parallelism: + tensor_parallel_degree: 4 + pipeline_parallel_degree: 2 + data_parallel_replicate_degree: 1 + data_parallel_shard_degree: -1 + expert_parallel_degree: 1 + context_parallel_degree: 1 + pipeline_parallel_schedule: 1F1B + pipeline_parallel_microbatch_size: 1 + enable_async_tensor_parallel: false + +training: + global_batch_size: 256 + local_batch_size: 4 +``` + +For full worked examples, see `examples/megatron/configs/` and `examples/torchtitan/configs/` under your target hardware (for example `MI300X/`). diff --git a/docs/04-technical-guides/parallelism-strategies.md b/docs/04-technical-guides/parallelism-strategies.md new file mode 100644 index 000000000..45d61fe37 --- /dev/null +++ b/docs/04-technical-guides/parallelism-strategies.md @@ -0,0 +1,361 @@ +# Parallelism strategies for distributed training + +This guide explains the parallelism dimensions used when training large foundation models on AMD GPUs with Primus. It moves from basic data parallelism to advanced combinations of tensor, pipeline, context, and expert parallelism, including how Primus exposes these options through Megatron-LM and TorchTitan. + +For Megatron YAML flags and environment tuning, see [Megatron parameters](../03-configuration-reference/megatron-parameters.md) and [Environment variables](../03-configuration-reference/environment-variables.md). + +--- + +## 1. Introduction + +### Why parallelism is needed + +Modern foundation models often exceed the memory of a single GPU: parameters, activations, optimizer states, and KV caches cannot all reside on one device at useful batch sizes. Even when a model *fits*, training throughput might be too low without scaling across many GPUs. Parallelism splits the problem along several independent **dimensions** so that: + +- **Memory** is shared across devices (sharding, pipeline stages, sequence splits). +- **Compute** is scaled by processing more data in parallel or by overlapping communication with computation. + +### Overview of parallelism dimensions + +| Dimension | What is split | Primary goal | +|-----------|----------------|--------------| +| **Data parallelism (DP)** | Input batches | Throughput; same model on each GPU | +| **FSDP / ZeRO** | Parameters, gradients, optimizer (by stage) | Memory; keep DP semantics | +| **Tensor parallelism (TP)** | Individual weight matrices and matmuls | Memory per layer; needs fast links | +| **Sequence parallelism (SP)** | Sequence in non-TP regions | Activation memory with TP | +| **Pipeline parallelism (PP)** | Layer groups across stages | Memory; depth-wise split | +| **Context parallelism (CP)** | Sequence for attention (e.g. ring) | Very long contexts | +| **Expert parallelism (EP)** | MoE experts across devices | Memory and compute for MoE | + +These can be **combined**. The product of parallel degrees must match how processes are laid out on the cluster (see [Section 9](#9-combining-parallelism-strategies)). + +--- + +## 2. Data parallelism (DP) + +In **classic data parallelism**, every GPU holds a **full copy** of the model. Each rank receives a **different mini-batch** of data. After the backward pass, **gradients are synchronized** so that all ranks apply the same update. + +``` + Batch shard 0 Batch shard 1 Batch shard 2 Batch shard 3 + | | | | + v v v v + +--------+ +--------+ +--------+ +--------+ + | GPU 0 | | GPU 1 | | GPU 2 | | GPU 3 | + | full | | full | | full | | full | + | model | | model | | model | | model | + +--------+ +--------+ +--------+ +--------+ + | | | | + +------------------+------------------+------------------+ + | + AllReduce(gradients) + | + v + Same weights on all ranks after optimizer step +``` + +**Properties** + +- Simple to reason about and widely supported. +- Requires the **full model, activations for one micro-batch, and optimizer state** to fit in **one GPU’s memory** (unless combined with other strategies). + +**Effective batch size** + +For a single update that aggregates over data-parallel ranks and gradient accumulation: + +\[ +\text{effective\_batch\_size} = \text{micro\_batch\_size} \times \text{num\_GPUs}_{\text{DP}} \times \text{gradient\_accumulation\_steps} +\] + +Here `num_GPUs_DP` is the **data-parallel group size** (not always the same as `world_size` when TP/PP/EP are also used). + +--- + +## 3. Fully sharded data parallel (FSDP / ZeRO) + +**ZeRO** (Zero Redundancy Optimizer) reduces redundant storage by **sharding** optimizer states, gradients, and/or parameters across data-parallel ranks. + +| ZeRO stage | Sharded | Idea | +|------------|---------|------| +| **Stage 1** | Optimizer states | Each rank keeps only \(1/N\) of optimizer tensors | +| **Stage 2** | + Gradients | Gradients are sharded; reduced where needed | +| **Stage 3** | + Parameters | Each rank holds \(1/N\) of parameters; gather before use | + +**FSDP** (Fully Sharded Data Parallel) in PyTorch is the common **implementation** of sharded data parallel training; in the Megatron ecosystem, **ZeRO-3-style** behavior is often discussed alongside **FSDP** for full parameter sharding. + +**Typical execution pattern (conceptual)** + +1. **Forward:** **AllGather** (or equivalent) to materialize parameters needed for the current layer/batch on each rank. +2. **Backward:** **ReduceScatter** (or equivalent) to write shard-sized gradient pieces back to ranks. + +**Memory intuition** + +If replicated training used \(M\) memory per rank for parameters+gradients+optimizer, **ideal** full sharding across \(N\) ranks approaches **\(M/N\)** for the sharded pieces (plus buffers and fragmentation). Moving from **full replication** to **\(1/N\)** sharding for those tensors saves roughly **\((N-1)/N\)** of that component—for **8 GPUs**, about **87.5%** of the replicated footprint for the sharded tensors. + +### In Primus + +| Backend | Configuration | +|---------|----------------| +| **Megatron-LM** | `use_distributed_optimizer: true` enables the **distributed optimizer** (ZeRO-1–style optimizer sharding in Megatron). For **PyTorch FSDP2**, set `use_torch_fsdp2: true` (see Megatron constraints: FSDP2 and distributed optimizer are not used together). | +| **TorchTitan** | `data_parallel_shard_degree` controls how ranks participate in **FSDP-style** sharding (see TorchTitan job config; `-1` often means auto). | + +Exact interactions with checkpoint formats and DDP are documented in [Megatron parameters](../03-configuration-reference/megatron-parameters.md). + +--- + +## 4. Tensor parallelism (TP) + +**Tensor parallelism** splits **individual layers** (usually linear / attention projections) across GPUs so **no single GPU stores the full weight matrix** for that layer. + +### Column-parallel vs row-parallel + +Consider a linear layer \(Y = X W\) with weight matrix \(W\). **Column-parallel** splits \(W\) **along the output dimension** (columns). **Row-parallel** splits \(W\) **along the input dimension** (rows) and **splits \(X\)** so each rank’s matmul dimensions match. + +**Column-parallel linear**—each rank holds **disjoint columns** of \(W\); each rank's output is a **disjoint column shard** (half the width for 2-way TP). To recover the full-width tensor the shards are **concatenated (All-Gather along the output dim)**—this is only done when the full tensor is actually needed: + +``` + SAME full X replicated on each TP rank + | + +-----------------+-----------------+ + | | + v v + Rank 0: X @ W[:,0:h/2] Rank 1: X @ W[:,h/2:h] + | | + v v + partial Y_0 partial Y_1 + (narrow) (narrow) + | | + +-----------------+-----------------+ + | + All-Gather (concatenate) on output dim + (only when the full tensor is needed; with + gather_output=False the output stays column- + sharded and feeds the next layer with no comm) + | + v + full-width Y (concatenation of shards) +``` + +**Row-parallel linear**—each rank holds **disjoint rows** of \(W\); **input \(X\)** is **split** along the **input feature** dimension so each rank computes part of the reduction: + +``` + Rank 0: X_0 @ W[0:r/2,:] ----+ + +-- AllReduce --> Y + Rank 1: X_1 @ W[r/2:r,:] ----+ + (X split along features) (partial sums add to full Y) +``` + +Typical **transformer block** pattern: **column-parallel** for the first projection—its column-sharded output is fed directly into the next layer **without communication**—then **row-parallel** for the second projection, which performs the single **AllReduce** that reconstructs the full output. Column-parallel itself only communicates when `gather_output=True`. + +**Communication** + +- Often **AllReduce** of partial outputs, or **ReduceScatter** + **AllGather** sequences depending on implementation and **sequence parallelism** (see next section). + +**When to use** + +- Best **within a node** (NVLink / high-bandwidth GPU–GPU paths). Multi-node TP is possible but latency-sensitive. + +### In Primus + +| Backend | Parameter | +|---------|-----------| +| Megatron-LM | `tensor_model_parallel_size` | +| TorchTitan | `parallelism.tensor_parallel_degree` | + +--- + +## 5. Sequence parallelism (SP) + +**Sequence parallelism** extends TP by splitting the **sequence dimension** in regions that are not covered by tensor-parallel matmuls—commonly **LayerNorm**, **dropout**, and sometimes **residual** paths—so **activation memory** scales better when **TP > 1**. + +**Interaction with TP** + +- After a **column-parallel** region, partial activations can be **ReduceScatter**d along the sequence. +- Before a **row-parallel** region, activations might be **AllGather**d along the sequence. + +So SP trades **extra collectives** for **lower per-rank activation footprint** on long sequences. + +### In Primus + +| Backend | Parameter | +|---------|-----------| +| Megatron-LM | `sequence_parallel: true` (used with TP) | +| TorchTitan | Sequence-parallel behavior is integrated with TP/parallelization pipelines in supported models | + +--- + +## 6. Pipeline parallelism (PP) + +**Pipeline parallelism** assigns **disjoint subsets of layers** to **stages** on different devices. Activations (and gradients) move **between stages** with **point-to-point** communication. + +``` + Microbatch 1: Stage0 -> Stage1 -> Stage2 -> Stage3 + Microbatch 2: Stage0 -> Stage1 -> Stage2 -> Stage3 + ... +``` + +### Pipeline bubbles + +If a stage waits for input while other stages compute, **idle time** appears (**pipeline bubble**). Schedulers reduce bubbles by overlapping forwards and backwards across microbatches. + +**Common schedules** + +| Schedule | Idea | +|----------|------| +| **1F1B** | One forward, one backward; classic **warmup / steady / cooldown** phases | +| **1F1B interleaved (VPP)** | **Virtual pipeline** stages: multiple chunks per device to improve utilization | +| **Zero-bubble (ZB)** | Reorders / splits backward so **forward and backward** hide each other better; might separate **input-gradient** vs **weight-gradient** phases | +| **V-Schedule / V-Half / V-Min** | Variants reducing bubbles further (names vary by codebase) | +| **DualPipe** | Bidirectional pipeline scheduling (e.g. DeepSeek-style) to overlap forward/backward paths | + +**Bubble rate** + +\[ +\text{bubble\_rate} = \frac{\text{idle time}}{\text{total time}} +\] + +Lower is better; large **microbatch counts** and better schedules reduce bubble overhead. + +### In Primus (Megatron) + +| Parameter | Role | +|-----------|------| +| `pipeline_model_parallel_size` | Number of pipeline stages | +| `patch_zero_bubble` | Enable Primus/Megatron **zero-bubble** pipeline patches | +| `patch_primus_pipeline` | Use Primus pipeline implementation for schedule logic | +| `pp_algorithm` | e.g. `1f1b`, `1f1b-interleaved`, `zero-bubble`, `zero-bubble-heuristic`, `zbv-formatted`, `v-half`, `v-min` | + +See `primus/configs/modules/megatron/primus_pipeline.yaml` and `zero_bubble.yaml` in the repo for defaults. + +### In Primus (TorchTitan) + +| Parameter | Role | +|-----------|------| +| `parallelism.pipeline_parallel_degree` | Pipeline depth | +| `parallelism.pipeline_parallel_schedule` | e.g. `1F1B`, `Interleaved1F1B`, `GPipe`, zero-bubble variants where supported | + +--- + +## 7. Context parallelism (CP) + +**Context parallelism** splits the **sequence length** across devices for long-context training. A common pattern is **ring attention**: each rank holds a **chunk** of queries/keys/values and participates in a **ring** of message passing so attention covers the full sequence without centralizing all activations on one GPU. + +**Use cases** + +- Long documents, 32K–128K+ tokens, where **per-layer activation memory** and **attention compute** must be distributed. + +### In Primus + +| Backend | Parameter | +|---------|-----------| +| Megatron-LM | `context_parallel_size` | +| TorchTitan | `parallelism.context_parallel_degree` | + +--- + +## 8. Expert parallelism (EP) + +**Mixture-of-Experts (MoE)** models route each token to a small subset of **experts**. **Expert parallelism** assigns **different experts** to **different GPUs** so expert weights are not duplicated on every device. + +**Communication** + +- **AllToAll** (or equivalent) is typical: **dispatch** tokens to expert ranks and **combine** expert outputs back. + +**Expert tensor parallelism (ETP)** + +- Experts can be further **tensor-parallel** within a subset of GPUs, analogous to TP for dense layers. + +### In Primus + +| Backend | Parameter | +|---------|-----------| +| Megatron-LM | `expert_model_parallel_size` | +| TorchTitan | `parallelism.expert_parallel_degree`, `parallelism.expert_tensor_parallel_degree` | + +--- + +## 9. Combining parallelism strategies + +### Common pattern + +- **TP within a node** (fast interconnect). +- **PP across nodes or across groups** when layers do not fit on one device. +- **DP / FSDP** for scaling batch size and sharding optimizer state or parameters. + +### GPU count (simplified) + +For dense models (ignoring CP and detailed MoE layout): + +\[ +\text{world\_size} \approx \text{TP} \times \text{PP} \times \text{DP} +\] + +For MoE-heavy setups, you often see: + +\[ +\text{world\_size} \approx \text{TP} \times \text{PP} \times \text{EP} \times \text{DP} +\] + +**Context parallelism** introduces another multiplicative factor in layouts where CP ranks are part of the global mesh (exact rank ordering is implementation-specific). + +### Memory vs communication + +- **More TP** → smaller matrices per GPU but **more frequent** collectives within layers. +- **More PP** → less memory per stage but **pipeline bubbles** and **latency** between stages. +- **More DP/FSDP** → better throughput scaling if communication is not saturated. + +### Example configurations (illustrative) + +| Scenario | TP | PP | DP / notes | +|----------|----|----|------------| +| ~7B on 8 GPUs | 1 | 1 | 8-way DP (or FSDP) | +| ~70B on 64 GPUs (8 nodes × 8) | 8 | 2 | 4-way DP | +| Large MoE (e.g. 671B-class) on 256 GPUs | 8 | 4 | EP 8 (example; real jobs vary widely) | + +Always validate against **memory profiling**, **checkpoint sharding**, and **network** on your cluster. + +--- + +## 10. Batch size relationships + +Let: + +- \(B_{\text{micro}}\) = micro-batch size per forward/backward **per data-parallel rank** (per step inside accumulation), +- \(D\) = **data parallel size** (ranks that share the same model split for DP), +- \(G\) = **gradient accumulation** steps, +- \(B_{\text{global}}\) = **global batch size** across all DP ranks for one optimizer update. + +Then: + +\[ +B_{\text{global}} = B_{\text{micro}} \times D \times G +\] + +**Data parallel size** from world size (when using TP, PP, EP): + +\[ +D = \frac{\text{world\_size}}{\text{TP} \times \text{PP} \times \text{EP}} +\] + +(If **context parallelism** is present, the denominator must include **CP** in the same way your trainer defines the mesh.) + +Solve for accumulation: + +\[ +G = \frac{B_{\text{global}}}{B_{\text{micro}} \times D} +\] + +**Practical notes** + +- **Micro batch** drives **per-GPU activation memory** (often linearly in sequence length for attention). +- **Global batch** affects **convergence** and learning dynamics; scaling laws often refer to global batch. +- **Gradient accumulation** increases **time per optimizer step** but **reduces memory** by using smaller \(B_{\text{micro}}\). + +Megatron-specific names for batch arguments appear in [Megatron parameters](../03-configuration-reference/megatron-parameters.md). + +--- + +## Related documentation + +- [NCCL/RCCL collective operations guide](./collective-operations.md)—which collectives each strategy uses. +- [Megatron parameters](../03-configuration-reference/megatron-parameters.md) +- [Environment variables](../03-configuration-reference/environment-variables.md) diff --git a/docs/04-technical-guides/performance-tuning.md b/docs/04-technical-guides/performance-tuning.md new file mode 100644 index 000000000..5505d9a81 --- /dev/null +++ b/docs/04-technical-guides/performance-tuning.md @@ -0,0 +1,242 @@ +# Performance tuning guide + +This guide covers AMD-focused performance work in Primus: HipBLASLt autotuning for GEMMs, **Primus-Turbo** optional kernels, mixed precision, activation recomputation, communication overlap, memory settings, and MoE-specific flags. It references Primus examples and Megatron module YAMLs. + +--- + +## 1. HipBLASLt autotuning + +Transformer Engine and GEMM-heavy training benefit from HipBLASLt kernel selection. Primus integrates a **three-stage** workflow controlled by `PRIMUS_HIPBLASLT_TUNING_STAGE` (see `examples/README.md` and `examples/run_pretrain.sh`). + +> **Activate tuning first.** The stage variable is only honored when the master switch `PRIMUS_HIPBLASLT_TUNING=1` is set (and `PRIMUS_DETERMINISTIC` is not `1`). Without `PRIMUS_HIPBLASLT_TUNING=1`, both `run_pretrain.sh` and the CLI hook `runner/helpers/hooks/train/pretrain/prepare_experiment.sh` skip tuning entirely and force `TE_HIPBLASLT_TUNING_RUN_COUNT=0` / `TE_HIPBLASLT_TUNING_ALGO_COUNT=0`. Export `PRIMUS_HIPBLASLT_TUNING=1` alongside the stage in every command below. + +### Stage 0 (default) + +No tuning: + +```bash +export PRIMUS_HIPBLASLT_TUNING_STAGE=0 # default +``` + +### Stage 1: Dump GEMM shapes + +Run a **short** training job so shapes are collected during real forward/backward passes. Reduce `train_iters` (or equivalent) for faster shape collection. + +```bash +export PRIMUS_HIPBLASLT_TUNING=1 +export PRIMUS_HIPBLASLT_TUNING_STAGE=1 +./runner/primus-cli direct -- train pretrain \ + --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml +``` + +Output layout (from `examples/README.md`): + +- `./output/tune_hipblaslt/${PRIMUS_MODEL}/gemm_shape` + +### Stage 2: Offline tuning + +Runs offline tuning from dumped shapes (often 10–30 minutes depending on model and shapes): + +```bash +export PRIMUS_HIPBLASLT_TUNING=1 +export PRIMUS_HIPBLASLT_TUNING_STAGE=2 +./runner/primus-cli direct -- train pretrain \ + --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml +``` + +Expected output: + +- `./output/tune_hipblaslt/${PRIMUS_MODEL}/gemm_tune/tune_hipblas_gemm_results.txt` + +### Stage 3: Train with tuned kernels + +Point the runtime at the tuned override file: + +```bash +export PRIMUS_HIPBLASLT_TUNING=1 +export PRIMUS_HIPBLASLT_TUNING_STAGE=3 +export HIPBLASLT_TUNING_OVERRIDE_FILE=/path/to/tune_hipblas_gemm_results.txt +./runner/primus-cli direct -- train pretrain \ + --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml +``` + +### Related environment variables + +| Variable | Role | +|----------|------| +| `TE_HIPBLASLT_TUNING_ALGO_COUNT` | Breadth of algorithm search for TE HipBLASLt tuning (see `examples/run_pretrain.sh` defaults). | +| `TE_HIPBLASLT_TUNING_RUN_COUNT` | Number of benchmark runs per shape during TE tuning. | +| `TE_HIPBLASLT_TUNING_ALGO_FILE` | Optional algorithm file for TE tuning flows. | +| `TE_HIPBLASLT_TUNING` | When set, interacts with deterministic mode; avoid conflicting settings with shape dump (see script comments in `examples/run_pretrain.sh`). | +| `HIPBLASLT_TUNING_OVERRIDE_FILE` | Override file for stage 3 training. | + +### Standalone offline tool + +For manual HipBLASLt bench workflows, see `examples/offline_tune/offline_tune_gemm.py` and `examples/offline_tune/README.md` (hipblaslt-bench integration and `HIPBLASLT_TUNING_OVERRIDE_FILE` usage). + +--- + +## 2. Primus-Turbo optimization + +**Primus-Turbo** is a separate package of optimized AMD GPU kernels used by Primus Megatron and TorchTitan integrations. It is controlled by the master flag `enable_primus_turbo` in Megatron configs (`primus/configs/modules/megatron/primus_turbo.yaml` extends into trainer/model as needed). **You must install the external `primus_turbo` package** for these paths to be available. + +### Master flag (Megatron) + +```yaml +enable_primus_turbo: true +``` + +### Feature flags (Megatron) + +Defaults in `primus/configs/modules/megatron/primus_turbo.yaml` are mostly `false` until enabled. + +| Flag | Purpose | +|------|---------| +| `use_turbo_attention` | Optimized attention kernels. | +| `use_turbo_parallel_linear` | Optimized tensor-parallel linear layers. | +| `use_turbo_grouped_gemm` | Optimized grouped GEMM for MoE. | +| `use_turbo_grouped_mlp` | Removed—use `use_turbo_grouped_gemm` (passing this key now raises an error). | +| `use_turbo_rms_norm` | Optimized RMSNorm. | +| `moe_use_fused_router_with_aux_score` | Fused MoE router (requires Primus-Turbo backend; see [Backend Patch Notes](../06-developer-guide/backend-patch-notes.md)). | +| `use_turbo_deepep` | DeepEP token dispatcher; set with `enable_primus_turbo: true`. | +| `turbo_deepep_num_cu` | Compute units for DeepEP (patch notes suggest practices such as 64 or 80 for EP8, 32 for EP16–64). | +| `turbo_sync_free_moe_stage` | Sync-free MoE stages (`0`–`3`; `0` disables, stage `2` recommended for performance per patch notes). See [MoE training deep-dive](./moe-training.md). | +| `use_turbo_fused_act_with_probs` | Fused activation with probabilities to reduce redundant work. | + +### Feature flags (TorchTitan) + +TorchTitan presets include `primus_turbo` in `primus/configs/modules/torchtitan/pre_trainer.yaml` + +Example keys: + +```yaml +primus_turbo: + enable_primus_turbo: true + use_turbo_attention: true + use_turbo_async_tp: true + use_turbo_float8_linear: true + use_turbo_grouped_mm: false +``` + +### Documentation + +Extended Megatron arguments and Turbo-related behavior are summarized in [Backend Patch Notes](../06-developer-guide/backend-patch-notes.md). + +--- + +## 3. Mixed precision training + +### Megatron (`trainer_base.yaml` patterns) + +| Setting | Description | +|---------|-------------| +| `bf16: true` | BFloat16 training (default `true` in `trainer_base.yaml`). | +| `fp16: false` | FP16 training (optional). | +| `fp8` | FP8 recipe control (`null` / recipes such as delayed scaling in upstream Megatron). | +| `fp8_recipe`, `fp8_margin`, `fp8_interval` | FP8 scaling behavior. | +| `fp4`, `fp4_recipe` | Experimental FP4 paths. | +| `first_last_layers_bf16: true` | Keep first/last layers in BF16 for stability (`num_layers_at_start_in_bf16`, `num_layers_at_end_in_bf16` fine-tune). | + +### TorchTitan + +| Setting | Location | +|---------|----------| +| `training.mixed_precision_param: bfloat16` | `pre_trainer.yaml` default | +| `training.mixed_precision_reduce: float32` | Reduce precision | +| FP8 / quantization | `quantize.linear.float8.*` in `primus/configs/modules/torchtitan/quantize.yaml` | + +### Loss fusion (Megatron model) + +From `primus/configs/models/megatron/language_model.yaml`: + +- Default: `cross_entropy_loss_fusion: false` with `cross_entropy_fusion_impl: "native"`. +- To use Transformer Engine fused cross entropy where supported, enable the fusion explicitly and set `cross_entropy_fusion_impl: "te"`. + +--- + +## 4. Activation recomputation + +### Megatron + +| Parameter | Typical values | Notes | +|-----------|----------------|-------| +| `recompute_granularity` | `full`, `selective` | `full` recomputes more; max memory savings. | +| `recompute_method` | `uniform`, `block` | How recomputation is distributed. | +| `recompute_num_layers` | integer | Layers to recompute when using selective or uniform strategies. | +| `recompute_layer_ids` | list or null | Primus extension: **global** layer indices from `0` to `num_layers - 1` (the patch resolves block-local indices to global ids via `layer_offset`). Use with `recompute_granularity: full` and supported recompute methods. | + +### TorchTitan + +| Parameter | Location | +|-----------|----------| +| `activation_checkpoint.mode` | `none` in default `pre_trainer.yaml` | +| `activation_checkpoint.selective_ac_option` | Selective AC options | + +--- + +## 5. Communication overlap + +### Megatron + +From `trainer_base.yaml` and model settings: + +| Setting | Purpose | +|---------|---------| +| `overlap_grad_reduce` | Overlap gradient reduction with backward. | +| `overlap_param_gather` | Overlap parameter gather with forward. | +| `overlap_p2p_comm` | Pipeline P2P overlap. | +| `async_tensor_model_parallel_allreduce` | Async TP all-reduce (model config). | + +### TorchTitan + +| Setting | Purpose | +|---------|---------| +| `parallelism.enable_async_tensor_parallel: true` | Async tensor parallelism. | + +### Environment + +`CUDA_DEVICE_MAX_CONNECTIONS=1` is commonly required for **correct** overlap behavior in TP/PP stacks (see `docs/03-configuration-reference/environment-variables.md` and Megatron tests). Primus launch scripts or your cluster setup might set this. + +--- + +## 6. Memory optimization + +### Megatron + +| Parameter | Purpose | +|-----------|---------| +| `optimizer_cpu_offload: true` | Offload optimizer state to CPU. | +| `optimizer_offload_fraction` | Fraction to offload (`1.0` in `trainer_base.yaml`). | +| `use_distributed_optimizer: true` | Shards optimizer state across DP ranks (when enabled). | +| `empty_unused_memory_level` | Aggressive emptying of unused memory (`0` default). | +| `global_batch_size` + `micro_batch_size` | Increase global batch via **gradient accumulation** without increasing per-step activation memory. | + +### TorchTitan + +| Parameter | Purpose | +|-----------|---------| +| `training.enable_cpu_offload` | CPU offload path in `pre_trainer.yaml` | + +--- + +## 7. MoE-specific optimization + +### Megatron (model + turbo) + +| Setting | Purpose | +|---------|---------| +| `moe_permute_fusion: true` | Fuse permutation / unpermutation (`patch-notes.md`). | +| `moe_use_fused_router_with_aux_score: true` | Fused router + aux loss (Primus-Turbo). | +| `use_turbo_deepep: true` | DeepEP dispatcher (`enable_primus_turbo` must be true). | +| `turbo_sync_free_moe_stage: 2` | Recommended stage for sync-free MoE (per patch notes). | +| `overlap_moe_expert_parallel_comm: true` | Overlap expert parallel communication (`trainer_base.yaml`). | + +--- + +## Quick checklist + +1. **GEMMs:** run HipBLASLt stages 1–3 or use `offline_tune_gemm.py` for custom workflows. +2. **Kernels:** enable `primus_turbo` after installing `primus_turbo`; turn on attention/MoE flags as needed. +3. **Precision:** BF16 by default; add FP8/FP4 only with recipe testing. +4. **Memory:** recomputation + distributed optimizer + CPU offload + accumulation before buying more GPUs. +5. **MoE:** fusion + DeepEP + sync-free stages + EP comm overlap when supported. diff --git a/docs/04-technical-guides/profiling-and-observability.md b/docs/04-technical-guides/profiling-and-observability.md new file mode 100644 index 000000000..42560cbbf --- /dev/null +++ b/docs/04-technical-guides/profiling-and-observability.md @@ -0,0 +1,170 @@ +# Profiling and observability + +This guide covers how to capture and analyze performance data in Primus: the PyTorch/Kineto profiler, GPU memory snapshots, AMD's TraceLens trace analysis, ROCm memory sampling, memory/performance projection, and pipeline-schedule visualization. Parameters are grounded in `primus/configs/modules/megatron/`, `primus/configs/modules/torchtitan/pre_trainer.yaml`, and `primus/configs/modules/maxtext/pre_trainer.yaml`. + +--- + +## 1. Torch profiler (Megatron) + +Megatron training integrates the PyTorch profiler. Defaults live in `primus/configs/modules/megatron/trainer_base.yaml` and `primus_megatron_module.yaml`. + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `profile` | `false` | Master switch for profiling. | +| `use_pytorch_profiler` | `false` | Use the PyTorch (Kineto) profiler path. | +| `profile_ranks` | `[0]` | Which global ranks to profile. | +| `profile_step_start` | `10` | First step to capture. | +| `profile_step_end` | `12` | Last step to capture. | +| `disable_profiler_activity_cpu` | `false` | Drop CPU-side activity to shrink traces (GPU-only trace). | +| `torch_profiler_record_shapes` | `true` | Record tensor shapes per op. | +| `torch_profiler_with_stack` | `true` | Record Python/C++ stacks (larger traces). | +| `torch_profiler_use_gzip` | `false` | Gzip the exported trace. | + +Enable via CLI overrides on `train pretrain`: + +```bash +./runner/primus-cli direct -- train pretrain \ + --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml \ + --profile True \ + --use_pytorch_profiler True \ + --profile_step_start 5 \ + --profile_step_end 6 \ + --disable_profiler_activity_cpu False +``` + +Keep the capture window **short** (a few steps after warmup)—traces grow quickly, especially with `with_stack` and CPU activity enabled. Load the resulting trace in [Perfetto](https://ui.perfetto.dev/) to inspect CPU/GPU overlap, kernel launch delays, and idle gaps. + +--- + +## 2. GPU memory profiling (Megatron) + +Two complementary mechanisms: + +**Memory history snapshot** (`trainer_base.yaml`): + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `record_memory_history` | `false` | Record the CUDA/HIP allocator history for snapshot analysis. | +| `memory_snapshot_path` | `snapshot.pickle` | Output path for the allocator snapshot. | + +Load the pickle with PyTorch's memory visualizer to find fragmentation and peak allocations. + +**ROCm memory sampling** (`primus_megatron_module.yaml`): + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `use_rocm_mem_info` | `false` | When `true`, collect ROCm memory info via `rocm-smi` **every** iteration. | +| `use_rocm_mem_info_iters` | `[1, 2]` | When `use_rocm_mem_info=false`, only sample at these iterations. | + +Also relevant: `log_memory_to_tensorboard` (`trainer_base.yaml`) writes memory metrics to TensorBoard. + +--- + +## 3. TraceLens automated trace analysis (Megatron) + +[TraceLens](https://github.com/AMD-AGI/TraceLens) turns raw profiler traces into hierarchical breakdowns (roofline/efficiency, compute-vs-memory bound kernels, communication-vs-sync separation, trace diffing). Primus can generate and optionally upload these reports. Configured in `primus/configs/modules/megatron/primus_megatron_module.yaml`: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `generate_tracelens_report` | `false` | Generate TraceLens reports locally (auto-enabled when upload is on). | +| `mlflow_upload_tracelens_report` | `false` | Upload reports to MLflow (auto-enables generation, profiling, tensorboard). | +| `mlflow_tracelens_ranks` | `null` | Ranks to analyze (`null` = all; e.g. `[0, 8]` for one rank/node). | +| `mlflow_tracelens_output_format` | `xlsx` | `xlsx` (fastest), `csv`, or `all`. | +| `mlflow_tracelens_cleanup_after_upload` | `false` | Delete local reports after upload to save disk. | +| `mlflow_tracelens_auto_install` | `true` | Auto-install TraceLens if missing (set `false` to disable). | + +Related profiler/log uploads: `mlflow_upload_traces` (upload raw trace files) and `mlflow_upload_logs` (upload training logs). See [Logging & experiment tracking](./logging-and-experiment-tracking.md) for MLflow setup. + +--- + +## 4. Performance metrics to MLflow (Megatron) + +`mlflow_upload_performance_metrics: false` (`primus_megatron_module.yaml`) enables a comprehensive scaling-test metric set when turned on (implicitly enabling throughput calculation): + +- `perf/throughput_tflops_per_gpu`, `perf/tps_tokens_per_sec_per_gpu`, `perf/iteration_time_ms` +- `perf/{rocm,hip}_current_mem_gb`, `perf/{rocm,hip}_mem_utilization_pct` +- `perf/gpu_utilization_pct_rank{N}`, `perf/gpu_utilization_pct_avg` + +> GPU utilization collection uses an `all_gather` every `log_interval`, which synchronizes ranks—keep this in mind for throughput-sensitive runs. + +--- + +## 5. Profiling (TorchTitan) + +Configured under `profiling:` in `primus/configs/modules/torchtitan/pre_trainer.yaml`: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `enable_profiling` | `false` | Enable the Torch profiler. | +| `profile_freq` | `10` | Capture every N steps. | +| `save_traces_folder` | `profile_traces` | Output folder for traces. | +| `enable_memory_snapshot` | `false` | Capture GPU memory snapshots. | +| `save_memory_snapshot_folder` | `memory_snapshot` | Output folder for snapshots. | + +Communication tracing is configured under `comm:` (`trace_buf_size`, `save_traces_folder: comm_traces`). + +--- + +## 6. Profiling (MaxText) + +Configured in `primus/configs/modules/maxtext/pre_trainer.yaml`: + +| Parameter | Default | Purpose | +|-----------|---------|---------| +| `profiler` | `xplane` | Profiler backend (XPlane traces). | +| `skip_first_n_steps_for_profiler` | `3` | Warmup steps to skip before capture. | +| `profiler_steps` | `1` | Number of steps to capture. | + +--- + +## 7. Memory and performance projection + +Project resource usage **before** launching, without consuming a full cluster. Exposed through the Primus CLI `projection` subcommand (`primus/cli/subcommands/projection.py`). + +```bash +# Memory projection from an experiment config +./primus-cli direct -- projection memory --config examples/megatron/configs/MI300X/llama2_7B-BF16-pretrain.yaml + +# Performance projection (single-node benchmarking) +./primus-cli direct -- projection performance --config .yaml + +# Performance projection scaled to N nodes (simulation) +./primus-cli direct -- projection performance --config .yaml --target-nodes 4 +``` + +Memory projection breaks VRAM down across parameters, gradients, activations, optimizer states, and mixed-precision overhead—invaluable for MoE and ultra-large models where activations dominate. See [Projection](../02-user-guide/projection.md) for the full reference. + +--- + +## 8. Pipeline schedule visualization + +Diagnose pipeline bubbles and stage imbalance with the built-in tool `tools/visualization/pp_vis/`. + +1. Dump per-rank schedule data during training with `--dump_pp_data true` (Megatron flag `dump_pp_data`, `primus_megatron_module.yaml`). Output lands under `output/pp_data/` (`config.json`, `pp_rank_*.json`). +2. Install the tool deps and run the viewer: + +```bash +pip install -r tools/visualization/pp_vis/requirements.txt +python tools/visualization/pp_vis/vis.py # open http://127.0.0.1:8988 +``` + +Configure `task_list` in `vis.py` to point at your dumped `log_path` and the iterations to render. The tool can also visualize the PP simulator output (see `tools/visualization/pp_vis/README.md`). + +--- + +## 9. Recommended workflow + +1. **Project first**—run `projection memory` to confirm the config fits before booking GPUs. +2. **Capture a short trace**—a few steps after warmup with `profile` + `use_pytorch_profiler`. +3. **Inspect**—Perfetto for the timeline; TraceLens for automated hierarchical analysis and trace diffs. +4. **Check memory**—`record_memory_history` / ROCm sampling for fragmentation and peaks. +5. **Check pipelines**—`dump_pp_data` + `pp_vis` for bubbles and stage imbalance. + +--- + +## Related documentation + +- [Logging & experiment tracking](./logging-and-experiment-tracking.md)—WandB / TensorBoard / MLflow setup. +- [MoE training deep-dive](./moe-training.md)—applying this workflow to sparse models. +- [Performance tuning](./performance-tuning.md)—what to change once you've found the bottleneck. +- [Projection](../02-user-guide/projection.md) and [Monitoring and logging](../05-operations/monitoring-logging.md). diff --git a/docs/05-operations/README.md b/docs/05-operations/README.md new file mode 100644 index 000000000..6b9c1e016 --- /dev/null +++ b/docs/05-operations/README.md @@ -0,0 +1,12 @@ +# Operations + +Production deployment and operational guidance. + +- [Deployment](deployment.md): container, Slurm, and Kubernetes deployment +- [Monitoring and logging](monitoring-logging.md): WandB, TensorBoard, MLflow, Primus logging +- [Troubleshooting](troubleshooting.md): common failures, diagnostics, and fixes +- [Security](security.md): secrets handling, container security, dependencies + +--- + +[← Documentation home](../README.md) diff --git a/docs/05-operations/deployment.md b/docs/05-operations/deployment.md new file mode 100644 index 000000000..28948b621 --- /dev/null +++ b/docs/05-operations/deployment.md @@ -0,0 +1,237 @@ +# Deployment guide + +This guide describes how to deploy Primus training across **container**, **direct (bare metal)**, and **Slurm** environments using the unified `primus-cli` launcher. For environment variable semantics, see [Environment variables](../03-configuration-reference/environment-variables.md). For YAML hierarchy and precedence, see [Configuration system](../02-user-guide/configuration-system.md). + +--- + +## 1. Deployment overview + +Primus supports three deployment modes: + +| Mode | Description | Typical use | +|------|-------------|-------------| +| **Container** | Docker/Podman with ROCm-capable GPU devices and capabilities | Recommended default; reproducible images | +| **Direct** | Runs on the current host (or inside an existing container) | Local debugging, single-node, clusters with ROCm on nodes | +| **Slurm** | Wraps `srun`/`sbatch` and launches per-node entry scripts | Multi-node clusters with Slurm | + +**Container image:** `docker.io/rocm/primus:v26.4` (default in `runner/.primus.yaml`). For clusters using **AINIC**, use `runner/use_ainic.yaml` and tune the image and NCCL-related variables (for example `USING_AINIC`, `NCCL_IB_GID_INDEX`) to match your fabric. + +**Prerequisites (baseline):** + +- **AMD ROCm** >= 7.0 on the host (or in the image when using containers) +- **Docker** or **Podman** >= 24.0 when using container mode +- **AMD Instinct** GPUs and working ROCm stack (`rocm-smi` should report devices) + +--- + +## 2. Container deployment + +### 2.1 Pull the image + +```bash +docker pull docker.io/rocm/primus:v26.4 +``` + +The default `container.options.image` in `runner/.primus.yaml` is `rocm/primus:v26.4` (equivalent to `docker.io/rocm/primus:v26.4` when the registry is omitted). + +### 2.2 Required device mounts + +System defaults (`runner/.primus.yaml`, `container.options.device`) pass each path as `--device` to the runtime: + +| Device | Purpose | +|--------|---------| +| `/dev/kfd` | Kernel Fusion Driver (ROCm core) | +| `/dev/dri` | Direct Rendering Infrastructure (GPU access) | +| `/dev/infiniband` | InfiniBand character devices (multi-node / RDMA) | + +### 2.3 Required capabilities + +Defaults (`container.options.cap-add`): + +| Capability | Purpose | +|------------|---------| +| `SYS_PTRACE` | Debugging and profiling tools | +| `CAP_SYS_ADMIN` | Administrative operations required by some ROCm/GPU workflows | + +### 2.4 Container runtime options + +Defaults in `runner/.primus.yaml` include: + +| Option | Value | +|--------|--------| +| `ipc` | `host` | +| `network` | `host` | +| `privileged` | `true` | +| `security-opt` | `seccomp=unconfined` | +| `group-add` | `video` | + +`primus-cli-container.sh` always mounts the **Primus repository root** into the container at the same path (`-v $PRIMUS_PATH:$PRIMUS_PATH`). Mount additional paths for **datasets**, **model weights**, and **outputs** with `--volume` (or `container.options.volume` in YAML). + +### 2.5 Environment passthrough + +`container.options.env` lists names that are forwarded into the **inner** `primus-cli` invocation as `--env` when set on the host (see `runner/.primus.yaml`). Examples include: + +`MASTER_ADDR`, `MASTER_PORT`, `NNODES`, `NODE_RANK`, `GPUS_PER_NODE`, `DOCKER_IMAGE`, `HF_TOKEN`, `WANDB_API_KEY`, `ENABLE_NUMA_BINDING`, `USING_AINIC`, and NCCL/GLOO socket and IB-related variables (`NCCL_IB_HCA`, `NCCL_SOCKET_IFNAME`, `GLOO_SOCKET_IFNAME`, `NCCL_IB_GID_INDEX`, and others). + +Additionally, `primus-cli-container.sh` auto-forwards any environment variable whose name starts with `PRIMUS_`, `NCCL_`, `RCCL_`, `GLOO_`, `IONIC_`, or `HIPBLASLT_` when present on the host. + +### 2.6 Single-node example + +```bash +./primus-cli container --volume /data:/data -- train pretrain --config /data/exp.yaml +``` + +### 2.7 Multi-node container deployment + +Set `MASTER_ADDR`, `MASTER_PORT`, `NNODES`, `NODE_RANK`, and `GPUS_PER_NODE` on each node (Slurm or your orchestrator sets these; see `runner/primus-cli-slurm-entry.sh`). Example pattern when launching manually: + +```bash +export MASTER_ADDR= +export MASTER_PORT=1234 +export NNODES=4 +export NODE_RANK=<0-based index for this node> +export GPUS_PER_NODE=8 + +./primus-cli container -- train pretrain --config /path/to/config.yaml +``` + +Use `--clean` before launch to remove existing containers (`primus-cli-container.sh`). + +--- + +## 3. Slurm deployment + +### 3.1 `srun` (interactive or blocking) + +```bash +./primus-cli slurm srun -N -p -- train pretrain --config +``` + +The Slurm entry script invokes the container launcher on each allocated node. Set the image through `runner/.primus.yaml`, a custom launcher config file, or site policy; the default is `rocm/primus:v26.4`. + +### 3.2 `sbatch` (batch jobs) + +```bash +./primus-cli slurm sbatch -N -p --time --job-name -o -- \ + train pretrain --config +``` + +Add `-e ` if you want separate stderr. + +### 3.3 Slurm-to-Primus environment mapping + +`runner/primus-cli-slurm-entry.sh` sets: + +| Variable | Source (typical) | +|----------|-------------------| +| `NNODES` | `SLURM_NNODES`, or `SLURM_JOB_NUM_NODES`, or existing `NNODES` | +| `NODE_RANK` | `SLURM_NODEID`, or `SLURM_PROCID`, or existing `NODE_RANK` | +| `GPUS_PER_NODE` | Default `8` if unset | +| `MASTER_ADDR` | First host in `SLURM_NODELIST` if unset | +| `MASTER_PORT` | Default `1234` if unset | + +The entry script then exports `MASTER_ADDR`, `MASTER_PORT`, `NNODES`, `NODE_RANK`, and `GPUS_PER_NODE` into the container launcher. + +### 3.4 Slurm YAML defaults (`runner/.primus.yaml`) + +| Key | Default | +|-----|---------| +| `slurm.nodes` | `1` | +| `slurm.gpus_per_node` | `8` | +| `slurm.time` | `"4:00:00"` | +| `slurm.partition` | (commented; set per site) | + +CLI Slurm flags override YAML when both are specified (see `runner/primus-cli-slurm.sh`). + +### 3.5 Entry after the first `--` + +Production examples pass the Primus Python command after the Slurm `--` separator, for example: + +```bash +./primus-cli slurm srun -N 4 -p gpu -- train pretrain --config exp.yaml +``` + +The shipped `primus-cli-slurm-entry.sh` invokes **`primus-cli-container.sh`** with distributed variables set from Slurm. Container options should come from launcher configuration instead of a literal `container` token in the inner command. For **bare-metal** nodes without Docker, run `primus-cli direct` under your allocation and ensure the same distributed variables and ROCm layout as in [Multi-node configuration](#5-multi-node-configuration). + +--- + +## 4. Kubernetes deployment + +Kubernetes integration is **not** shipped as a Helm chart or operator in this repository. The repo includes **`examples/run_k8s_pretrain.sh`**, a client script that talks to a Kubernetes **API** to create and manage training workloads (image default `docker.io/rocm/primus:v26.4`). + +Use that script as a reference for your platform; adapt networking, storage, and scheduling to your cluster policies. + +--- + +## 5. Multi-node configuration + +Required variables for distributed training: + +| Variable | Role | +|----------|------| +| `MASTER_ADDR` | Hostname or IP of rank-0 process | +| `MASTER_PORT` | TCP port for the process group rendezvous | +| `NNODES` | Number of nodes | +| `NODE_RANK` | Zero-based index of this node | +| `GPUS_PER_NODE` | GPUs per node used by `torchrun` | + +**Flow:** User or Slurm sets the environment → `primus-cli` and `primus-cli-direct.sh` load GPU and comm settings → **`torchrun`** launches `primus/cli/main.py` with the distributed topology. + +**Defaults from `runner/.primus.yaml` (`direct` section):** + +| Key | Default | +|-----|---------| +| `direct.master_port` | `1234` | +| `direct.gpus_per_node` | `8` | +| `direct.nnodes` | `1` | +| `direct.master_addr` | `"localhost"` | + +--- + +## 6. Startup and shutdown + +**Lifecycle (high level):** + +1. Parse CLI and load YAML (`--config` chain: see [Configuration system](../02-user-guide/configuration-system.md)). +2. Load environment (GPU detection, hooks, patches in `primus-cli-direct.sh`). +3. Launch training via **`torchrun`** into the Python CLI. + +**Verification:** + +- `--dry-run` prints the command that would run without executing (supported in container and Slurm scripts). +- `--debug` sets `PRIMUS_LOG_LEVEL=DEBUG` for verbose launcher and shell logging. + +**Shutdown:** + +- Normal completion or **Ctrl+C** terminates the training process. +- In container mode, **`--clean`** removes existing containers before launch (`primus-cli-container.sh`). + +**Timeouts (config):** + +| Backend | Parameter | Location | +|---------|-----------|----------| +| Megatron | `distributed_timeout_minutes` | `primus/configs/modules/megatron/trainer_base.yaml` (default `10`) | +| TorchTitan | `comm.init_timeout_seconds` | `primus/configs/modules/torchtitan/pre_trainer.yaml` (default `300`) | + +--- + +## 7. Production checklist + +| Item | Action | +|------|--------| +| ROCm drivers | Install and verify with `rocm-smi` | +| Container image | Pulled and aligned with host ROCm expectations | +| Network | Run `preflight --network` (see [Preflight](../02-user-guide/preflight.md)) | +| Shared data | Paths visible and consistent on all nodes | +| Hugging Face | Set `HF_TOKEN` if using gated models | +| Checkpoints | Save directory on shared or replicated storage with sufficient space | +| Monitoring | Configure Weights & Biases or TensorBoard (see [Monitoring and Logging](./monitoring-logging.md)) | +| Resources | Slurm time limits, partitions, and GPU counts match your YAML and hardware | + +--- + +## Related documentation + +- [CLI reference](../02-user-guide/cli-reference.md) +- [Troubleshooting](./troubleshooting.md) +- [Multi-node networking](../04-technical-guides/multi-node-networking.md) diff --git a/docs/05-operations/monitoring-logging.md b/docs/05-operations/monitoring-logging.md new file mode 100644 index 000000000..581ddfe44 --- /dev/null +++ b/docs/05-operations/monitoring-logging.md @@ -0,0 +1,244 @@ +# Monitoring and logging + +This page summarizes how Primus configures application logging, experiment tracking (Weights & Biases, TensorBoard, MLflow), training metrics, profilers, ROCm memory probes, and how to capture a reproducible configuration snapshot. + +--- + +## 1. Primus logging system + +Primus uses **loguru** for structured logging. Initialization wires **file sinks** (per log level) and a **stderr** sink, binds experiment and distributed context (`team`, `user`, `exp`, `module_name`, `node_ip`, `rank`, `world_size`), and installs an **intercept handler** so legacy `logging` output from frameworks such as Megatron is forwarded to loguru with consistent formatting. + +**Rank-aware behavior** + +- Worker processes write under `{exp_root}/logs/{module_name}/rank-{rank}/` with separate rotated files for `debug`, `info`, `warning`, and `error` (subject to `file_sink_level`). +- The launcher **master** process can use `logs/master/` when the master logger is configured with `is_head=True`. + +**Levels from module configuration** (`primus/configs/modules/module_base.yaml`) + +| Parameter | Default | Role | +|-----------|---------|------| +| `sink_level` | `null` | If set, overrides both file and stderr sink levels. | +| `file_sink_level` | `DEBUG` | Minimum level for file sinks when `sink_level` is unset. | +| `stderr_sink_level` | `INFO` | Minimum level for stderr when `sink_level` is unset. | + +`init_worker_logger` in `primus/core/runtime/logging.py` reads `sink_level`, `file_sink_level`, and `stderr_sink_level` from the merged module config. The Megatron trainer maps `stderr_sink_level` to Megatron’s numeric `logging_level` (the `logging_level` field in `trainer_base.yaml` is deprecated; this mapping supersedes it).. + +**Shell / runner environment** (see `docs/03-configuration-reference/environment-variables.md`) + +| Variable | Purpose | +|----------|---------| +| `PRIMUS_LOG_LEVEL` | Runner verbosity: `DEBUG`, `INFO`, `WARN`, `ERROR` (default `INFO`). | +| `PRIMUS_LOG_TIMESTAMP` | `1` enables timestamps on runner logs; `0` disables. | +| `PRIMUS_LOG_COLOR` | `1` enables ANSI colors when appropriate; often `0` in non-TTY contexts. | + +**CLI** + +- `primus-cli --debug` sets `PRIMUS_LOG_LEVEL=DEBUG` so launcher and shell logging are verbose (see `docs/02-user-guide/cli-reference.md`). + +--- + +## 2. Weights & Biases + +### Megatron + +Configuration files: `primus/configs/modules/megatron/trainer_base.yaml` and `primus_megatron_module.yaml`. + +Defaults in `primus_megatron_module.yaml` disable Weights & Biases; trainer fields in `trainer_base.yaml` supply names and paths when enabled. + +| Parameter | Default (module / trainer) | Description | +|-----------|----------------------------|-------------| +| `disable_wandb` | `true` (`primus_megatron_module.yaml`) | Master switch; when `false`, Primus sets paths and default project/run names from experiment metadata. | +| `wandb_project` | `null` | If unset when Weights & Biases is enabled, defaults to `{work_group}_{user_name}`. | +| `wandb_exp_name` | `null` | If unset, defaults to `exp_name`. | +| `wandb_entity` | `null` | Optional WandB entity/team. | +| `wandb_save_dir` | `null` | Deprecated in favor of `{exp_root}`; artifacts use `{exp_root}/wandb`. | + +**Environment** + +- `WANDB_API_KEY` is **required** when Weights & Biases is enabled; Primus emits a warning if it is missing (`primus/backends/megatron/patches/args/wandb_config_patches.py`). + +### TorchTitan + +Configuration file: `primus/configs/modules/torchtitan/pre_trainer.yaml`. + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `metrics.enable_wandb` | `false` | Enables WandB in the TorchTitan metrics stack. | + +When enabled, `primus/backends/torchtitan/patches/wandb_patches.py` can set `WANDB_PROJECT` and `WANDB_RUN_NAME` from Primus experiment metadata if unset. Use `WANDB_API_KEY` for authentication. + +--- + +## 3. TensorBoard + +### Megatron + +**Module toggles** + +Configuration file: `primus_megatron_module.yaml` + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `disable_tensorboard` | `true` | When `false`, TensorBoard output is placed under `{exp_root}/tensorboard` (Primus overrides deprecated `tensorboard_dir` with this path). | + +**Trainer** + +Configuration file: `trainer_base.yaml` + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `tensorboard_log_interval` | `1` | Steps between TensorBoard writes. | +| `tensorboard_queue_size` | `1000` | Event file queue size. | +| `log_timers_to_tensorboard` | `false` | Log timer stats. | +| `log_batch_size_to_tensorboard` | `false` | Log batch size. | +| `log_learning_rate_to_tensorboard` | `true` | Log learning rate. | +| `log_validation_ppl_to_tensorboard` | `false` | Log validation perplexity. | +| `log_memory_to_tensorboard` | `false` | Log memory stats. | +| `log_world_size_to_tensorboard` | `false` | Log world size. | +| `log_loss_scale_to_tensorboard` | `true` | Log loss scale. | +| `tensorboard_dir` | `null` | Deprecated; Primus sets the directory under `exp_root`. | + +**Note:** Enabling Megatron **profiling** (`profile: true`) forces `disable_tensorboard` off in `update_primus_config` so TensorBoard is available for profile-related views. + +### TorchTitan + +Configuration file: `pre_trainer.yaml`. + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `metrics.enable_tensorboard` | `false` | Enables TensorBoard logging. | +| `metrics.save_tb_folder` | `tb` | Subfolder name (typically under the job dump directory in TorchTitan layouts). | + +**Launch TensorBoard locally** + +```bash +tensorboard --logdir +``` + +Point `` at the Megatron `tensorboard` directory under the experiment root, or at the TorchTitan metrics folder that contains the `save_tb_folder` subtree. + +--- + +## 4. MLflow + +MLflow integration is **Megatron-only** in the paths described here. + +**Module** + +Configuration file: `primus_megatron_module.yaml` + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `disable_mlflow` | `true` | When `false`, MLflow run setup runs on the **last** global rank (`world_size - 1`). | +| `mlflow_run_name` | `null` | If unset when enabled, defaults to `{work_group}_{user_name}`. | +| `mlflow_experiment_name` | `null` | Passed to `mlflow.set_experiment` when set. | + +**Startup behavior** + +Configuration file: `primus/backends/megatron/training/global_vars.py` + +- Logs training `args` as parameters. +- Logs filtered environment variables with an `env__` prefix. +- Collects git metadata, sets MLflow source tags, and writes `system/git_metadata.json` as a run artifact. + +**Environment** (typical Databricks / hosted tracking) + +| Variable | Role | +|----------|------| +| `DATABRICKS_HOST` | Checked by the Megatron trainer when MLflow is enabled; a warning is printed if unset. | +| `DATABRICKS_TOKEN` | Authentication for Databricks-hosted tracking (see environment reference). | +| `MLFLOW_TRACKING_URI` | Tracking server URI; optional depending on deployment. | + +--- + +## 5. Training metrics + +### Megatron + +Configuration file: `trainer_base.yaml` + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `log_interval` | `100` | Steps between standard training log lines. | +| `log_throughput` | `false` | Log throughput metrics. | +| `log_avg_skip_iterations` | `2` | Skip initial iterations when computing averages. | +| `log_avg_reset_interval` | `10` | Interval for resetting running averages. | +| `log_params_norm` | `false` | Log parameter norm. | +| `log_num_zeros_in_grad` | `false` | Log count of zero gradients. | +| `log_progress` | `false` | Progress-style logging. | +| `timing_log_level` | `0` | Timing log verbosity. | +| `timing_log_option` | `minmax` | Timing aggregation option. | + +### TorchTitan + +Configuration file: `pre_trainer.yaml`. + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `metrics.log_freq` | `10` | Metric logging frequency (steps). | +| `metrics.disable_color_printing` | `false` | Disable colored console metrics. | +| `metrics.save_for_all_ranks` | `false` | Save metrics from every rank vs. reduced ranks. | + +--- + +## 6. Profiling + +### Megatron + +Configuration files: `trainer_base.yaml` and `primus_megatron_module.yaml` + +| Parameter | Source | Default | Description | +|-----------|--------|---------|-------------| +| `profile` | `trainer_base.yaml` | `false` | Enables Megatron profiling path; also forces TensorBoard on when `true`. | +| `use_pytorch_profiler` | `trainer_base.yaml` | `false` | Use PyTorch profiler integration. | +| `profile_ranks` | `trainer_base.yaml` | `[0]` | Ranks to profile. | +| `profile_step_start` | `trainer_base.yaml` | `10` | First step to profile. | +| `profile_step_end` | `trainer_base.yaml` | `12` | Last step to profile. | +| `record_memory_history` | `trainer_base.yaml` | `false` | Record memory history. | +| `memory_snapshot_path` | `trainer_base.yaml` | `snapshot.pickle` | Memory snapshot file name. | +| `disable_profiler_activity_cpu` | `primus_megatron_module.yaml` | `false` | Disable CPU activities in the profiler. | +| `torch_profiler_record_shapes` | `primus_megatron_module.yaml` | `true` | Record tensor shapes. | +| `torch_profiler_with_stack` | `primus_megatron_module.yaml` | `true` | Capture Python stacks. | +| `torch_profiler_use_gzip` | `primus_megatron_module.yaml` | `false` | Gzip profiler traces. | + +### TorchTitan + +Configuration file: `pre_trainer.yaml`. + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `profiling.enable_profiling` | `false` | Master profiling toggle. | +| `profiling.profile_freq` | `10` | How often to capture traces. | +| `profiling.enable_memory_snapshot` | `false` | Enable memory snapshots. | +| `profiling.save_memory_snapshot_folder` | `memory_snapshot` | Output folder for snapshots. | +| `profiling.save_traces_folder` | `profile_traces` | Folder for profiler traces. | + +--- + +## 7. ROCm memory monitoring + +Configured in `primus/configs/modules/megatron/primus_megatron_module.yaml` and applied in the Megatron trainer when logging throughput. + +| Parameter | Default | Description | +|-----------|---------|-------------| +| `use_rocm_mem_info` | `false` | When `true`, collect ROCm memory information via `rocm-smi` on **every** iteration that hits the throughput logging branch. | +| `use_rocm_mem_info_iters` | `[1, 2]` | When `use_rocm_mem_info` is `false`, `rocm-smi` runs only on these iteration numbers (same branch). | + +Collection is evaluated where `log_throughput` drives the extended iteration log (see `primus/backends/megatron/patches/training_log/print_rank_last_patches.py`): enable `log_throughput` in `trainer_base.yaml` (or overrides) when you need ROCm memory lines in the training log. + +--- + +## 8. Experiment snapshots + +**On disk (every run)** + +- **Experiment root**: `{workspace}/{work_group}/{user_name}/{exp_name}` is created at config load time (`PrimusConfig`). +- **Per-rank logs**: `{exp_root}/logs/{module_name}/rank-{rank}/` with rotated level-specific files. +- **Checkpoints**: Megatron uses `{exp_root}/checkpoints` (trainer sets `save` to this path). +- **TensorBoard / WandB**: Under `exp_root` as described above when those features are enabled. + +**MLflow** (Megatron, when enabled): Parameters, environment snapshot, and git metadata artifact provide a structured record of the run configuration and repository state. + +**Resolved configuration** + +The launcher and parser accept `--export_config`, but the default core training path (`primus/cli/subcommands/train.py` into `PrimusRuntime`) does not currently write a resolved YAML file. Archive the submitted experiment YAML, any referenced presets, launcher config, and runtime logs with each run. Treat resolved-config export as a legacy or future capability unless your deployment has implemented it on the core runtime path. diff --git a/docs/05-operations/security.md b/docs/05-operations/security.md new file mode 100644 index 000000000..c51d362d0 --- /dev/null +++ b/docs/05-operations/security.md @@ -0,0 +1,154 @@ +# Security considerations + +This document describes security-relevant properties of Primus as a **YAML-driven training framework** for AMD GPUs (ROCm, RCCL, containers). It is intended for operators, platform engineers, and security reviewers. It does not replace organizational policies, threat models, or vendor hardening guides. + +**Related documentation:** [Environment variables](../03-configuration-reference/environment-variables.md), [Installation](../01-getting-started/installation.md), [CLI reference](../02-user-guide/cli-reference.md). + +--- + +## 1. Overview + +| Aspect | Description | +|--------|-------------| +| Role | Primus orchestrates distributed **training** jobs; it is **not** a general user-facing network service. | +| Authentication / authorization | **No built-in** authentication, authorization, or multi-tenant isolation in Primus itself. | +| Responsibility | **Security posture is determined by** the scheduler, container runtime, network, storage, identity systems, and operational practices of the deployment environment. | + +Treat Primus like privileged infrastructure software: run it on appropriately isolated hosts and networks, and govern secrets and data the same way you would for large-scale ML training elsewhere. + +--- + +## 2. Secrets management + +Secrets are commonly passed as **environment variables** consumed by Primus, launchers, or third-party libraries. + +| Variable (examples) | Typical use | +|---------------------|-------------| +| `HF_TOKEN` | Hugging Face token for **gated models** and authenticated downloads. | +| `WANDB_API_KEY` | Weights & Biases API key for experiment logging. | +| `DATABRICKS_HOST` / `DATABRICKS_TOKEN` | Databricks or MLflow-related credentials when those integrations are used. | + +**Practices** + +| Practice | Detail | +|----------|--------| +| Do not hardcode secrets | Avoid putting tokens or passwords directly in YAML, shell history, or committed scripts. | +| Prefer indirection | Use **`${VAR}`** substitution in configs to reference environment-injected values rather than literals. | +| Slurm | Use **`--export`** deliberately; prefer site-specific **secret injection** or **credential helpers** where available. | +| Containers | Pass secrets with **`--env`** or via **`runner/.primus.yaml`** env forwarding—never bake them into images. | +| Rotation | Rotate API keys and tokens on a schedule and after personnel or scope changes. | + +A broader catalog of variables appears in [Environment variables](../03-configuration-reference/environment-variables.md). + +--- + +## 3. Container security + +Primus-oriented container runs often require **elevated access** so ROCm, profilers, and high-performance networking behave correctly. + +**Common high-privilege options** + +| Option | Typical purpose | +|--------|-----------------| +| `--privileged true` | Broad device access (often required for ROCm workflows on some setups). | +| `--cap-add SYS_PTRACE` | Debugging and profiling tooling. | +| `--cap-add CAP_SYS_ADMIN` | Administrative operations expected by parts of the ROCm/tooling stack. | +| `--security-opt seccomp=unconfined` | Relaxes seccomp constraints for compatibility with drivers and tools. | +| `--ipc host` | Shared memory semantics for large tensors and collectives. | +| `--network host` | Host networking—frequently used for **multi-node RCCL** performance and simplicity. | + +**Device access (examples)** + +| Device | Role | +|--------|------| +| `/dev/kfd` | ROCm kernel interface. | +| `/dev/dri` | GPU render nodes. | +| `/dev/infiniband` | InfiniBand character devices when using IB. | + +**Risks** + +| Risk | Why it matters | +|------|----------------| +| Privileged containers | Substantial **host** access; container escape or compromise has high impact. | +| Host networking | Exposes the container to the **host’s network namespace**; services may bind broadly. | +| Shared IPC | Potential for **cross-process interference** or information leakage if workloads share hosts improperly. | + +**Mitigations** + +| Mitigation | Detail | +|------------|--------| +| Dedicated training nodes | Run training on **isolated** machines rather than mixed with user-facing services. | +| Network controls | Apply **firewall rules** and **segmentation** so only required ports and peers are reachable. | +| Trusted images | Pull from **trusted registries**, pin digests, and verify image provenance. | +| Monitoring | Track **CPU, memory, GPU, and network** usage; alert on anomalous processes or egress. | + +--- + +## 4. Third-party dependencies + +Primus integrates **third-party submodules** and Python packages; each carries its own license and maintenance cadence. + +**Representative submodules** + +| Component | Notes (non-exhaustive) | +|-----------|-------------------------| +| Megatron-LM | MIT License; NVIDIA upstream. | +| TorchTitan | Apache 2.0; PyTorch / Meta ecosystem. | +| MaxText | Apache 2.0; Google upstream. | +| Megatron-Bridge | NVIDIA NeMo ecosystem. | +| Emerging-Optimizers | NVIDIA NeMo ecosystem. | +| HummingbirdXT | AMD AGI ecosystem. | + +**Python dependencies** + +Runtime tooling often includes packages such as **loguru**, **wandb**, **nltk**, **matplotlib**, **mlflow**, and others as declared in project requirements—verify the canonical list in the repository’s `requirements.txt` (or lockfile) for your revision. + +**Recommendations** + +| Recommendation | Rationale | +|----------------|-----------| +| Pin versions | Reproducible builds and controlled upgrade paths. | +| Update submodules | Security and correctness fixes flow from upstream projects. | +| Monitor advisories | Subscribe to upstream security notices for frameworks you enable. | + +--- + +## 5. Network security + +| Property | Detail | +|----------|--------| +| RCCL / NCCL traffic | **Not encrypted** at the application layer; assumes a **trusted network path**. | +| Coordination | **`MASTER_ADDR`** and **`MASTER_PORT`** should reside on a **private** or otherwise **trusted** segment. | +| InfiniBand | Often on a **dedicated fabric**; still treat adjacent compromised hosts as in-scope for lateral movement. | +| TLS / mTLS | Primus does **not** provide TLS or mTLS for inter-node training traffic by default. | + +For physical and logical networking topics, see [Multi-node networking](../04-technical-guides/multi-node-networking.md). + +--- + +## 6. Data security + +| Asset | Consideration | +|-------|-----------------| +| Training data | May include **PII**, licensed corpora, or export-controlled material—classify and restrict accordingly. | +| Checkpoints | Contain **full model state**; treat as sensitive intellectual property. | +| Storage permissions | Use **least privilege** on shared filesystems and object stores. | +| `HF_TOKEN` | Grants access to **gated** Hugging Face assets—protect like any other long-lived credential. | + +Checkpoint formats and operational practices are described in [Checkpoint management](../04-technical-guides/checkpoint-management.md). + +--- + +## 7. What is not verified + +The following items reflect **typical gaps** in public-facing evidence for many research and infrastructure codebases; confirm against your organization’s audits and CI for your fork and deployment. + +| Topic | Status (evidence-based caveat) | +|-------|--------------------------------| +| Independent security audit | **No** comprehensive third-party audit of this codebase is asserted here. | +| CI secrets scanning | **No** guarantee of automated secret detection in CI unless your pipeline adds it. | +| Dependency vulnerability scanning | **No** guarantee of continuous SCA unless your pipeline adds it. | +| Container images | Images may contain **unpatched** OS or Python packages—scan and rebuild on a schedule. | +| RCCL / NCCL traffic | **Not** encrypted or mutually authenticated by default; rely on network trust boundaries. | + +Use this section as a checklist for **your** production controls: add scanning, signing, policy-as-code, and periodic reviews appropriate to your threat model. diff --git a/docs/05-operations/troubleshooting.md b/docs/05-operations/troubleshooting.md new file mode 100644 index 000000000..313a0a634 --- /dev/null +++ b/docs/05-operations/troubleshooting.md @@ -0,0 +1,193 @@ +# Troubleshooting guide + +This guide is the primary reference for diagnosing and resolving common failures when running Primus on AMD GPUs (ROCm, RCCL, Docker). It complements the [CLI reference](../02-user-guide/cli-reference.md), [Preflight](../02-user-guide/preflight.md), [Benchmarking](../02-user-guide/benchmarking.md), and [Configuration system](../02-user-guide/configuration-system.md) documentation. + +--- + +## 1. Diagnostic tools + +Use these tools before scaling a job or when a failure is hard to localize. + +| Tool | Purpose | +|------|---------| +| `primus-cli --debug` | Enables verbose logging in `primus-cli` for command construction, delegation, and runtime details. | +| `primus-cli --dry-run` | Prints the commands Primus would run without executing them—useful to verify wrappers, paths, and MPI/launcher wiring. | +| `--export_config ` | Parsed by the training config parser, but not written by the current default `PrimusRuntime` path. Treat it as legacy/future functionality unless your deployment has implemented it. | +| `NCCL_DEBUG=INFO` | Surfaces detailed RCCL/NCCL connection and collective logs (set in the job environment). | +| `primus-cli direct -- preflight --host --gpu --network` | Fast host, GPU, and network validation. See [Preflight](../02-user-guide/preflight.md). | +| `PRIMUS_PATCHES=none` | Disables Primus patches to the selected backend to isolate whether a failure is Primus-specific or upstream. | + +**Examples** + +```bash +# Verbose CLI + dry run (no training executed) +primus-cli --debug --dry-run direct -- train pretrain --config path/to/config.yaml + +# Preflight: environment validation only +primus-cli direct -- preflight --host --gpu --network + +# RCCL/NCCL verbose logs in the training process environment +export NCCL_DEBUG=INFO + +# Isolate backend vs. Primus patch layer +export PRIMUS_PATCHES=none +``` + +For end-to-end checks including optional performance probes, see [Preflight](../02-user-guide/preflight.md) (`preflight --perf-test`). + +--- + +## 2. Out of memory (OOM) errors + +**Symptoms:** HIP/CUDA OOM messages, worker processes killed by the OOM killer, or abrupt exits during forward/backward. + +**Primary levers and mitigations** + +| Approach | What to change | +|----------|------------------| +| Reduce per-device activation memory | Lower **`micro_batch_size`** (often the first knob). | +| Shard weights/activations across devices | Increase **tensor parallelism** (`tensor_model_parallel_size` or `parallelism.tensor_parallel_degree`, depending on backend). See [Parallelism configuration](../04-technical-guides/parallelism-configuration.md). | +| Trade compute for memory | **Activation recomputation:** `recompute_granularity: full`, `recompute_method: uniform`, `recompute_num_layers: `. | +| Shard optimizer state | **`use_distributed_optimizer: true`** (Megatron-style stacks). | +| FSDP / sharded data parallel | Megatron: **`use_torch_fsdp2: true`**. TorchTitan: increase **`data_parallel_shard_degree`**. | +| CPU offload | **`optimizer_cpu_offload: true`** where supported. | +| Sequence length | Reduce **maximum sequence length** if the workload allows. | +| MoE / scratch memory | Set **`HSA_NO_SCRATCH_RECLAIM=1`** to reduce scratch-memory conflicts on some MoE workloads. | +| Plan before you run | **`primus-cli ... -- projection memory --config `**—see [Projection](../02-user-guide/projection.md). | + +**Related references:** [Parallelism strategies](../04-technical-guides/parallelism-strategies.md), [Performance tuning](../04-technical-guides/performance-tuning.md), [Megatron parameters](../03-configuration-reference/megatron-parameters.md), [TorchTitan parameters](../03-configuration-reference/torchtitan-parameters.md). + +--- + +## 3. Distributed communication failures + +**Symptoms:** Hangs at initialization, NCCL/RCCL timeouts, connection errors, or inconsistent ranks. + +| Cause | What to verify / fix | +|-------|----------------------| +| Wrong network interface | Set `NCCL_SOCKET_IFNAME` to the correct interface; exclude virtual interfaces, e.g. `^docker0,lo`. | +| `MASTER_ADDR` unreachable | From every node, resolve DNS/IP consistently; verify firewalls and routing. | +| Port already in use | Change **`MASTER_PORT`** to a free port on all nodes. | +| InfiniBand not used or missing | Confirm `/dev/infiniband` exists where expected; run `ibstat`; ensure IB kernel modules are loaded. See [Multi-node networking](../04-technical-guides/multi-node-networking.md). | +| Timeout too aggressive | Megatron: increase **`distributed_timeout_minutes`**. TorchTitan: increase **`comm.init_timeout_seconds`**. | +| Mismatched world size | Align **`NNODES`**, **`GPUS_PER_NODE`**, and launcher settings across **all** nodes. | + +**Debugging** + +```bash +export NCCL_DEBUG=INFO +``` + +**Validation** + +```bash +primus-cli direct -- preflight --network +primus-cli direct -- benchmark rccl --op all_reduce +``` + +Collective behavior and RCCL roles are summarized in [Collective operations](../04-technical-guides/collective-operations.md). + +--- + +## 4. Container issues + +**Symptoms:** Permission denied on devices, GPUs not visible inside the container, immediate exit, or RCCL failures only under Docker. + +| Symptom | Typical cause | Mitigation | +|---------|-----------------|------------| +| GPU not visible | Devices not passed through | Ensure **`--device /dev/kfd`** and **`--device /dev/dri`** (Primus container mode typically sets these). | +| Permission denied on GPU | Group membership / permissions | Add **`--group-add video`**; ensure the user has **video/render** access on the host. | +| InfiniBand missing in container | Device not mounted | Add **`--device /dev/infiniband`** (and related uverbs devices as required by your site). | +| Debugger/profiler failures | Missing capabilities | **`--cap-add SYS_PTRACE`** and **`--cap-add CAP_SYS_ADMIN`** are required for many ROCm tooling paths. | +| Driver/library mismatch | Image vs. host ROCm | Match **container image ROCm** to **host ROCm driver** version. | +| Data or code not found | Bind mounts | Use **`--volume /host/path:/container/path`** for datasets and workspace. | +| Env vars missing in container | Forwarding | Check **`runner/.primus.yaml`** `container.options.env` for auto-forwarded variables; add extras with **`--env KEY=VALUE`**. | + +Installation and container-oriented setup are covered in [Installation](../01-getting-started/installation.md). + +--- + +## 5. Configuration errors + +**Symptoms:** YAML parse failures, "unknown key" or type errors, silent wrong behavior after edits. + +| Issue | Resolution | +|-------|------------| +| Unset `${VAR}` | `${VAR}` with no default fails if `VAR` is unset. Use **`${VAR:default}`** or **export** the variable before launch. | +| Broken `extends:` chain | Confirm every referenced file exists and paths resolve relative to the expected directory. | +| Wrong parameter name | Cross-check backend docs: [Megatron](../03-configuration-reference/megatron-parameters.md), [TorchTitan](../03-configuration-reference/torchtitan-parameters.md), [MaxText](../03-configuration-reference/maxtext-parameters.md), [Megatron Bridge](../03-configuration-reference/megatron-bridge-parameters.md). | +| Override not applied | Review merge order: **CLI > experiment overrides > module preset with model preset additions**; duplicate top-level module keys win over model preset keys. See [Configuration system](../02-user-guide/configuration-system.md). | +| Effective config unknown | Use **`--dry-run`** to inspect the launch command and manually trace the experiment YAML, module preset, model preset, and overrides. Resolved-config export is not currently written by the default core runtime. | + +--- + +## 6. Backend-specific issues + +### Megatron + +| Issue | Mitigation | +|-------|------------| +| Suspected Primus patch interaction | `export PRIMUS_PATCHES=none` and retry with vanilla Megatron behavior. | +| Custom kernel compile failures | `disable_compile_dependencies: true` skips custom kernel compilation where applicable. | +| Wrong third-party path | Set **`BACKEND_PATH`** to override third-party resolution. | + +### TorchTitan + +| Issue | Mitigation | +|-------|------------| +| Submodule drift | `git submodule update --recursive` so `third_party/torchtitan` matches the Primus revision you run. | +| `torch.compile` instability | `export TORCH_COMPILE_DISABLE=1` or set **`compile.enable: false`** in config. | + +### MaxText (JAX) + +| Issue | Mitigation | +|-------|------------| +| JAX / jaxlib vs ROCm | Verify JAX and jaxlib builds match your ROCm stack. | +| XLA memory pressure | Tune **`XLA_PYTHON_CLIENT_MEM_FRACTION`** to cap client-side allocator use. | + +--- + +## 7. Performance issues + +**Symptoms:** Low tokens/sec, long iteration time, or poor scaling versus expectations. + +**Diagnosis** + +| Step | Command / action | +|------|-------------------| +| Compute sanity | `primus-cli direct -- benchmark gemm` | +| Interconnect | `primus-cli direct -- benchmark rccl --op all_reduce` | +| Broader probe | `primus-cli direct -- preflight --perf-test` (see [Preflight](../02-user-guide/preflight.md)) | + +**Common fixes** + +| Area | Action | +|------|--------| +| GEMM / kernels | Enable **hipBLASLt tuning** (multi-stage workflow—see [Performance tuning](../04-technical-guides/performance-tuning.md)). | +| Primus stack | Enable **`enable_primus_turbo: true`** where supported. | +| Communication overlap | **`overlap_grad_reduce: true`**, **`overlap_param_gather: true`** (when applicable to your backend). | +| MoE / scratch | Confirm **`HSA_NO_SCRATCH_RECLAIM=1`** when recommended for your model class. | +| FP8 | Enable FP8 when hardware and backend support it. | + +--- + +## 8. Data issues + +| Symptom | Checks | +|---------|--------| +| Mock data works; real data fails | **`data_path`** format: Megatron typically expects **`.bin` / `.idx`** pairs. See [Data preparation](../04-technical-guides/data-preparation.md). | +| Tokenizer errors | **`tokenizer_type`** and **`tokenizer_model`** must match (e.g., Hugging Face tokenizer ID for `HuggingFaceTokenizer`). | +| Hugging Face download failures | Set **`HF_TOKEN`** for gated models; verify outbound network and cache directories. | + +--- + +## 9. Known limitations + +| Area | Note | +|------|------| +| MaxText | Parameter completeness depends on upstream MaxText **`base.yml`**; some keys are inherited from upstream defaults. | +| Megatron Bridge | Recipe parameters may be loaded dynamically; not every key appears in static reference tables. | +| HummingbirdXT | Less mature than other backends; expect sharper edges in configs and tooling. | +| Primus-Turbo | Requires a **separate installation** step; not always present by default. | + +For terminology, see the [Glossary](../01-getting-started/glossary.md). For checkpoint-related failures, see [Checkpoint management](../04-technical-guides/checkpoint-management.md). diff --git a/docs/06-developer-guide/README.md b/docs/06-developer-guide/README.md new file mode 100644 index 000000000..a8c21c5a6 --- /dev/null +++ b/docs/06-developer-guide/README.md @@ -0,0 +1,17 @@ +# Developer guide + +For contributors and maintainers. + +- [Architecture](architecture.md): system design, runtime, backends, patch system +- [Contributing](contributing.md): development setup, code style, PR process +- [Testing](testing.md): test types, running tests, CI pipeline +- [Extending backends](extending-backends.md): adding new training backends +- [Adding models](adding-models.md): adding model configurations per backend +- [Model support matrix](model-support-matrix.md): supported models per backend and GPU +- [CLI architecture](cli-architecture.md): CLI internals: subcommand discovery, dispatch, and launch wrappers +- [Backend patch notes](backend-patch-notes.md): Primus-specific backend arguments and the files they patch +- [Tooling](tooling.md): auxiliary analysis, benchmarking, visualization, and diagnostics tools + +--- + +[← Documentation home](../README.md) diff --git a/docs/06-developer-guide/adding-models.md b/docs/06-developer-guide/adding-models.md new file mode 100644 index 000000000..d76010615 --- /dev/null +++ b/docs/06-developer-guide/adding-models.md @@ -0,0 +1,379 @@ +# Adding model configurations + +This guide explains how to add **model configuration YAML** for each Primus training backend. Model presets live under `primus/configs/models//` and are referenced from **experiment** YAML under `examples//configs/...`. Backend-specific parameter references: + +- [Megatron parameters](../03-configuration-reference/megatron-parameters.md) +- [TorchTitan parameters](../03-configuration-reference/torchtitan-parameters.md) +- [MaxText parameters](../03-configuration-reference/maxtext-parameters.md) +- [Megatron Bridge parameters](../03-configuration-reference/megatron-bridge-parameters.md) + +--- + +## Overview: Three-layer configuration + +For each backend, Primus composes configuration in three layers: + +1. **Experiment config** (entry point): `examples//configs//.yaml`—selects `framework`, `config` (module preset), `model` (model preset), and `overrides`. +2. **Module config** (trainer defaults): `primus/configs/modules//.yaml`—training loop defaults, logging, optimizer blocks, and backend-specific knobs. +3. **Model config** (architecture and assets): `primus/configs/models//.yaml`—architecture fields and tokenizer or Hugging Face paths, shaped differently per backend (see sections below). + +At runtime, `modules..model: .yaml` resolves to `primus/configs/models//.yaml` and is merged into module parameters before the backend adapter converts them. + +--- + +## Adding a Megatron model + +### How Megatron configs are wired + +1. **Experiment config** (entry point): + + ```yaml + # examples/megatron/configs/MI300X/llama3.1_8B-BF16-pretrain.yaml + modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml # module-level trainer config + + # model to run + model: llama3.1_8B.yaml # model config name + ``` + +2. **Module config** (trainer-level defaults): `primus/configs/modules/megatron/pre_trainer.yaml`—extends shared bases and sets Megatron training defaults. + +3. **Model config** (architecture + tokenizer): + + ```yaml + # primus/configs/models/megatron/llama3.1_8B.yaml + extends: + - llama3_8B.yaml + + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: meta-llama/Llama-3.1-8B + + max_position_embeddings: 131072 + ``` + +At runtime, `modules.pre_trainer.model: llama3.1_8B.yaml` resolves to `primus/configs/models/megatron/llama3.1_8B.yaml`. The `extends` chain pulls in parent files (for example `llama3_8B.yaml` → `llama3_base.yaml` → `llama_base.yaml`). + +### Files you typically add + +| Artifact | Purpose | +| -------- | ------- | +| **Model preset** (required) | New YAML under `primus/configs/models/megatron/`—architecture, tokenizer, and optional `extends`. | +| **Experiment config** (required) | New or copied YAML under `examples/megatron/configs/MI300X/` or `MI355X/`—points `model:` at your preset and sets `overrides` (batch size, precision, parallelism, `mock_data`, and so on). | +| **Module preset** (optional) | Only if you need trainer defaults that differ from `pre_trainer.yaml`—new file under `primus/configs/modules/megatron/` and reference it as `config:` in the experiment. | + +### Example: TinyLlama 1.1B from Hugging Face + +Assume Hugging Face repo `TinyLlama/TinyLlama-1.1B-Chat-v1.0` is not yet represented in Primus. You can add a local model preset (not necessarily committed upstream) as follows. + +**1. Decide the architecture** + +Because TinyLlama is not shipped as a Megatron preset in Primus, you can: + +- **Option A (recommended):** extend `language_model.yaml` and set all architecture fields explicitly. +- **Option B:** extend the closest existing model (for example a LLaMA-style preset) and override differing fields. + +**2. Map Hugging Face `config.json` to Megatron keys** + +Read from the Hugging Face model (typically `config.json` or the model card): + +| Hugging Face / concept | Megatron YAML (typical keys) | +| ---------------------- | ---------------------------- | +| `hidden_size` | `hidden_size` | +| `intermediate_size` | `ffn_hidden_size` | +| `num_attention_heads` | `num_attention_heads` | +| `num_hidden_layers` | `num_layers` | +| `num_key_value_heads` | Use with `num_attention_heads` to set `num_query_groups` (often `num_attention_heads / num_key_value_heads`) | +| `max_position_embeddings` | `max_position_embeddings` | + +**3. Create `tinyllama_1.1B.yaml`** + +Path: `primus/configs/models/megatron/tinyllama_1.1B.yaml` + +```yaml +extends: + - language_model.yaml # generic Megatron language model base + +tokenizer_type: HuggingFaceTokenizer +tokenizer_model: TinyLlama/TinyLlama-1.1B-Chat-v1.0 + +hidden_size: 2048 +ffn_hidden_size: 5632 # intermediate_size in HF config.json +num_attention_heads: 32 +num_layers: 22 # num_hidden_layers in HF config.json +num_query_groups: 8 # e.g. 32 / 4 if HF has 4 KV heads + +max_position_embeddings: 2048 +position_embedding_type: rope +``` + +**4. Point an experiment at the new model** + +Copy an existing experiment (for example `examples/megatron/configs/MI300X/llama3.1_8B-BF16-pretrain.yaml`) and set `model:` to your preset. Use **mock data** first for a quick smoke test: + +```yaml +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:tinyllama_1.1B-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: tinyllama_1.1B.yaml + overrides: + save: null + disable_last_saving: true + stderr_sink_level: DEBUG + + mock_data: true + train_iters: 50 + micro_batch_size: 2 + global_batch_size: 128 + + seq_length: 2048 +``` + +**5. Run verification** + +```bash +./primus-cli direct -- \ + train pretrain \ + --config examples/megatron/configs/MI300X/tinyllama_1.1B-pretrain.yaml +``` + +Confirm in logs that `framework` is `megatron`, the resolved model file is `tinyllama_1.1B.yaml`, and the tokenizer matches your preset. + +### Megatron checklist + +- [ ] Choose an appropriate base under `primus/configs/models/megatron/` (`language_model.yaml` or a close LLaMA-style model). +- [ ] Set `tokenizer_type`, `tokenizer_model`, and architecture fields aligned with Hugging Face. +- [ ] Add or update an experiment YAML under `examples/megatron/configs/...` with `model: .yaml`. +- [ ] Run `./primus-cli direct -- train pretrain --config ...` to validate resolution and a short run. + +--- + +## Adding a TorchTitan model + +### How TorchTitan configs are wired in Primus + +1. **Experiment config**: + + ```yaml + # examples/torchtitan/configs/MI300X/llama3.1_8B-BF16-pretrain.yaml + modules: + pre_trainer: + framework: torchtitan + config: pre_trainer.yaml + + model: llama3.1_8B.yaml + overrides: + training: + local_batch_size: 4 + seq_len: 8192 + mock_data: false + steps: 50 + ``` + +2. **Module config**: `primus/configs/modules/torchtitan/pre_trainer.yaml`—training defaults, quantization fragments, and TorchTitan-oriented structure. + +3. **Model config**—`job` and `model` sections consumed by the TorchTitan launcher: + + ```yaml + # primus/configs/models/torchtitan/llama3.1_8B.yaml + job: + dump_folder: "./outputs" + description: "Llama 3.1 8B training" + + model: + name: "llama3" + flavor: "8B" + hf_assets_path: "meta-llama/Llama-3.1-8B" + converters: + - primus_turbo + ``` + +At runtime, `modules.pre_trainer.model: llama3.1_8B.yaml` resolves to `primus/configs/models/torchtitan/llama3.1_8B.yaml`. The launcher uses `job` and `model` to wire the PyTorch model and training loop. + +### Mapping from Hugging Face to TorchTitan + +You need: + +- **Model family** (`model.name`): must match a family implemented in TorchTitan (for example `llama3`, `qwen3`, `deepseek_v3`). +- **Flavor** (`model.flavor`): a size key defined in TorchTitan code (for example `8B`, `70B`, `1.7b`)—see `third_party/torchtitan/torchtitan/models//`. +- **Hugging Face assets** (`model.hf_assets_path`): repository used to load weights and tokenizer. + +**Important limitations** + +- TorchTitan can only train models that are **implemented in the TorchTitan codebase**. The YAML under `primus/configs/models/torchtitan/` does **not** define new architectures; it selects and configures existing `*ModelArgs` entries. +- If a family or flavor is missing in TorchTitan, you cannot enable it with YAML alone—extend TorchTitan first, then add a Primus preset. + +### Example pattern: Qwen3 8B preset + +Qwen3 8B already exists in this repository as a TorchTitan preset and example. Use it as a pattern when adding a different TorchTitan model or flavor that is implemented upstream but not yet represented in Primus. + +**Existing file:** `primus/configs/models/torchtitan/qwen3_8b.yaml` + +```yaml +job: + dump_folder: "./outputs" + description: "Qwen 3 8B training" + +model: + name: "qwen3" + flavor: "8B" + hf_assets_path: "Qwen/Qwen3-8B" + converters: + - primus_turbo +``` + +**Field meanings:** + +- **`job.dump_folder`**—where TorchTitan writes logs and checkpoints for the job. +- **`job.description`**—free-form description shown in logs and metadata. +- **`model.name` / `model.flavor`**—the TorchTitan family and size key; both must exist in the TorchTitan code. +- **`model.hf_assets_path`**—Hugging Face repository used to load weights and tokenizer. +- **`model.converters`**—extra TorchTitan converters; `primus_turbo` is the default used in the Primus examples. + +The architecture itself lives in TorchTitan code, not in this YAML. For example, Qwen3 8B is declared in `third_party/torchtitan/torchtitan/models/qwen3/__init__.py`: + +```python +"8B": Qwen3ModelArgs( + vocab_size=151936, + max_seq_len=4096, + head_dim=128, + dim=4096, + n_layers=36, + n_heads=32, + n_kv_heads=8, + qk_norm=True, + hidden_dim=12288, + rope_theta=1000000, +), +``` + +The Primus preset only selects and configures such a definition. + +**Experiment snippet** (copy from an existing TorchTitan example and change `model:`): + +```yaml +modules: + pre_trainer: + framework: torchtitan + config: pre_trainer.yaml + model: qwen3_8b.yaml + overrides: + training: + local_batch_size: 4 + seq_len: 4096 + mock_data: true + steps: 50 +``` + +**Run:** + +```bash +./primus-cli direct -- \ + train pretrain \ + --config examples/torchtitan/configs/MI300X/qwen3_8B-pretrain.yaml +``` + +For a new model, create a new preset and example path that matches the upstream TorchTitan family/flavor you are adding. + +### TorchTitan checklist + +- [ ] Define `job` (for example `dump_folder`, `description`) and `model` (`name`, `flavor`, `hf_assets_path`, `converters`). +- [ ] Add an experiment under `examples/torchtitan/configs/...` referencing `model: .yaml`. +- [ ] Run `./primus-cli direct -- train pretrain --config ...` for a short job. + +--- + +## Adding a MaxText model + +MaxText (JAX) model presets in Primus are intentionally thin: they set **`model_name`** and **`tokenizer_path`** (and extend `model_base.yaml`) so MaxText can load its own architecture tables when available. + +**Typical model preset** + +Path pattern: `primus/configs/models/maxtext/.yaml` + +```yaml +extends: + - model_base.yaml + +model_name: "llama3-8b" +tokenizer_path: "meta-llama/Meta-Llama-3-8B" +``` + +Comments in `primus/configs/models/maxtext/model_base.yaml` explain that architecture parameters are resolved from MaxText’s `configs/models/.yml` when present, or from Primus overrides as appropriate. + +**Experiment wiring** + +Experiments reference the preset the same way as other backends, for example: + +```yaml +modules: + pre_trainer: + framework: maxtext + config: pre_trainer.yaml + model: llama3_8B.yaml +``` + +**Supported architectures** + +For the authoritative list of model names and architectures MaxText supports, see the [MaxText](https://github.com/AI-Hypercomputer/maxtext) repository and upstream documentation. Primus examples under `examples/maxtext/configs/MI300X/` and `MI355X/` illustrate which presets are exercised in this tree. + +--- + +## Adding a Megatron Bridge model (post-training) + +Megatron Bridge model presets are small YAML files that select a **recipe**, **flavor**, and **Hugging Face path**, plus optional dataset blocks. + +**Example preset** (`primus/configs/models/megatron_bridge/qwen3_8b.yaml`): + +```yaml +recipe: qwen.qwen3 +flavor: qwen3_8b_finetune_config +hf_path: Qwen/Qwen3-8B + +dataset: + dataset_name: "rajpurkar/squad" +``` + +| Field | Role | +| ----- | ---- | +| `recipe` | Logical recipe module (for example `qwen.qwen3`, `llama.llama3`). | +| `flavor` | Named configuration within the recipe (for example `qwen3_8b_finetune_config`). | +| `hf_path` | Hugging Face model id for weights and tokenizer. | + +**Experiment** (pattern from `examples/megatron_bridge/configs/`): + +```yaml +modules: + post_trainer: + framework: megatron_bridge + config: sft_trainer.yaml + model: qwen3_8b.yaml + overrides: + precision_config: bf16_mixed + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 +``` + +See [Megatron Bridge parameters](../03-configuration-reference/megatron-bridge-parameters.md) for the full override surface. + +--- + +## Testing new models + +Use a staged approach so failures are easy to localize. + +| Stage | Goal | Typical settings | +| ----- | ---- | ---------------- | +| **Mock or synthetic data** | Validate config resolution, tokenizer, and a few steps without real datasets. | Megatron: `mock_data: true`. TorchTitan: `training.mock_data: true`. MaxText: `dataset_type: "synthetic"` in overrides where applicable. Keep `train_iters` / `steps` small. | +| **Single-GPU** | Confirm numerics and memory before scaling. | Set tensor and pipeline parallelism to 1 in overrides; use one process / one device per your launcher docs. | +| **Multi-GPU** | Match production parallelism. | Set `tensor_model_parallel_size`, `pipeline_model_parallel_size`, expert / context parallel sizes, or MaxText `ici_*` / `dcn_*` fields as required by the model size and hardware. | + +Cross-check [Parallelism configuration](../04-technical-guides/parallelism-configuration.md) and [Model support matrix](./model-support-matrix.md) when moving from single-device to multi-device runs. diff --git a/docs/06-developer-guide/architecture.md b/docs/06-developer-guide/architecture.md new file mode 100644 index 000000000..a82520b5e --- /dev/null +++ b/docs/06-developer-guide/architecture.md @@ -0,0 +1,119 @@ +# Architecture overview + +This document describes how the Primus training framework is structured: CLI and configuration, the core runtime orchestrator, backend adapters, trainer lifecycle, and the patch system. + +## 1. System overview + +Primus is organized into three conceptual layers: runtime launch (how processes and GPUs are started), hooks and patches (environment and in-process adjustments), and task execution (CLI subcommands that drive training and utilities). + +``` +┌─────────────────────────────────────────────────────┐ +│ Runtime Layer (runner/) │ +│ direct | container | slurm │ +│ GPU detection, env setup, distributed launch │ +├─────────────────────────────────────────────────────┤ +│ Hook / Patch System │ +│ runner/helpers/hooks/ | primus/core/patches/ │ +│ Pre/post processing, runtime monkey-patches │ +├─────────────────────────────────────────────────────┤ +│ Task Execution Layer │ +│ primus/cli/subcommands/ │ +│ train | benchmark | preflight | projection │ +└─────────────────────────────────────────────────────┘ +``` + +The repository also provides shell entrypoints under `runner/` (for example `primus-cli-direct.sh`, `primus-cli-container.sh`, `primus-cli-slurm.sh`) that prepare the environment and invoke the Python CLI. + +## 2. CLI and plugin system + +- **Entry point:** `primus/cli/main.py` is the unified CLI entry. It discovers subcommand modules under `primus/cli/subcommands/` with `pkgutil.walk_packages`, skipping modules whose leaf name starts with `_`. +- **Registration contract:** Each subcommand module exposes `register_subcommand(subparsers)` and must return the configured parser. The parser must call `set_defaults(func=run)` so `main()` can dispatch to the handler. +- **Parsing:** The CLI uses the standard library `argparse` only (no Click or Typer). +- **Unknown arguments:** `main()` calls `parse_known_args()`. For selected subcommands (`train`, `projection`, `preflight`), trailing tokens are passed through to the handler as overrides; for other commands, unknown arguments are rejected. + +## 3. Configuration pipeline + +Configuration flows from experiment YAML to a resolved structure consumed by the runtime. + +1. **CLI** parses `--config` / `--exp` (required for train flows) pointing at an experiment YAML file. +2. **`load_primus_config()`** (used by `PrimusRuntime`) delegates to **`PrimusParser.parse()`** in `primus/core/launcher/parser.py`. The parser loads the experiment file via **`yaml_utils.parse_yaml_to_namespace()`**, which uses **`primus/core/config/yaml_loader.parse_yaml()`** for `${VAR}` / `${VAR:default}` substitution and `extends:` inheritance with deep merge. +3. **Per trainer module** (names containing `trainer`, for example `pre_trainer`): + - **`PresetLoader.load()`** loads the module preset from `primus/configs/modules//.yaml`. + - **`PresetLoader.load()`** loads the model preset from `primus/configs/models//.yaml`. + - Each preset is loaded through the same YAML pipeline (env substitution and `extends:` chains). +4. **`parse_platform()`** merges platform settings from `primus/configs/platforms/` (defaulting to `platform_azure.yaml` when the experiment omits `platform`). +5. **CLI overrides:** For `primus train`, `main()` passes `unknown_args` into the train handler. `PrimusRuntime` parses them with `parse_cli_overrides()` and **deep-merges** them into `module_config.params`. +6. **Result:** A resolved configuration where each module exposes a **`params`** namespace (`SimpleNamespace`) for training parameters, produced by `_normalize_module_for_runtime()` in `primus/core/config/primus_config.py`. + +The object returned from `load_primus_config()` is a lightweight `SimpleNamespace` (not `PrimusConfig`), with `modules` as a **list** of module configs, each tagged with a `.name` field. + +## 4. Core runtime (PrimusRuntime) + +`primus/core/runtime/train_runtime.py` defines **`PrimusRuntime`**, the main orchestrator for the new core training path. Execution for a single module follows this flow: + +1. **`load_primus_config()`** loads and validates the experiment; **`get_module_config()`** selects the requested module (for example `pre_trainer` or `post_trainer`). +2. **`_apply_overrides()`** merges CLI overrides into `module_config.params`. +3. **`_initialize_environment()`** ensures the data directory exists and calls **`setup_training_env()`** (Hugging Face cache and related setup). +4. **`_initialize_distributed_context()`** reads torchrun-style rank and master information via **`get_torchrun_env()`**. +5. **`_initialize_logging()`** initializes worker logging. +6. **`BackendRegistry.get_adapter(framework)`** resolves the **`BackendAdapter`** (lazy-importing `primus.backends.` if needed). +7. **`adapter.setup_backend_path()`** inserts the backend tree on `sys.path`. Resolution order: CLI `--backend_path`, then the `BACKEND_PATH` env var, then the default—`third_party/` under the repo root, followed by `$PRIMUS_THIRDPARTY_DIR` or `~/.cache/Primus/third_party` (the `primus-cli deps sync` location). +8. **`adapter.prepare_backend()`** runs backend setup hooks (via **`BackendRegistry.run_setup()`** by default). +9. **`adapter.convert_config(module_config.params)`** produces **`backend_args`** for the trainer. +10. **`run_patches(phase="build_args", ...)`** runs registered patches; backend version detection runs when patches first need it (**`adapter.detect_backend_version()`** via **`_get_backend_version()`**). +11. **`merge_namespace()`** merges `backend_args` into `module_config.params` (backend wins on conflicts); **`adapter.load_trainer_class(stage)`** resolves the trainer class (default stage `pretrain`). +12. **`TrainerClass(backend_args=backend_args)`** constructs the trainer. +13. **`run_patches(phase="setup")`** then **`trainer.setup()`**. +14. **`trainer.init()`**. +15. **`run_patches(phase="before_train")`** then **`trainer.train()`** then **`run_patches(phase="after_train")`** then **`trainer.cleanup()`**. +16. On failure, **`_safe_cleanup()`** calls **`trainer.cleanup(on_error=True)`** when possible. + +## 5. Backend system + +- **`BackendAdapter`** (`primus/core/backend/backend_adapter.py`) is the abstract integration surface. Subclasses implement **`convert_config()`**, **`load_trainer_class()`**, and **`detect_backend_version()`**. Shared behavior includes **`setup_backend_path()`** and a default **`prepare_backend()`** that runs registered setup hooks. +- **`BackendRegistry`** (`primus/core/backend/backend_registry.py`) maps backend names to adapter classes, supports **lazy import** of `primus.backends.`, and stores optional **setup hooks** per backend. +- **Registered adapters** (via each backend package’s `__init__.py` calling **`BackendRegistry.register_adapter()`**): **`megatron`**, **`torchtitan`**, **`maxtext`**, **`megatron_bridge`**, **`hummingbirdxt`**. +- Backend code lives under **`primus/backends//`**. Importing the package registers the adapter and any trainers or hooks that package defines. + +## 6. Trainer lifecycle + +- **`BaseTrainer`** (`primus/core/trainer/base_trainer.py`) defines the lifecycle. **`setup()`**, **`init()`**, and **`train()`** are **abstract** (subclasses must implement them); **`cleanup(on_error=False)`** is **optional**—it ships a default (no-op) implementation that subclasses may override. The constructor stores **`backend_args`** and reads distributed settings from **`get_torchrun_env()`**. +- Concrete trainers (for example Megatron or TorchTitan pretrain classes) subclass **`BaseTrainer`** and implement the abstract methods. +- **`PrimusRuntime`** drives **`setup` → `init` → `train` → `cleanup`**, with patch phases **`build_args`** (before the trainer is created), **`setup`**, and **`before_train`/`after_train`** (around `train`). No patch phase runs around `cleanup`—`after_train` fires before `cleanup` (see §4). + +## 7. Patch system + +- **`PatchRegistry`** (`primus/core/patches/patch_registry.py`) stores **`FunctionPatch`** objects keyed by backend and phase, with wildcard buckets (`None`) for patches that apply broadly. +- The **`@register_patch`** decorator registers a patch with metadata (priority, optional version patterns, tags). +- **`run_patches()`** (`primus/core/patches/patch_runner.py`) collects applicable patches, filters by **`PatchContext`**, sorts by **priority**, and runs handlers. It accepts an optional **`enabled_ids`** list; if omitted, behavior is controlled by **`PRIMUS_PATCHES`**: + - unset or **`all`**: all patches + - **`none`**: disable all + - comma-separated IDs: only those patches +- **Phases** used by the core runtime include **`build_args`**, **`setup`**, **`before_train`**, and **`after_train`**. +- Patch implementations are typically colocated with backends under **`primus/backends//patches/`**. + +## 8. Legacy runtime + +The legacy pretrain path—previously selected with **`PRIMUS_TRAIN_RUNTIME=legacy`** and backed by the **`primus/modules/`** stack (**`BaseModule`**-style composition)—has been **removed**. `primus/modules/` no longer contains any source code, and **`primus/cli/subcommands/train.py`** no longer reads `PRIMUS_TRAIN_RUNTIME` or resolves a legacy-vs-core runtime. + +All training now runs exclusively through the **core runtime**: both `primus train pretrain` and `primus train posttrain` construct a **`PrimusRuntime`** (**`primus/core/runtime/train_runtime.py`**). **`primus/pretrain.py`** now only provides shared backend-path / environment helpers (for example **`setup_backend_path()`**) used by the training and projection entry points; it no longer defines a `launch_pretrain_from_cli()` legacy launcher. + +## 9. Key source files + +| Path | Role | +|------|------| +| `primus/cli/main.py` | CLI entry, subcommand discovery, dispatch | +| `primus/cli/subcommands/train.py` | `train` subcommand; chooses core vs legacy pretrain; `posttrain` via `PrimusRuntime` | +| `primus/core/launcher/parser.py` | **`PrimusParser`**: experiment, platform, and module preset loading | +| `primus/core/config/preset_loader.py` | **`PresetLoader`**: load framework presets from `primus/configs/` | +| `primus/core/config/yaml_loader.py` | YAML load with env substitution and `extends` | +| `primus/core/config/primus_config.py` | **`load_primus_config()`**, **`get_module_config()`**, module normalization | +| `primus/core/runtime/train_runtime.py` | **`PrimusRuntime`**, **`TrainContext`** | +| `primus/core/backend/backend_adapter.py` | **`BackendAdapter`** ABC | +| `primus/core/backend/backend_registry.py` | **`BackendRegistry`** | +| `primus/core/trainer/base_trainer.py` | **`BaseTrainer`** ABC | +| `primus/core/patches/patch_registry.py` | **`PatchRegistry`**, **`@register_patch`** | +| `primus/core/patches/patch_runner.py` | **`run_patches()`**, **`PRIMUS_PATCHES`** parsing | +| `runner/primus-cli-*.sh` | Shell wrappers for direct, container, and Slurm launch | + +For a deep dive on the CLI internals (subcommand discovery, dispatch, and the launch wrappers), see [CLI Architecture](cli-architecture.md). For day-to-day contribution workflows (style, tests, CI), see [Contributing Guide](contributing.md) and [Testing Guide](testing.md). diff --git a/docs/06-developer-guide/backend-patch-notes.md b/docs/06-developer-guide/backend-patch-notes.md new file mode 100644 index 000000000..da8a68ad1 --- /dev/null +++ b/docs/06-developer-guide/backend-patch-notes.md @@ -0,0 +1,143 @@ +# Backend patch notes + +Primus integrates several large-model backends (Megatron-LM, TorchTitan, JAX MaxText, …) and applies a lightweight patch layer to keep configuration flags consistent with the Primus CLI. This page captures those backend-specific switches so they live alongside the rest of the documentation (instead of the historical `primus/README_patch.md` file). + +## How to read these notes + +- Start with the **Base Module Parameters** table below—every backend module inherits these knobs. +- Jump to the backend-specific section for details on extra CLI/config options and links to the patched source files. +- When editing configs or CLI presets, cross-reference the [Primus CLI Reference](../02-user-guide/cli-reference.md) so command examples and backend parameters stay in sync. + +## Supported models + +This section lists, at a high level, the model families Primus currently targets on each backend. For more details and configuration examples, refer to the backend-specific patch notes below. + +### Megatron-LM + +- **LLaMA family**: LLaMA2, LLaMA3, LLaMA3.1, LLaMA3.3, LLaMA4 (various sizes from 7B up to 405B+) +- **DeepSeek family**: DeepSeek-V2 (lite/base/full) and DeepSeek-V3 +- **MoE / Mixtral**: Mixtral-8x7B / 8x22B, large MoE configs (515B, 1T, 2T, 4T) and DeepSeek-style MoE +- **Qwen family**: Qwen2.5 (7B/72B) and Qwen3 (8B/30B/235B variants) +- **Other GPT-style models**: Grok1/2, GPT-OSS 20B and generic `language_model.yaml` + +### TorchTitan + +- **LLaMA family**: LLaMA3, LLaMA3.1, LLaMA3.3 (various sizes, including FP8 variants) +- **DeepSeek family**: DeepSeek-V3 (16B and 671B, FP8 and BF16 configs) +- **Qwen family**: Qwen3 small/medium models (0.6B, 1.7B, 32B) + +### JAX MaxText + +- **LLaMA family**: LLaMA2 (7B/70B), LLaMA3 (8B/70B), LLaMA3.3 (70B) +- **DeepSeek family**: DeepSeek-V2 16B +- **MoE / Mixtral**: Mixtral-8x7B +- **Other models**: Grok1 and additional MaxText-supported transformers (see MaxText docs for the full list) + +## Base module parameters + +All modules inherit the options defined in [`primus/configs/modules/module_base.yaml`](https://github.com/AMD-AGI/Primus/blob/main/primus/configs/modules/module_base.yaml): + +| Argument Name | Default Value | Description | +| ------------------- | ------------- | ------------------------------------------------------------------------------------------ | +| `trainable` | `false` | Whether the module participates in training. | +| `sink_level` | `null` | Global sink level for logging. Overrides `file_sink_level` and `stderr_sink_level` if set. | +| `file_sink_level` | `DEBUG` | Logging level for file sink (log files). | +| `stderr_sink_level` | `INFO` | Logging level for stderr/console output. | + +### Backend index + +- [Megatron-LM patch notes](#megatron-lm-patch-notes) +- [TorchTitan patch notes](#torchtitan-patch-notes) +- [JAX MaxText patch notes](#jax-maxtext-patch-notes) + +--- + +## Megatron-LM patch notes + +Primus keeps a curated patch layer on top of upstream Megatron-LM so CLI presets and configs can expose additional controls. Use this section with the [Base Module Parameters](#base-module-parameters) above for shared module parameters, and the [Primus CLI Reference](../02-user-guide/cli-reference.md) for CLI/config usage patterns. + +> ℹ️ The **Version** column maps to Primus internal patch tags (v0.x.y) so you know when a flag landed. + +### 1. Module-level parameters + +These arguments are introduced in the Megatron module logic (e.g., training loop, logging, resume logic). They are defined via patching and can be configured to control training behavior and logging utilities. + +| New Argument | Default Value | Version | Description | Patched Files | Notes | +| ------------------------------------ | ------------- | ------- | ---------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------ | +| `disable_tensorboard` | `true` | v0.1.0 | Whether to disable TensorBoard. Set to `false` if you want to enable profiling or torch trace. | NA | Required for timeline and performance debugging. | +| `disable_wandb` | `true` | v0.1.0 | Whether to disable Weights & Biases logging. | NA | Useful for internal benchmarking. | +| `disable_compile_dependencies` | `true` | v0.1.0 | Disables Megatron’s custom kernel compilation. Most ops are already covered by TE. | NA | Avoids redundant compilation steps. | +| `auto_continue_train` | `false` | v0.1.0 | Automatically resume training from the latest checkpoint if found in the `--save` path. | NA | Simplifies job restarts. | +| `disable_last_saving` | `false` | v0.1.0 | Skip saving the final checkpoint at the last iteration. | NA | Useful for profiling or benchmarking runs. | +| `no_fp8_weight_transpose_cache` | `false` | v0.2.0 | Disable the FP8 weight transpose cache to save memory. | `megatron.core.extensions.transformer_engine.TELinear`, `megatron.core.extensions.transformer_engine.TELayerNormColumnParallelLinear`, `megatron.core.extensions.transformer_engine.TEDelayedScaling` | May affect performance but reduce memory use. | +| `decoder_pipeline_manual_split_list` | `null` | v0.2.0 | Enable manual pipeline split in (interleaved) 1F1B pipeline parallelism. | `megatron.core.transformer.transformer_block.get_num_layers_to_build`, `megatron.core.transformer.transformer_layer.get_transformer_layer_offset` | Deprecated. Use `pipeline_model_parallel_layout` instead. | +| `pp_warmup` | `false` | v0.2.0 | Add fwd/bwd warmup to save iter1's time when pp degree is large. | NA | Can save much time for pipeline debug. | +| `dump_pp_data` | `false` | v0.2.0 | Enable dumping pp schedule data for visualization. | `megatron.core.pipeline_parallel.schedules.forward_step`, `megatron.core.pipeline_parallel.schedules.backward_step`, `megatron.core.pipeline_parallel.schedules.forward_backward_pipelining_with_interleaving`, `megatron.core.pipeline_parallel.schedules.forward_backward_pipelining_without_interleaving` | Useful for pipeline schedule visualization. | +| `disable_profiler_activity_cpu` | `false` | v0.2.0 | Disable CPU activity in torch profiling. | NA | If you only want to trace CUDA kernels and get a smaller trace JSON file, you can enable this option. However, if you plan to run with TraceLen, please do not enable it. more torch profiler args:
`torch_profiler_record_shapes: true`,
`torch_profiler_with_stack: true`,
`torch_profiler_use_gzip: true` | +| `use_rocm_mem_info` | `false` | v0.2.0 | Logging ROCm memory information in Megatron-LM Trainer | NA | If `use_rocm_mem_info = True`, ROCm memory information will be collected with `rocm-smi` at every iteration. | +| `use_rocm_mem_info_iters` | `[1,2]` | v0.2.0 | Logging ROCm memory information in Megatron-LM Trainer for some iterations | NA | If `use_rocm_mem_info = False`, ROCm memory information will be collected at the iterations specified in `use_rocm_mem_info_iters`. | +| `patch_zero_bubble` | `false` | v0.2.0 | Using Zero-Bubble pipeline parallism | `megatron.core.optimizer.ChainedOptimizer`, `megatron.core.pipeline_parallel.get_forward_backward_func`, `megatron.core.tensor_parallel.layers.LinearWithGradAccumulationAndAsyncCommunication`, `megatron.core.parallel_stat.default_embedding_ranks`, `megatron.core.parallel_stat.is_pipeline_last_stage`, `megatron.core.parallel_stat.is_rank_in_embedding_group`, `megatron.core.distributed.finalize_model_grads`, `megatron.core.transformer.transformer_layer.get_transformer_layer_offset` | If `patch_zero_bubble = True`, Zero bubble pipeline parallism will be enable to use. See more detail at [ZeroBubble User Guide](../../primus/backends/megatron/core/pipeline_parallel/zerobubble/README.md) | +| `disable_mlflow` | `true` | v0.3.0 | Track model development using MLflow | NA | Envs:
`export DATABRICKS_TOKEN=your_token`
`export DATABRICKS_HOST=your_host`
`export MLFLOW_TRACKING_URI=databricks`
`export MLFLOW_REGISTRY_URI=databricks-uc`
Arguments:
`mlflow_run_name: null`,
`mlflow_experiment_name: null` | +| `recompute_layer_ids` | `null` | v0.4.0 | Specify the exact IDs of layers to recompute, enabling more flexible memory reduction | NA | Using `recompute_layer_ids=[layer_id_0, layer_id_1,...]` together with `recompute_granularity=full`, where layer_id ranges from 0 to num_layers - 1. | +| `dataloader_mp_context` | `null` | v0.5.0 | Set `DataLoader.multiprocessing_context` to avoid SIGSEGV caused by fork()-hostile native state (RDMA MRs, HIP runtime, IPC handles). | `torch.utils.data.DataLoader.__init__` | Accepted values: `"forkserver"`, `"spawn"`, `"fork"`, or `null` (keep PyTorch default). Only takes effect when `num_workers > 0` and no explicit `multiprocessing_context` is passed. | + +--- + +### 2. Model-definition parameters + +These arguments affect the internal architecture or layer implementations. They are patched into the model construction logic and used for tuning or debugging specific variants. + +| New Argument | Default Value | Version | Description | Patched Files | Notes | +| ----------------------------------- | ------------- | ------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `disable_primus_topk_router` | `false` | v0.1.0 | Disable PrimusTopkRouter and use TopkRouter implemented by megatron. | `megatron.core.transformer.moe.router.TopKRouter` | Used to debug internal. | +| `moe_router_force_load_balancing` | `false` | v0.1.0 | Force token redistribution in MoE to achieve load balance across experts. | `megatron.core.transformer.moe.router.TopKRouter` | Use to debug MoE imbalance issues. | +| `use_deprecated_20241209_moe_layer` | `false` | v0.1.0 | Enable legacy MoE implementation for debugging/perf comparison. | `megatron.core.transformer.moe.moe_layer.MoELayer`, `megatron.core.transformer.moe.moe_layer.MoESubmodules`, `megatron.core.transformer.moe.experts.GroupedMLP`, `megatron.core.transformer.moe.experts.SequentialMLP`, `megatron.core.transformer.moe.experts.TEGroupedMLP`, `megatron.core.transformer.moe.router.TopKRouter` | Deprecated, used for internal testing only. | +| `moe_permute_fusion` | `false` | v0.1.0 | Permutation and unpermutation fusion. | `megatron.core.extensions.transformer_engine`, `megatron.core.transformer.moe.moe_utils` | Fuse permutation and unpermutation in moe layer. | +| `moe_use_fused_router_with_aux_score` | `false` | v0.2.0 | Fused router topk and calculation of moe aux loss score. Need Primus turbo backend | `megatron.core.transformer.moe.router.TopKRouter` | Used to reduce launch overhead of the small kernels in router. | + +### 3. Primus-Turbo related options + +| New Argument | Default Value | Version | Description | Patched Files | Notes | +| ----------------------------------- | ------------- | ------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------- | +| `use_turbo_gemm` | `false` | v0.8.0 | Use Primus-Turbo linear modules (`PrimusTurboLinear`, `PrimusTurboColumnParallelLinear`, `PrimusTurboRowParallelLinear`, `PrimusTurboLayerNormColumnParallelLinear`) in place of the TE linear modules. | `megatron.core.extensions.transformer_engine.TELinear`, `megatron.core.extensions.transformer_engine.TEColumnParallelLinear`, `megatron.core.extensions.transformer_engine.TERowParallelLinear`, `megatron.core.extensions.transformer_engine.TELayerNormColumnParallelLinear` | Accelerates dense GEMMs. Supports FP8 recipes (`tensorwise`, `blockwise`, `mxfp8`) and FP4 recipe (`mxfp4`). Replaces the deprecated `use_turbo_parallel_linear`. **Please set `enable_primus_turbo=True` first.** | +| `use_turbo_grouped_gemm` | `false` | v0.8.0 | Use Primus-Turbo grouped GEMM (`PrimusGroupedMLP` with `PrimusTurboColumnParallelGroupedLinear` / `PrimusTurboRowParallelGroupedLinear`) for MoE experts in place of `TEGroupedMLP`. | `megatron.core.transformer.moe.experts.TEGroupedMLP`, `megatron.core.extensions.transformer_engine.TEColumnParallelGroupedLinear`, `megatron.core.extensions.transformer_engine.TERowParallelGroupedLinear` | Accelerates MoE grouped GEMMs. Incompatible with `moe_use_legacy_grouped_gemm=True`. Required by Sync-Free MoE stage 2/3. Replaces the deprecated `use_turbo_grouped_mlp`. **Please set `enable_primus_turbo=True` first.** | +| `use_turbo_permute_padding` | `false` | v0.8.0 | Pad tokens of every experts to 16 or 32 multiple to reduce d2h and h2d. | `megatron.core.transformer.moe.token_dispatcher.MoEFlexTokenDispatcher`, `megatron.core.transformer.moe.experts.TEGroupedMLP` | Only effective under FP8/FP4 with `use_turbo_deepep=True`. Pad multiple is 16/32 (FP8, depending on recipe) or 32 (FP4). **Please set `enable_primus_turbo=True` first.** | +| `use_turbo_deepep` | `false` | v0.4.0 | Use Primus-turbo `DeepEPTokenDispatcher`. | `megatron.core.transformer.moe.token_dispatcher.MoEFlexTokenDispatcher` | Used Primus-Turbo DeepEP to accelerate MoE token dispatcher. **You must both set`enable_primus_turbo=True` and `use_turbo_deepep=True` to enable this function.** | +| `turbo_deepep_num_cu` | `32` | v0.4.0 | Set the number of CUs to use for Primus-Turbo DeepEP. | | 64 or 80 for ep8, 32 for ep16-64 is best practice. | +| `turbo_deepep_use_comm_stream` | `false` | v0.4.0 | Primus-Turbo DeepEP will use an internal stream to dispatch/combine when enabled, default used `current_stream` | | **Please both set`enable_primus_turbo=True` and `use_turbo_deepep=True` first** +| `turbo_sync_free_moe_stage` | `0` | v0.4.0 | Primus Sync-Free MoE has 4 stages. See [RFC: Primus-Megatron SyncFree MoE](https://github.com/AMD-AGI/Primus/issues/203) for more details. | | stage 2 is recommended for better performance. **Please set`enable_primus_turbo=True` first** | +| `use_turbo_mega_moe` | `false` | v0.8.0 | Replace the whole MoE layer with the FlyDSL-based fused Primus-Turbo MegaMoE layer (fused router + `dispatch_grouped_gemm` → SwiGLU → `grouped_gemm_combine`). | `megatron.core.transformer.moe.moe_layer.MoELayer` | EP-only: requires `tensor_model_parallel_size=1`, `params_dtype=bf16`, and an EP process group; asserts on unsupported router options (sequence/global aux loss, z-loss, sinkhorn, input jitter, expert bias). Needs a Primus-Turbo build with MegaMoE. See [MegaMoE guide](../04-technical-guides/mega-moe.md). **Please set `enable_primus_turbo=True` first.** | + +--- + +## TorchTitan patch notes + +TorchTitan integration uses the same Primus configuration surface (CLI flags + YAML) but exposes a few extra knobs via patches. Pair this with the [Base Module Parameters](#base-module-parameters) above for shared module parameters. + +| New Argument | Default Value | Version | Description | Patched Files | Notes | +| ------------ | ------------- | ------- | ----------- | ------------- | ----- | +| `primus_turbo.enable_embedding_autocast` | `true` | v0.4.0 | Automatically casts `nn.Embedding` outputs to the AMP dtype (e.g., bf16) during training so downstream layers stay in sync. | (Primus TorchTitan patch set) | Disable only if you manage casting manually. | + +--- + +## JAX MaxText patch notes + +Primus integrates JAX MaxText as a backend for running LLaMA and related transformer models on AMD GPUs. At the moment, Primus does not apply any additional patch-layer arguments on top of MaxText—the MaxText configuration surface (YAML + CLI) is used as-is. + +Use this section together with: + +- The [Base Module Parameters](#base-module-parameters) and [Supported Models](#supported-models) above for a high-level model overview +- The [Primus CLI Reference](../02-user-guide/cli-reference.md) for Primus CLI usage patterns +- The official MaxText documentation for the full set of MaxText-specific arguments + +### MaxText-specific notes + +- Primus currently wires MaxText via `primus/configs/models/maxtext` and `primus/configs/modules/maxtext`. +- Model families currently exercised in examples include: + - LLaMA2 7B/70B + - LLaMA3 8B/70B + - LLaMA3.3 70B + - DeepSeek-V2 16B + - Mixtral-8x7B + - Grok1 +- There are no extra Primus-only flags for MaxText yet; as we add MaxText-specific patches (e.g., ROCm optimizations, logging helpers), they will be documented in tables here in the same style as the Megatron-LM and TorchTitan patch notes. diff --git a/docs/cli/CLI-ARCHITECTURE.md b/docs/06-developer-guide/cli-architecture.md similarity index 93% rename from docs/cli/CLI-ARCHITECTURE.md rename to docs/06-developer-guide/cli-architecture.md index 944988aa3..8dd86e78f 100644 --- a/docs/cli/CLI-ARCHITECTURE.md +++ b/docs/06-developer-guide/cli-architecture.md @@ -1,4 +1,4 @@ -# 🚀 From Chaos to Order: Building a Unified Entry Point for AMD GPU LLM Training +# 🚀 From chaos to order: Building a unified entry point for AMD GPU LLM training > ⚠️ **NOTE**: This is a draft version and not the final release. > @@ -8,7 +8,7 @@ --- -## 📖 The Beginning: Pain Points in Training Workflows +## 📖 The beginning: Pain points in training workflows Imagine this scenario: @@ -41,7 +41,7 @@ The traditional approach is to use a large number of Bash scripts to handle thes --- -## 💡 Design Philosophy: One Command, Done +## 💡 Design philosophy: One command, done Our core philosophy is simple: **One command, from environment configuration to training launch, fully automated.** @@ -50,7 +50,7 @@ Our core philosophy is simple: **One command, from environment configuration to primus-cli direct -- train pretrain --config deepseek_v2.yaml ``` -### 🏗️ Three-Layer Architecture Design +### 🏗️ Three-layer architecture design Primus CLI adopts a clear **three-layer structure + plugin system**: @@ -74,7 +74,7 @@ Primus CLI adopts a clear **three-layer structure + plugin system**: └─────────────────────────────────────────────────────┘ ``` -### 🎯 Four Design Goals +### 🎯 Four design goals | Goal | Implementation | User Benefits | |------|---------------|---------------| @@ -85,9 +85,9 @@ Primus CLI adopts a clear **three-layer structure + plugin system**: --- -## 🔍 Deep Dive: Architecture Dissection +## 🔍 Deep dive: Architecture dissection -### ⚙️ Layer 1: Intelligent Runtime Abstraction +### ⚙️ Layer 1: Intelligent runtime abstraction Different scenarios require different runtime environments, but users shouldn't have to worry about these details. Primus CLI provides three seamlessly switchable runtime modes: @@ -114,7 +114,7 @@ primus-cli slurm srun -N 8 -- benchmark gemm -M 4096 --- -### 🔁 Layer 2: Hook & Patch System +### 🔁 Layer 2: Hook and patch system Training is more than just running a Python script. You might need to: - 🗂️ Preprocess datasets before training @@ -147,7 +147,7 @@ This is especially useful when you need to quickly apply temporary fixes or make --- -### 🧩 Layer 3: Task Execution Layer +### 🧩 Layer 3: Task execution layer This layer is responsible for executing specific business logic—training, testing, environment checks, and other actual tasks. Remember we said "zero-intrusion extension"? How is this achieved? @@ -185,15 +185,15 @@ This plugin-based design allows Primus CLI to quickly respond to new requirement --- -## 🌐 The Magic Behind: Intelligent Environment Detection +## 🌐 The magic behind: Intelligent environment detection This is probably the most "black tech" part of Primus CLI. -### Problem: Different GPUs Need Different Configurations +### Problem: Different GPUs need different configurations AMD's GPU family is rich: MI300X, MI250X, MI210... Each GPU has its optimal ROCm configuration and environment variable settings. The traditional approach is to let users manually select configurations, but this is both error-prone and insufficiently automated. -### Solution: Three-Step Auto-Configuration +### Solution: Three-step auto-configuration **Step 1: Load Common Environment** @@ -226,7 +226,7 @@ Now, `MI300X.sh` can contain all best practices for this GPU model: **Users don't need to worry about these details at all - everything is automatic.** -### Real-World Example +### Real-world example ```bash # On MI300X cluster @@ -242,7 +242,7 @@ primus-cli direct -- train pretrain --config config.yaml --- -## 🧪 Foundation of Scientific Experiments: Reproducibility +## 🧪 Foundation of scientific experiments: Reproducibility In machine learning research, reproducibility is crucial. But reality is harsh: @@ -250,7 +250,7 @@ In machine learning research, reproducibility is crucial. But reality is harsh: Does this sound familiar? Primus CLI completely solves this problem with an **automated snapshot mechanism**. -### Auto-Record Everything +### Auto-record everything Every time training starts, Primus CLI automatically saves the complete runtime context: @@ -270,7 +270,7 @@ output/exp_2025_11_10_134522/ └── metadata.json # Runtime metadata ``` -### One-Click Reproduction +### One-click reproduction Three months later, when you want to reproduce this experiment: @@ -285,7 +285,7 @@ Primus CLI will automatically: 3. Verify GPU and system environment 4. Start training (if environment is compatible) -### Real-World Value +### Real-world value | Scenario | Traditional Approach | Using Primus CLI | |----------|---------------------|------------------| @@ -297,11 +297,11 @@ Primus CLI will automatically: --- -## 📊 Real-World Case: From Development to Production +## 📊 Real-world case: From development to production Let's see how Primus CLI simplifies the entire workflow through a real scenario. -### Scenario: Training DeepSeek-V2 Model +### Scenario: Training DeepSeek-V2 model **Step 1: Local Development & Validation** 🖥️ @@ -346,7 +346,7 @@ primus-cli slurm sbatch \ -- train pretrain --config configs/deepseek_v2_prod.yaml ``` -### Key Insight +### Key insight Notice? **From development to production, the core command structure remains unchanged**: ``` @@ -357,7 +357,7 @@ Only the runtime environment (`direct` → `container` → `slurm`) changes - ev --- -## 🎯 Core Advantages Summary +## 🎯 Core advantages summary After the detailed introduction above, let's summarize the core value Primus CLI brings: @@ -373,11 +373,11 @@ After the detailed introduction above, let's summarize the core value Primus CLI --- -## 🛣️ Future Roadmap +## 🛣️ Future roadmap Primus CLI continues to evolve, and our near-term plans include: -### Short-Term Goals (2025) +### Short-term goals (2025) - 🎯 **Python Hook API**: Support writing Hooks in Python scripts for more flexible extension capabilities - 🎯 **Intelligent Preflight**: Auto-check GPU health, network topology, InfiniBand connectivity before launch - 🎯 **Configuration Template System**: Built-in best practice config templates for common models @@ -386,12 +386,12 @@ Primus CLI continues to evolve, and our near-term plans include: - 🎯 **Extended Framework Support**: Improve support for more training frameworks like TorchTitan, JAX/Flax - 🎯 **CI/CD Integration**: Provide standardized testing and validation workflows, support automated regression testing -### Long-Term Vision +### Long-term vision - 🌟 Become the **standard training entry point for the ROCm ecosystem** --- -## 🎓 Summary: The Power of One Command +## 🎓 Summary: The power of one command Back to the question at the beginning: How do we make large model training go from complex to simple? @@ -415,9 +415,10 @@ primus-cli direct -- train pretrain --config deepseek_v2.yaml --- -## 📚 Learn More +## 📚 Learn more -- 📖 **User Guide**: [PRIMUS-CLI-GUIDE.md](./PRIMUS-CLI-GUIDE.md) +- 📖 **CLI Reference (user guide)**: [cli-reference.md](../02-user-guide/cli-reference.md) +- 🏛 **System Architecture**: [architecture.md](./architecture.md) - 🔧 **Quick Start**: `primus-cli --help` - 💬 **Issue Reporting**: GitHub Issues - 🌐 **ROCm Ecosystem**: [rocm.github.io](https://rocm.github.io) diff --git a/docs/06-developer-guide/contributing.md b/docs/06-developer-guide/contributing.md new file mode 100644 index 000000000..9cc07f781 --- /dev/null +++ b/docs/06-developer-guide/contributing.md @@ -0,0 +1,142 @@ +# Contributing guide + +This guide summarizes how to set up a development environment, follow project conventions, run checks locally, and align with the CI pipeline. For test commands and layout, see [Testing Guide](testing.md). The repository root [CONTRIBUTING.md](../../CONTRIBUTING.md) summarizes key contribution guidelines, such as branch naming conventions, commit message style, and pull request requirements. + +## 1. Development setup + +1. **Clone the repository** (include submodules): + + ```bash + git clone --recurse-submodules https://github.com/AMD-AGI/Primus.git + cd Primus + ``` + +2. **Install Python dependencies:** + + ```bash + pip install -r requirements.txt + ``` + +3. **Install pre-commit hooks** (recommended): + + ```bash + pip install pre-commit + pre-commit install + ``` + +4. **Optional—JAX / MaxText work:** + + ```bash + pip install -r requirements-jax.txt + ``` + +5. **Quick verification** (from the repository root, with Primus on your `PATH` or via the bundled launcher): + + ```bash + ./primus-cli direct -- benchmark gemm --M 4096 --N 4096 --K 4096 + ``` + +## 2. Code style + +Configuration lives in `.pre-commit-config.yaml`. Hooks run automatically on `git commit` after `pre-commit install`. + +| Tool | Version | Purpose | +|------|---------|---------| +| **black** | 24.8.0 | Python formatter, line length 110 | +| **isort** | 5.13.2 | Import sorting, profile `black` | +| **autoflake** | 2.3.1 | Removes unused imports and variables (see hook args for star imports and `__init__`) | +| **shellcheck** | 0.10.0.1 (shellcheck-py) | Shell script analysis | +| **pre-commit-hooks** | v4.0.1 | `trailing-whitespace`, `end-of-file-fixer`, `check-yaml`, `check-added-large-files`, `check-merge-conflict` | + +Manual one-off runs (repository root): + +```bash +black --line-length=110 . +isort --profile black . +autoflake --remove-all-unused-imports --remove-unused-variables --expand-star-imports --ignore-init-module-imports --recursive --in-place . +``` + +CI runs `pre-commit run --all-files --show-diff-on-failure`, so lint behavior follows `.pre-commit-config.yaml` rather than a separate hand-written list of formatter commands. + +## 3. Branch naming convention + +Format: + +```text +// +``` + +**Types:** `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci` + +**Scope (optional):** `engine`, `model`, `scheduler`, `docs`, `tests`, `config`, or another short area name. + +**Examples:** + +- `feat/model/implement-moe-routing` +- `fix/engine/init-error` + +## 4. Commit convention + +Follow [Conventional Commits](https://www.conventionalcommits.org/): + +```text +(): +``` + +**Examples:** + +- `feat(model): add MOE routing functionality` +- `fix(engine): resolve initialization error` + +## 5. Testing requirements + +Before opening a pull request, run the following from the repository root: + +- **Shell integration tests:** + + ```bash + bash ./tests/runner/run_all_tests.sh + ``` + +- **Python unit tests:** + + ```bash + pytest tests/unit_tests/ --maxfail=1 -s + ``` + +- **Backend / trainer tests** (GPU, datasets, and sometimes Hugging Face tokens): run the relevant file under `tests/trainer/` when your change touches that backend. See [Testing Guide](testing.md). + +- **Pre-commit on all files:** + + ```bash + pre-commit run --all-files + ``` + +## 6. Pull request process + +1. Fork the repository (unless you have write access and use a feature branch). +2. Create a branch that follows the naming convention above. +3. Implement changes and commit using the commit message convention. +4. Run tests and pre-commit locally. +5. Push and open a pull request with a clear description. +6. Reference related issues when applicable. +7. Request reviewers. +8. Address review feedback. +9. Ensure CI passes (lint and unit tests on the paths your PR triggers). + +## 7. CI pipeline + +The workflow **`.github/workflows/ci.yaml`** defines how changes are validated. + +**Triggers:** `workflow_dispatch`, pushes to `main`, tags matching `v*`, and pull requests. + +**Jobs (high level):** + +- **`code-lint`:** Ubuntu, Python 3.12—installs `pre-commit` and runs `pre-commit run --all-files --show-diff-on-failure`, so the checks (black, isort, autoflake, shellcheck, and the `pre-commit-hooks` set) follow `.pre-commit-config.yaml` exactly. +- **`build-docker`:** Builds and pushes Docker images (depends on `code-lint`). +- **`run-unittest-torch`:** Self-hosted GPU runner—installs dependencies (including Primus-Turbo and AITER as defined in the workflow), runs `bash ./tests/runner/run_all_tests.sh`, `pytest` on `tests/unit_tests/` (with a few deselected tests), then Megatron and TorchTitan trainer tests with `DATA_PATH`, `MASTER_PORT`, `HSA_NO_SCRATCH_RECLAIM=1`, and `HF_TOKEN` where required. +- **`run-unittest-jax`:** JAX runner—installs `requirements-jax.txt`, runs shell tests and `python ./tests/run_unit_tests.py --jax` with CI-specific environment variables. + +Lint checks mirror the pre-commit stack. Trainer jobs require GPU resources and shared secrets (for example `HF_TOKEN`) in the hosted environment. + +For a focused description of local vs CI test commands, see [Testing Guide](testing.md). diff --git a/docs/06-developer-guide/extending-backends.md b/docs/06-developer-guide/extending-backends.md new file mode 100644 index 000000000..cc42151fc --- /dev/null +++ b/docs/06-developer-guide/extending-backends.md @@ -0,0 +1,419 @@ +# Extending backends + +This guide explains how to add a **new training backend** to Primus using the current runtime architecture. It complements the high-level picture in [Primus overview](../01-getting-started/overview.md): adapters sit under the unified CLI and configuration system ([Configuration system](../02-user-guide/configuration-system.md)), and each backend plugs in through the same lifecycle and hook points as Megatron-LM, TorchTitan, MaxText, and the other integrated stacks. + +The runtime is built around: + +- **`BackendAdapter`** – integrates a backend framework +- **`BackendRegistry`** – discovers and instantiates adapters +- **`BaseTrainer`** – defines the minimal training lifecycle that all backends follow +- **`PrimusRuntime`** – orchestrates config loading, environment setup, patches, adapter, and trainer + +The examples below use a minimal **`dummy`** backend as a template. The dummy files are illustrative and are not checked into this repository; existing backends such as Megatron, TorchTitan, MaxText, Megatron Bridge, and HummingbirdXT show the production pattern. + +--- + +## What happens when you run Primus? + +When you run: + +```bash +primus train pretrain --config +``` + +the runtime (`PrimusRuntime`) does roughly: + +1. Load the experiment config—`load_primus_config()` returns a lightweight `SimpleNamespace` (not a `PrimusConfig`)—and select the `module_config` +2. Apply CLI overrides to `module_config.params` +3. Initialize environment (HF, logging, distributed environment, data directory) +4. Resolve backend adapter via `BackendRegistry.get_adapter(framework)` +5. Call `adapter.setup_backend_path(...)` to put the backend on `sys.path` +6. Call `adapter.prepare_backend(module_config)` (usually runs backend setup hooks) +7. Build backend arguments: + + ```python + backend_args = adapter.convert_config(module_config.params) + # run "build_args" patches and merge back into module_config.params + ``` + +8. Load and construct the trainer: + + ```python + TrainerClass = adapter.load_trainer_class(stage=module_config.params.stage or "pretrain") + trainer = TrainerClass(backend_args=backend_args) + ``` + +9. Execute the trainer lifecycle (with patches around it). Backend version detection is lazy during patch handling through `adapter.detect_backend_version()` rather than a separate pre-trainer step: + + ```python + # PrimusRuntime (the "build_args" patches from step 7 already ran + # before the trainer was constructed): + run_patches(phase="setup", backend_args=backend_args) + trainer.setup() + + trainer.init() + + run_patches(phase="before_train", backend_args=backend_args) + trainer.train() + run_patches(phase="after_train", backend_args=backend_args) + + trainer.cleanup() + ``` + +So a complete backend must provide: + +- An **adapter** subclassing `BackendAdapter` +- A **trainer** subclassing `BaseTrainer` and implementing `setup`, `init`, and `train` (and optionally overriding `cleanup`, which has a default no-op implementation) +- A small `primus.backends..__init__` that calls `BackendRegistry.register_adapter(...)` + +--- + +## Minimal backend layout + +Create a new backend folder under `primus/backends/`: + +```text +primus/backends/dummy/ +├── __init__.py +├── dummy_adapter.py +└── dummy_pretrain_trainer.py +``` + +This mirrors the pattern used by existing backends (for example Megatron, TorchTitan). + +--- + +## Implement the adapter (`BackendAdapter`) + +**File**: `primus/backends/dummy/dummy_adapter.py` + +```python +from __future__ import annotations + +from types import SimpleNamespace +from typing import Any + +from primus.core.backend.backend_adapter import BackendAdapter +from primus.core.backend.backend_registry import BackendRegistry +from primus.core.utils.module_utils import log_rank_0 + + +class DummyAdapter(BackendAdapter): + """Minimal adapter for a 'dummy' backend.""" + + def __init__(self, framework: str = "dummy"): + super().__init__(framework) + + def setup_backend_path(self, backend_path=None) -> str: + """ + Dummy backend lives inside the Primus tree (no third_party submodule), + so we don't need to modify sys.path or resolve any external path here. + + For real backends that live under third_party/, you can rely on + the default implementation in BackendAdapter instead. + """ + log_rank_0("[Primus:DummyAdapter] setup_backend_path: no-op for in-tree dummy backend") + return "" + + def convert_config(self, params: Any) -> Any: + """ + Convert Primus module params → backend-specific args. + + For a real backend you would build a structured args object. Here we + just wrap the incoming params in a SimpleNamespace. + """ + if isinstance(params, dict): + backend_args = SimpleNamespace(**params) + else: + backend_args = params + log_rank_0("[Primus:DummyAdapter] Converted Primus params -> dummy backend_args") + return backend_args + + def detect_backend_version(self) -> str: + """Return a version string used by patch filtering.""" + return "dummy-0.1" + + def load_trainer_class(self, stage: str = "pretrain"): + """Return the Trainer class for the specified training stage.""" + from primus.backends.dummy.dummy_pretrain_trainer import DummyPretrainTrainer + + log_rank_0("[Primus:DummyAdapter] Loaded trainer class: DummyPretrainTrainer") + return DummyPretrainTrainer +``` + +Key points: + +- Since the dummy backend is implemented directly under `primus.backends.dummy` (not in `third_party/`), it overrides `setup_backend_path()` as a **no-op** so that the default third-party path resolution is skipped. +- `convert_config()` returns whatever your trainer expects as `backend_args`. +- `load_trainer_class()` imports and returns `DummyPretrainTrainer` directly (similar to `MegatronAdapter`), without going through a registry lookup. + +--- + +## Implement a runnable trainer (`BaseTrainer`) + +**File**: `primus/backends/dummy/dummy_pretrain_trainer.py` + +```python +from typing import Any + +from primus.core.trainer.base_trainer import BaseTrainer +from primus.core.utils.module_utils import log_rank_0 + + +class DummyPretrainTrainer(BaseTrainer): + """Minimal runnable trainer for the dummy backend.""" + + def __init__(self, backend_args: Any): + # BaseTrainer stores backend_args and reads torchrun env (rank, world_size, etc.) + super().__init__(backend_args=backend_args) + self._initialized = False + + def setup(self): + # Optional pre-init setup (e.g., logging, sanity checks) + log_rank_0(f"[DummyPretrainTrainer] setup() on rank={self.rank}") + + def init(self): + # Build your model / optimizer / dataloader here in a real backend. + log_rank_0("[DummyPretrainTrainer] init()") + self._initialized = True + + def train(self): + if not self._initialized: + raise RuntimeError("DummyPretrainTrainer.init() must be called before train().") + + log_rank_0("[DummyPretrainTrainer] train()") + # Example: access a custom param (e.g., 'hello') from backend_args. + hello_value = getattr(self.backend_args, "hello", "") + log_rank_0(f"[DummyPretrainTrainer] hello={hello_value}") + # Real training loop would go here. + log_rank_0("[DummyPretrainTrainer] training finished successfully.") + + def cleanup(self, on_error: bool = False): + # Optional cleanup logic (close files, finalize logging, etc.) + status = "error" if on_error else "success" + log_rank_0(f"[DummyPretrainTrainer] cleanup(on_error={status})") +``` + +Why this matches the core architecture: + +- `BaseTrainer.__init__` reads distributed environment from `get_torchrun_env()`. +- `PrimusRuntime` drives the lifecycle: `setup` → `init` → `train` → `cleanup` and runs patch phases around these steps. +- Your trainer only needs to implement `setup`, `init`, `train`, and `cleanup` using `backend_args` and the resolved environment information. + +--- + +## Register the adapter in `BackendRegistry` + +**File**: `primus/backends/dummy/__init__.py` + +```python +from primus.backends.dummy.dummy_adapter import DummyAdapter +from primus.core.backend.backend_registry import BackendRegistry + + +# Register adapter (backend name → adapter class) +BackendRegistry.register_adapter("dummy", DummyAdapter) +``` + +At runtime, when `framework: dummy` is requested: + +- `BackendRegistry.get_adapter("dummy")` lazily imports `primus.backends.dummy` (this file), which calls `register_adapter("dummy", DummyAdapter)`. +- The adapter instance is created and used by `PrimusRuntime` to set up the backend path, run setup hooks, build `backend_args`, and load and construct the trainer. + +--- + +## Minimal config example + +Create an experiment YAML (simplified; full template in the next section): + +```yaml +modules: + pre_trainer: + framework: dummy + config: dummy_trainer.yaml + model: dummy_8B.yaml +``` + +Run: + +```bash +./primus-cli direct -- train pretrain --config examples/dummy/configs/dummy_8B-pretrain.yaml +``` + +Because this dummy backend is an in-tree template and `setup_backend_path()` is a no-op, you should see logs similar to: + +- `[Primus:DummyAdapter] setup_backend_path: no-op for in-tree dummy backend` +- `[Primus:DummyAdapter] Converted Primus params -> dummy backend_args` +- `[DummyPretrainTrainer] setup()` +- `[DummyPretrainTrainer] init()` +- `[DummyPretrainTrainer] train()` + +--- + +## Example end-to-end YAML configs + +This template mirrors the Megatron pattern. Create these files only when you are actually adding a dummy backend for local development or tests: + +- The **top-level experiment config** lives under `examples//configs/...` +- The **module config** is resolved from `primus/configs/modules/{framework}/` +- The **model config** is resolved from `primus/configs/models/{framework}/` + +### Top-level experiment config + +**File 1**: `examples/dummy/configs/dummy_8B-pretrain.yaml` + +```yaml +work_group: ${PRIMUS_TEAM:local} +user_name: ${PRIMUS_USER:local} +exp_name: ${PRIMUS_EXP_NAME:dummy_8B-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: dummy + config: dummy_trainer.yaml + + # model to run + model: dummy_8B.yaml + + overrides: + # log / debug + stderr_sink_level: DEBUG + + # example training overrides (merged into module params) + train_iters: 100 + global_batch_size: 32 + micro_batch_size: 4 + seq_length: 1024 + hello: world +``` + +### Module-level trainer config + +**File 2**: `primus/configs/modules/dummy/dummy_trainer.yaml` + +```yaml +extends: + - trainer_base.yaml # optional, if you have a shared base; otherwise omit + +train_iters: 1000 +global_batch_size: 16 +micro_batch_size: 1 +seq_length: 512 + +log_interval: 1 +save_interval: 100 +``` + +This file defines **default training hyperparameters** for the `dummy` backend. Fields under `modules.pre_trainer.overrides` in the top-level config are deep-merged on top of these defaults. + +### Model-level config + +**File 3**: `primus/configs/models/dummy/dummy_8B.yaml` + +```yaml +extends: [] + +model_name: dummy_8B +vocab_size: 32000 +hidden_size: 4096 +num_layers: 32 +num_attention_heads: 32 +``` + +This file plays the same role as Megatron model configs under `primus/configs/models/megatron/`. It is loaded via: + +- `modules.pre_trainer.model: dummy_8B.yaml` +- resolved as `primus/configs/models/{framework}/dummy_8B.yaml` + +### Running the example + +Run: + +```bash +./primus-cli direct -- train pretrain --config examples/dummy/configs/dummy_8B-pretrain.yaml +``` + +Primus will: + +- load `examples/dummy/configs/dummy_8B-pretrain.yaml` +- resolve `modules.pre_trainer.config` → `primus/configs/modules/dummy/dummy_trainer.yaml` +- resolve `modules.pre_trainer.model` → `primus/configs/models/dummy/dummy_8B.yaml` +- build `module_config.params` from these sources plus `overrides` +- call `DummyAdapter.convert_config(params)` to build `backend_args` +- construct `DummyPretrainTrainer(backend_args=...)` +- execute `setup` → `init` → `train` → `cleanup`. + +For adding model YAML for existing backends (Megatron, TorchTitan, and others), see [Adding model configurations](./adding-models.md). + +--- + +## Checklist for a complete backend + +Use this as a quick checklist when adding a new backend: + +- [ ] Adapter subclass of `BackendAdapter` implements: + - `load_trainer_class(stage: str)` + - `convert_config(params)` + - `detect_backend_version()` + - (optionally) overrides `prepare_backend()` / `third_party_dir_name` +- [ ] Trainer subclass of `BaseTrainer` implements: + - `setup()`, `init()`, `train()`, and optional `cleanup(on_error: bool)` +- [ ] `BackendRegistry.register_adapter(backend, AdapterClass)` is called in `primus.backends..__init__` +- [ ] At least one unit test is added under `tests/unit_tests/backends/` + +Once these are in place, your backend is fully integrated into the Primus runtime and follows the same lifecycle and patch phases as the built-in backends. + +--- + +## Advanced: Backend-specific setup with train hooks + +For more advanced scenarios (for example installing extra Python packages or configuring backend-specific environment variables at runtime), you can use **train hooks** under `runner/helpers/hooks`. + +- **Hook locations for training**: + - Global hooks (run for all commands): `runner/helpers/hooks/*.sh` and `runner/helpers/hooks/*.py`. These are discovered with `find ... -maxdepth 1 \( -name "*.sh" -o -name "*.py" \)` and executed in **lexicographical order** of their filenames (see `runner/helpers/execute_hooks.sh`). + - Command-specific hooks: `runner/helpers/hooks/train/pretrain/*.sh|*.py` (and `.../posttrain/...`), discovered and ordered the same way. For pretrain, this directory contains the dispatcher `prepare_experiment.sh`. + - Per-framework hooks: `runner/helpers/hooks/train/pretrain//` and `runner/helpers/hooks/train/posttrain//`, where `` is `megatron`, `torchtitan`, `dummy`, and so on. These are **not** run directly by `execute_hooks`; instead `prepare_experiment.sh` detects the framework from the experiment config, runs that framework folder's `*.sh` files in lexicographical order, and then invokes the framework's `prepare.py` dispatcher. + +When you run: + +```bash +./primus-cli direct -- train pretrain --config +``` + +Primus will: + +- Call `execute_hooks train pretrain ...`, which: + - Runs global hooks under `runner/helpers/hooks/` (lexicographical order) + - Then runs command-specific hooks under `runner/helpers/hooks/train/pretrain/`, including `prepare_experiment.sh` + - `prepare_experiment.sh` resolves the framework from the config and runs the per-framework hooks under `runner/helpers/hooks/train/pretrain//` (its `*.sh` files in lexicographical order, then `prepare.py`) + +Each hook script can **emit control lines on stdout** that Primus parses (the framework hooks' stdout is captured through `prepare_experiment.sh`): + +- **`env.*=value` → environment variables** + + ```bash + # inside runner/helpers/hooks/train/pretrain//.sh + echo "env.MY_BACKEND_FLAG=1" # becomes: export MY_BACKEND_FLAG=1 + echo "env.PYTHONPATH=/opt/mylib:$PYTHONPATH" + ``` + + These are exported into the environment of the `primus-cli direct` process, so downstream backend code and trainers see them. + +- **`extra.*=value` → extra CLI arguments** + + ```bash + # inside the same hook + echo "extra.backend_path=/opt/my-backend" # becomes: --backend_path /opt/my-backend + echo "extra.train_data_path=/my/data" # becomes: --train_data_path /my/data + ``` + + These `extra.*` pairs are appended to the Primus CLI invocation as `-- ` after hook execution. + +Typical pattern to install or configure packages for a backend: + +- Add a script under `runner/helpers/hooks/train/pretrain//-setup.sh` (use a numeric prefix such as `000-` or `010-` to control ordering). +- In that script: + - Optionally run `python -m pip install ...` or other setup commands. + - Emit `env.*=...` lines to export any required environment variables. + - Emit `extra.*=...` lines if you need to pass additional CLI arguments (for example `backend_path`) into the Primus runtime for this run. diff --git a/docs/06-developer-guide/model-support-matrix.md b/docs/06-developer-guide/model-support-matrix.md new file mode 100644 index 000000000..f749f8343 --- /dev/null +++ b/docs/06-developer-guide/model-support-matrix.md @@ -0,0 +1,211 @@ +# Model support matrix + +This document summarizes which model families Primus targets per backend and lists representative checked-in model presets and example experiment YAML under the repository. It distinguishes **curated examples** from **theoretical** support (a preset or upstream stack may exist without a matching `examples/` entry). Use the filesystem under `primus/configs/models/` and `examples/*/configs/` as the authoritative live inventory. + +For how to add presets, see [Adding model configurations](./adding-models.md). Backend parameter references: [Megatron](../03-configuration-reference/megatron-parameters.md), [TorchTitan](../03-configuration-reference/torchtitan-parameters.md), [MaxText](../03-configuration-reference/maxtext-parameters.md), [Megatron Bridge](../03-configuration-reference/megatron-bridge-parameters.md). + +--- + +## Overview: Supported model families (high level) + +The following aligns with the backend overview and the configs present in this tree. + +| Backend | Model families (documentation / stack scope) | +| ------- | ---------------------------------------------- | +| **Megatron-LM** | LLaMA2 / LLaMA3 / LLaMA3.1 / LLaMA3.3 / LLaMA4 (sizes from small to 405B+), DeepSeek-V2 (including lite), DeepSeek-V3, and DeepSeek-V4 (flash / pro), Mixtral MoE and large MoE recipe YAML, Qwen2.5 and Qwen3 (dense and MoE), Grok, GPT-OSS (20B / 120B), GLM, Kimi K2, LFM2, MiniMax, Zebra LLaMA (including GDN and KDA linear-attention variants), Mamba, and generic `language_model.yaml` bases. | +| **TorchTitan** | LLaMA3 family (including 3.1), LLaMA4 examples, DeepSeek-V3 examples, and Qwen3 examples including 0.6B, 1.7B, 4B, 8B, 14B, and 32B variants where present. Additional presets exist under `primus/configs/models/torchtitan/` without being exhaustively listed here. | +| **MaxText (JAX)** | LLaMA2 / LLaMA3 / LLaMA3.3, DeepSeek-V2 16B, Mixtral-8x7B, Grok1, Qwen3 14B / 30B-A3B (per presets and examples). Broader coverage may exist in upstream MaxText; see [MaxText](https://github.com/AI-Hypercomputer/maxtext). | +| **Megatron Bridge** | Qwen3 pretraining and post-training examples, plus post-training examples for Zebra LLaMA and Mamba where present. LLaMA 3.1 70B Bridge examples appear under MI355X. | +| **Diffusion** | Flux.1 (`schnell` / `dev`) text-to-image and Wan 2.1 / 2.2 text- and image-to-video presets under `primus/configs/models/diffusion/`, with examples under `examples/diffusion/configs/` and `examples/megatron/configs/*/diffusion/`. See [Diffusion models](../04-technical-guides/diffusion-models/README.md). | +| **HummingbirdXT** | Registered backend with a post-training trainer and one checked-in example; user-facing support level still needs maintainer confirmation. | + +**Interpretation:** “Supported” in upstream code can exceed what this repository ships as YAML. Rows below reference representative files that exist under `primus/configs/models/` and `examples/`; they should not be treated as a complete generated inventory. + +--- + +## Megatron model configs + +Model presets live in `primus/configs/models/megatron/`. Example experiments that reference those presets appear under `examples/megatron/configs/MI300X/`, `MI325X/`, and `MI355X/`. + +For **TorchTitan**, the MI300X, MI325X, and MI355X example directories carry the same model set (21 configs each). For **Megatron**, MI300X and MI325X are nearly identical **except** that MI325X omits `qwen3_5_35B_A3B` (BF16 and FP8)—so MI300X has 70 example configs while MI325X has 68—and **MI355X** is a superset (99 configs; it adds models such as `glm5`, `gpt_oss_120B`, `kimi_k2`, `lfm2_8B_A1B`, and `minimax_m2.5`). Each row's SKU list below reflects exactly which SKUs ship a curated example (see, for example, `qwen3_5_35B_A3B`, which is MI300X/MI355X only). + +| Model name (file) | Preset path | Role | Example experiment dirs | Precision in examples | +| ----------------- | ----------- | ---- | ----------------------- | ---------------------- | +| `deepseek_v2.yaml` | `primus/configs/models/megatron/deepseek_v2.yaml` | Dense model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `deepseek_v2_base.yaml` | `primus/configs/models/megatron/deepseek_v2_base.yaml` | Base fragment (`extends` only) | — | — | +| `deepseek_v2_lite.yaml` | `primus/configs/models/megatron/deepseek_v2_lite.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `deepseek_v3.yaml` | `primus/configs/models/megatron/deepseek_v3.yaml` | MoE model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `deepseek_v3_base.yaml` | `primus/configs/models/megatron/deepseek_v3_base.yaml` | Base fragment | — | — | +| `glm4_7.yaml` | `primus/configs/models/megatron/glm4_7.yaml` | Model preset | No curated example in this repo | — | +| `glm5.yaml` | `primus/configs/models/megatron/glm5.yaml` | Model preset | MI355X | BF16, FP8 | +| `gpt_oss_20B.yaml` | `primus/configs/models/megatron/gpt_oss_20B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `gpt_oss_120B.yaml` | `primus/configs/models/megatron/gpt_oss_120B.yaml` | Model preset | MI355X | BF16, FP8 | +| `grok1.yaml` | `primus/configs/models/megatron/grok1.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `grok2.yaml` | `primus/configs/models/megatron/grok2.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `grok_base.yaml` | `primus/configs/models/megatron/grok_base.yaml` | Base fragment | — | — | +| `hybrid_model_base.yaml` | `primus/configs/models/megatron/hybrid_model_base.yaml` | Base fragment | — | — | +| `kimi_k2.yaml` | `primus/configs/models/megatron/kimi_k2.yaml` | MoE model preset | MI355X | BF16, FP8 | +| `language_model.yaml` | `primus/configs/models/megatron/language_model.yaml` | Generic Megatron LM defaults | Used via `extends` | — | +| `lfm2_8B_A1B.yaml` | `primus/configs/models/megatron/lfm2_8B_A1B.yaml` | MoE model preset | MI355X | BF16, FP8 | +| `lfm_base.yaml` | `primus/configs/models/megatron/lfm_base.yaml` | Base fragment | — | — | +| `llama2_7B.yaml` | `primus/configs/models/megatron/llama2_7B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama2_13B.yaml` | `primus/configs/models/megatron/llama2_13B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama2_70B.yaml` | `primus/configs/models/megatron/llama2_70B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama2_base.yaml` | `primus/configs/models/megatron/llama2_base.yaml` | Base fragment | — | — | +| `llama_base.yaml` | `primus/configs/models/megatron/llama_base.yaml` | Base fragment | — | — | +| `llama3_8B.yaml` | `primus/configs/models/megatron/llama3_8B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama3_70B.yaml` | `primus/configs/models/megatron/llama3_70B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama3_base.yaml` | `primus/configs/models/megatron/llama3_base.yaml` | Base fragment | — | — | +| `llama3.1_8B.yaml` | `primus/configs/models/megatron/llama3.1_8B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama3.1_70B.yaml` | `primus/configs/models/megatron/llama3.1_70B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama3.1_405B.yaml` | `primus/configs/models/megatron/llama3.1_405B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama3.2_1B.yaml` | `primus/configs/models/megatron/llama3.2_1B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama3.2_3B.yaml` | `primus/configs/models/megatron/llama3.2_3B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama3.3_70B.yaml` | `primus/configs/models/megatron/llama3.3_70B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama4_17B128E.yaml` | `primus/configs/models/megatron/llama4_17B128E.yaml` | MoE model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama4_17B16E.yaml` | `primus/configs/models/megatron/llama4_17B16E.yaml` | MoE model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `llama4_base.yaml` | `primus/configs/models/megatron/llama4_base.yaml` | Base fragment | — | — | +| `mamba_370M.yaml` | `primus/configs/models/megatron/mamba_370M.yaml` | Model preset | MI300X, MI325X, MI355X | Set in experiment overrides | +| `mamba_base.yaml` | `primus/configs/models/megatron/mamba_base.yaml` | Base fragment | — | — | +| `minimax_m2.5.yaml` | `primus/configs/models/megatron/minimax_m2.5.yaml` | MoE model preset | MI355X | BF16, FP8 | +| `mixtral_8x7B_v0.1.yaml` | `primus/configs/models/megatron/mixtral_8x7B_v0.1.yaml` | MoE model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `mixtral_8x22B_v0.1.yaml` | `primus/configs/models/megatron/mixtral_8x22B_v0.1.yaml` | MoE model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `mixtral_base.yaml` | `primus/configs/models/megatron/mixtral_base.yaml` | Base fragment | — | — | +| `moe_515B.yaml` | `primus/configs/models/megatron/moe_515B.yaml` | Large MoE template | No curated example in this repo | — | +| `moe_1T.yaml` | `primus/configs/models/megatron/moe_1T.yaml` | Large MoE template | No curated example in this repo | — | +| `moe_2T.yaml` | `primus/configs/models/megatron/moe_2T.yaml` | Large MoE template | No curated example in this repo | — | +| `moe_4T.yaml` | `primus/configs/models/megatron/moe_4T.yaml` | Large MoE template | No curated example in this repo | — | +| `moe_proxy_single_node.yaml` | `primus/configs/models/megatron/moe_proxy_single_node.yaml` | MoE proxy / test template | No curated example in this repo | — | +| `primus_megatron_model.yaml` | `primus/configs/models/megatron/primus_megatron_model.yaml` | Primus Megatron root defaults | Used via `extends` | — | +| `qwen2.5_3B.yaml` | `primus/configs/models/megatron/qwen2.5_3B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `qwen2.5_7B.yaml` | `primus/configs/models/megatron/qwen2.5_7B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `qwen2.5_14B.yaml` | `primus/configs/models/megatron/qwen2.5_14B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `qwen2.5_32B.yaml` | `primus/configs/models/megatron/qwen2.5_32B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `qwen2.5_72B.yaml` | `primus/configs/models/megatron/qwen2.5_72B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `qwen2.5_base.yaml` | `primus/configs/models/megatron/qwen2.5_base.yaml` | Base fragment | — | — | +| `qwen3_4B.yaml` | `primus/configs/models/megatron/qwen3_4B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `qwen3_8B.yaml` | `primus/configs/models/megatron/qwen3_8B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `qwen3_14B.yaml` | `primus/configs/models/megatron/qwen3_14B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `qwen3_32B.yaml` | `primus/configs/models/megatron/qwen3_32B.yaml` | Model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `qwen3_30B_A3B.yaml` | `primus/configs/models/megatron/qwen3_30B_A3B.yaml` | MoE model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `qwen3_5_35B_A3B.yaml` | `primus/configs/models/megatron/qwen3_5_35B_A3B.yaml` | MoE model preset | MI300X, MI355X | BF16, FP8 | +| `qwen3_235B_A22B.yaml` | `primus/configs/models/megatron/qwen3_235B_A22B.yaml` | MoE model preset | MI300X, MI325X, MI355X | BF16, FP8 | +| `zebra_llama_1B.yaml` | `primus/configs/models/megatron/zebra_llama_1B.yaml` | Model preset | MI300X, MI325X, MI355X | Set in experiment overrides | +| `zebra_llama_3B.yaml` | `primus/configs/models/megatron/zebra_llama_3B.yaml` | Model preset | MI300X, MI325X, MI355X | Set in experiment overrides | +| `zebra_llama_8B.yaml` | `primus/configs/models/megatron/zebra_llama_8B.yaml` | Model preset | MI300X, MI325X, MI355X | Set in experiment overrides | + +**Parallelism:** Tensor, pipeline, and expert parallel sizes are **not** fixed in model presets; they are set in experiment `overrides` (for example `tensor_model_parallel_size`, `pipeline_model_parallel_size`, `expert_model_parallel_size`). MoE presets such as `qwen3_235B_A22B.yaml` typically require non-default expert parallelism in real runs—see the matching experiment YAML. + +--- + +## TorchTitan model configs + +Presets: `primus/configs/models/torchtitan/`. Examples: `examples/torchtitan/configs/MI300X/`, `MI325X/`, and `MI355X/`. + +| Model name (file) | Preset path | Example experiment dirs | Precision in examples | +| ----------------- | ----------- | ------------------------- | ---------------------- | +| `deepseek_v3_16b.yaml` | `primus/configs/models/torchtitan/deepseek_v3_16b.yaml` | MI300X, MI325X, MI355X | BF16 | +| `deepseek_v3_16b-fp8.yaml` | `primus/configs/models/torchtitan/deepseek_v3_16b-fp8.yaml` | MI300X, MI325X, MI355X | FP8 | +| `deepseek_v3_236b.yaml` | `primus/configs/models/torchtitan/deepseek_v3_236b.yaml` | MI300X, MI325X, MI355X | BF16 | +| `deepseek_v3_236b-fp8.yaml` | `primus/configs/models/torchtitan/deepseek_v3_236b-fp8.yaml` | MI300X, MI325X, MI355X | FP8 | +| `deepseek_v3_671b.yaml` | `primus/configs/models/torchtitan/deepseek_v3_671b.yaml` | MI300X, MI325X, MI355X | (see experiment) | +| `deepseek_v3_671b-fp8.yaml` | `primus/configs/models/torchtitan/deepseek_v3_671b-fp8.yaml` | Preset only; stock examples use `deepseek_v3_671b.yaml` | — | +| `llama3_8B.yaml` | `primus/configs/models/torchtitan/llama3_8B.yaml` | No example in this repo | — | +| `llama3_8B-fp8.yaml` | `primus/configs/models/torchtitan/llama3_8B-fp8.yaml` | No example in this repo | — | +| `llama3_70B.yaml` | `primus/configs/models/torchtitan/llama3_70B.yaml` | No example in this repo | — | +| `llama3_70B-fp8.yaml` | `primus/configs/models/torchtitan/llama3_70B-fp8.yaml` | No example in this repo | — | +| `llama3.1_8B.yaml` | `primus/configs/models/torchtitan/llama3.1_8B.yaml` | MI300X, MI325X, MI355X | BF16 | +| `llama3.1_8B-fp8.yaml` | `primus/configs/models/torchtitan/llama3.1_8B-fp8.yaml` | MI300X, MI325X, MI355X | FP8 | +| `llama3.1_70B.yaml` | `primus/configs/models/torchtitan/llama3.1_70B.yaml` | MI300X, MI325X, MI355X | BF16 | +| `llama3.1_70B-fp8.yaml` | `primus/configs/models/torchtitan/llama3.1_70B-fp8.yaml` | MI300X, MI325X, MI355X | FP8 | +| `llama3.1_405B.yaml` | `primus/configs/models/torchtitan/llama3.1_405B.yaml` | MI300X, MI325X, MI355X | BF16 | +| `llama3.1_405B-fp8.yaml` | `primus/configs/models/torchtitan/llama3.1_405B-fp8.yaml` | MI300X, MI325X, MI355X | FP8 | +| `llama3.2_1B.yaml` | `primus/configs/models/torchtitan/llama3.2_1B.yaml` | No example in this repo | — | +| `llama3.3_70B.yaml` | `primus/configs/models/torchtitan/llama3.3_70B.yaml` | No example in this repo | — | +| `llama3.3_70B-fp8.yaml` | `primus/configs/models/torchtitan/llama3.3_70B-fp8.yaml` | No example in this repo | — | +| `llama4_17Bx128E.yaml` | `primus/configs/models/torchtitan/llama4_17Bx128E.yaml` | MoE; MI300X, MI325X, MI355X | BF16 | +| `llama4_17Bx128E-fp8.yaml` | `primus/configs/models/torchtitan/llama4_17Bx128E-fp8.yaml` | MoE; MI300X, MI325X, MI355X | FP8 | +| `llama4_17Bx16E.yaml` | `primus/configs/models/torchtitan/llama4_17Bx16E.yaml` | MoE; MI300X, MI325X, MI355X | BF16 | +| `llama4_17Bx16E-fp8.yaml` | `primus/configs/models/torchtitan/llama4_17Bx16E-fp8.yaml` | MoE; MI300X, MI325X, MI355X | FP8 | +| `qwen3_0.6b.yaml` | `primus/configs/models/torchtitan/qwen3_0.6b.yaml` | MI300X, MI325X, MI355X | (see experiment) | +| `qwen3_1.7b.yaml` | `primus/configs/models/torchtitan/qwen3_1.7b.yaml` | MI300X, MI325X, MI355X | (see experiment) | +| `qwen3_4b.yaml` | `primus/configs/models/torchtitan/qwen3_4b.yaml` | MI300X, MI325X, MI355X | (see experiment) | +| `qwen3_8b.yaml` | `primus/configs/models/torchtitan/qwen3_8b.yaml` | MI300X, MI325X, MI355X | (see experiment) | +| `qwen3_14b.yaml` | `primus/configs/models/torchtitan/qwen3_14b.yaml` | MI300X, MI325X, MI355X | (see experiment) | +| `qwen3_32b.yaml` | `primus/configs/models/torchtitan/qwen3_32b.yaml` | MI300X, MI325X, MI355X | (see experiment) | + +**Parallelism:** Controlled by TorchTitan launch configuration and Primus module overrides (see TorchTitan patch notes and [TorchTitan parameters](../03-configuration-reference/torchtitan-parameters.md)); not embedded in the small `job` / `model` preset alone. + +--- + +## MaxText model configs + +Presets: `primus/configs/models/maxtext/`. Examples: `examples/maxtext/configs/MI300X/` and `examples/maxtext/configs/MI355X/`. + +| Model name (file) | Preset path | Example experiment dirs | +| ----------------- | ----------- | ------------------------ | +| `deepseek_v2_16B.yaml` | `primus/configs/models/maxtext/deepseek_v2_16B.yaml` | MI300X, MI355X | +| `grok1.yaml` | `primus/configs/models/maxtext/grok1.yaml` | MI300X | +| `llama2_7B.yaml` | `primus/configs/models/maxtext/llama2_7B.yaml` | MI300X, MI355X | +| `llama2_70B.yaml` | `primus/configs/models/maxtext/llama2_70B.yaml` | MI300X, MI355X | +| `llama3_8B.yaml` | `primus/configs/models/maxtext/llama3_8B.yaml` | MI300X, MI355X | +| `llama3_70B.yaml` | `primus/configs/models/maxtext/llama3_70B.yaml` | MI300X, MI355X | +| `llama3.1_405B.yaml` | `primus/configs/models/maxtext/llama3.1_405B.yaml` | MI355X | +| `llama3.3_70B.yaml` | `primus/configs/models/maxtext/llama3.3_70B.yaml` | MI300X, MI355X | +| `mixtral_8x7B.yaml` | `primus/configs/models/maxtext/mixtral_8x7B.yaml` | MI300X, MI355X | +| `qwen3_14B.yaml` | `primus/configs/models/maxtext/qwen3_14B.yaml` | MI300X, MI355X | +| `qwen3_30B_A3B.yaml` | `primus/configs/models/maxtext/qwen3_30B_A3B.yaml` | MI300X, MI355X | +| `model_base.yaml` | `primus/configs/models/maxtext/model_base.yaml` | Extended by other presets (not a standalone run) | + +**Parallelism:** JAX / MaxText sharding is configured in experiment overrides (for example `ici_fsdp_parallelism`, `ici_data_parallelism`, `dcn_*` in sample experiments). See [MaxText parameters](../03-configuration-reference/maxtext-parameters.md). + +--- + +## Megatron Bridge model configs + +Presets: `primus/configs/models/megatron_bridge/`. Examples: `examples/megatron_bridge/configs/MI300X/` and `examples/megatron_bridge/configs/MI355X/`. + +| Model name (file) | Preset path | Recipe / flavor (from preset) | Example experiment dirs | +| ----------------- | ----------- | ----------------------------- | ------------------------ | +| `qwen3_8b.yaml` | `primus/configs/models/megatron_bridge/qwen3_8b.yaml` | `qwen.qwen3` / `qwen3_8b_finetune_config` | MI300X pretrain, MI355X posttrain | +| `qwen3_32b.yaml` | `primus/configs/models/megatron_bridge/qwen3_32b.yaml` | `qwen.qwen3` / `qwen3_32b_finetune_config` | MI300X, MI355X | +| `llama31_70b.yaml` | `primus/configs/models/megatron_bridge/llama31_70b.yaml` | `llama.llama3` / `llama31_70b_finetune_config` | MI355X | +| `zebra_llama_1B.yaml`, `zebra_llama_3B.yaml`, `zebra_llama_8B.yaml` | `primus/configs/models/megatron_bridge/` | Zebra LLaMA presets | MI300X posttrain | +| `mamba_370M.yaml` | `primus/configs/models/megatron_bridge/mamba_370M.yaml` | Mamba preset | MI300X posttrain | + +Example filenames include `*_pretrain.yaml`, `*_sft_posttrain.yaml`, and `*_lora_posttrain.yaml`; precision such as `bf16_mixed` is set in experiment `overrides`. + +--- + +## Hardware compatibility (example directories) + +Curated example layouts under `examples/` use GPU SKU subdirectories. As of this document: + +| GPU SKU | `examples/megatron/configs/` | `examples/torchtitan/configs/` | `examples/maxtext/configs/` | `examples/megatron_bridge/configs/` | +| ------- | ---------------------------- | ------------------------------ | ---------------------------- | ----------------------------------- | +| **MI300X** | Yes | Yes | Yes | Yes | +| **MI355X** | Yes | Yes | Yes | Yes | +| **MI325X** | Yes | Yes | No | No | + +Megatron and TorchTitan ship MI325X example directories in addition to MI300X and MI355X examples. MaxText includes MI300X and MI355X examples, including MI355X-only entries such as `llama3.1_405B-pretrain.yaml`. Megatron Bridge MI300X examples include Qwen3 8B and 32B pretraining plus Qwen3 32B, Zebra LLaMA, and Mamba post-training examples; LLaMA 3.1 70B Bridge examples appear under MI355X. + +Absence of a SKU directory for a given backend does **not** imply the backend cannot run there; it means this tree does not currently provide a checked-in example path to copy from. + +--- + +## Model architecture reference (Megatron presets) + +Values below come from `primus/configs/models/megatron/` presets (merged through `extends`). **Vocabulary size** is usually defined by the tokenizer / Hugging Face config, not duplicated in every YAML; **context** is `max_position_embeddings` where set in the chain. Use this table as a quick reference for common sizes—not an exhaustive spec of every parameter. + +| Model family | Example preset | Hidden size | Layers | Attention heads | KV heads (GQA) | Max position (context) | +| ------------ | -------------- | ----------- | ------ | ----------------- | ---------------- | ------------------------ | +| LLaMA 2 7B | `llama2_7B.yaml` | 4096 | 32 | 32 | 32 (no GQA) | From `llama2_base` / tokenizer | +| LLaMA 3 8B | `llama3_8B.yaml` | 4096 | 32 | 32 | 8 | 8192 (`llama3_base`) | +| LLaMA 3 70B | `llama3_70B.yaml` | 8192 | 80 | 64 | 8 | 8192 | +| LLaMA 3.1 405B | `llama3.1_405B.yaml` | 16384 | 126 | 128 | 8 | 8192 | +| Qwen3 8B | `qwen3_8B.yaml` | 4096 | 36 | 32 | 8 | 131072 (`qwen2.5_base` chain) | +| Mixtral 8x7B | `mixtral_8x7B_v0.1.yaml` | 4096 | 32 | 32 | — | 4096 | +| DeepSeek-V3 (MoE) | `deepseek_v3.yaml` | 7168 | 61 | 128 (MLA) | — | See preset / HF | +| Mamba 370M | `mamba_370M.yaml` | (Mamba stack) | — | — | — | — | + +For MoE and hybrid architectures (LLaMA 4, Qwen3-MoE, large `moe_*.yaml` templates), refer to the full YAML and upstream model cards; headline dimensions alone do not capture expert layout or MLA. diff --git a/docs/06-developer-guide/testing.md b/docs/06-developer-guide/testing.md new file mode 100644 index 000000000..aa8215ca4 --- /dev/null +++ b/docs/06-developer-guide/testing.md @@ -0,0 +1,175 @@ +# Testing guide + +This guide describes where tests live, how to run them locally, and how they map to CI. For coding standards and PR workflow, see [Contributing Guide](contributing.md). The canonical CI definition is `.github/workflows/ci.yaml`. + +## 1. Test organization + +Layout (simplified from the repository root): + +```text +Primus/ +├── runner/ +│ └── lib/ +│ └── common.sh # Shared logging/helpers sourced by the shell test runner +├── tests/ +│ ├── runner/ # Shell integration tests +│ │ ├── run_all_tests.sh # Master shell test runner +│ │ ├── lib/ # test_common.sh, test_config.sh, test_validation.sh +│ │ ├── helpers/ # Hook and env tests +│ │ ├── test_primus_cli.sh +│ │ ├── test_primus_cli_direct.sh +│ │ ├── test_primus_cli_container.sh +│ │ └── test_primus_cli_slurm.sh +│ ├── unit_tests/ # Python unit tests (pytest) +│ │ ├── agents/ # Tuning-agent tests +│ │ ├── backends/ +│ │ ├── ci/ +│ │ ├── cli/ +│ │ ├── core/ # config, backend, launcher, patches, projection, pipeline_parallel, runtime, trainer, utils +│ │ ├── megatron/ # Megatron-specific unit tests +│ │ ├── modules/ +│ │ └── tools/ +│ ├── trainer/ # Integration tests (typically need GPU + data) +│ │ ├── test_megatron_trainer.py +│ │ ├── test_torchtitan_trainer.py +│ │ └── test_maxtext_trainer.py +│ ├── scripts/ # CI unit/integration launch scripts and UT patches +│ ├── utils.py # Shared test utilities +│ └── run_unit_tests.py # Optional orchestrator (walks tests/, see below) +``` + +`tests/runner/run_all_tests.sh` sources shared helpers from **`runner/lib/common.sh`** at the repository root (not under `tests/runner/`). + +## 2. Running tests + +**Shell integration tests** (CLI behavior, config loading, hooks, environment): + +```bash +bash ./tests/runner/run_all_tests.sh +``` + +**Python unit tests:** + +```bash +pytest tests/unit_tests/ --maxfail=1 -s +``` + +**Trainer integration tests** (GPU and data; might require Hugging Face access): + +```bash +# Megatron +DATA_PATH= pytest tests/trainer/test_megatron_trainer.py -s + +# TorchTitan +DATA_PATH= pytest tests/trainer/test_torchtitan_trainer.py -s + +# MaxText (JAX) — often run via the orchestrator in CI +python ./tests/run_unit_tests.py --jax +``` + +`tests/run_unit_tests.py` walks **`tests/`** and runs every `test_*.py` it finds, except for **`tests/trainer/test_maxtext_trainer.py`** in the default mode (that file is only selected when **`--jax`** is set). That means the default orchestrator run includes **`tests/unit_tests/`** and **`tests/trainer/`** (and any other matching tests), which is broader than `pytest tests/unit_tests/` alone. + +**Orchestrator (default—all discovered tests except MaxText trainer):** + +```bash +python ./tests/run_unit_tests.py +``` + +**Orchestrator (JAX / MaxText trainer only):** + +```bash +python ./tests/run_unit_tests.py --jax +``` + +## 3. Test types + +- **Shell tests:** Exercise runner scripts, CLI wiring, configuration loading, hook execution, and environment setup. Implemented as bash scripts under `tests/runner/` and orchestrated by `run_all_tests.sh`. +- **Unit tests:** Cover configuration parsing, preset loading, CLI behavior, patch registration, adapters, and other library logic under `tests/unit_tests/`. +- **Trainer tests:** End-to-end training against real backends; require AMD GPUs and appropriate data paths (and sometimes tokens). See `.github/workflows/ci.yaml` for CI values such as `DATA_PATH`, `MASTER_PORT`, and `HSA_NO_SCRATCH_RECLAIM`. + +### Test tiers (slim on PRs, full on weekends) + +Each trainer E2E case is a full training launch, so the suites hold one model per +architecture and per feature path. A model that only scales the dims of an +existing test is **deleted**, not kept in a slower tier — its recipe stays +schema-checked by the example-config smoke test below. What remains is split by +the **`@pytest.mark.weekly`** marker (registered in `tests/conftest.py`): + +| Tier | Trigger | Model E2E | Unit tests | +| --- | --- | --- | --- | +| slim | pull request, push to `main`/tags | `-m "not weekly"` | default (slow gates skipped) | +| full | `schedule` cron (Sat 18:00 UTC), or `workflow_dispatch` with `full_tests=true` | no filter | `--run-slow` | + +The workflow derives every test step's arguments from a single `PRIMUS_CI_FULL` +workflow-level env var, so there is one switch to flip. + +Two kinds of case belong in the weekly tier: extended coverage that is not worth +a PR's wall clock — secondary architectures, extra precisions, variants of a +feature whose primary path stays per-PR — and new E2E cases during burn-in, +promoted to the per-PR tier by deleting the marker once a weekend run has shown +them green. Each marked case carries a comment saying what still covers its path +on PRs. + +Reproduce either tier locally: + +```bash +pytest tests/trainer/test_megatron_trainer.py -m "not weekly" -s # what PR CI runs +pytest tests/trainer/test_megatron_trainer.py -s # full matrix +``` + +Two things are deliberately outside this mechanism and stay hidden in **both** +tiers: the cases `--deselect`ed in `ci.yaml` because they are broken on the +current toolchain, and the MaxText models hidden by `JAX_SKIP_UT=1`. + +Do not delete the last remaining test of an architecture or of a feature path. +Two cases with identical `extra_args` are not necessarily isomorphic: the flag +that makes one unique often lives in its recipe yaml, so compare those too. + +### Example-config smoke test + +`tests/unit_tests/configs/test_example_configs.py` loads **every** yaml under +`examples/**/configs/` through the real config stack — env interpolation, +`extends:` merge, module/model preset merge, experiment overrides — and asserts +each resolves to a well-formed experiment whose declared `framework` matches its +backend directory. It is CPU-only (`load_primus_config` does not import +megatron/torchtitan/jax) and runs in seconds. + +This is what makes deleting an isomorphic E2E safe: the recipe stops being +*trained*, but a renamed model preset, a broken `extends:` chain or a `${VAR}` +without a default still fails CI, naming the exact yaml. + +## 4. Writing new tests + +- **Pytest:** Add files named `test_*.py` under `tests/unit_tests/`, following existing patterns and reusing fixtures from `conftest.py` where present. +- **Shell:** Add scripts under `tests/runner/` or extend `tests/runner/run_all_tests.sh` to invoke new suites, consistent with existing `test_primus_cli*.sh` scripts. +- **Backends:** Prefer `tests/unit_tests/backends//` for adapter-focused tests. + +## 5. CI pipeline details + +From `.github/workflows/ci.yaml`: + +- **`code-lint`:** Python 3.12 on GitHub-hosted runners. Runs `pre-commit run --all-files --show-diff-on-failure`, so checks follow `.pre-commit-config.yaml`. +- **`dependency-review`:** Runs `actions/dependency-review-action` on pull requests to flag dependency changes. +- **`run-unittest-torch`:** Self-hosted GPU runner. Installs `requirements.txt`, runs `bash ./tests/runner/run_all_tests.sh`, then `pytest tests/unit_tests/` under coverage (`--cov=primus --cov-report=term-missing`) with specific tests `--deselect`ed (currently some `megatron/cco` TP-overlap and `megatron/transformer/moe` dispatcher cases—see the workflow file). Trainer steps set `MASTER_PORT`, `DATA_PATH`, `HSA_NO_SCRATCH_RECLAIM=1`, and `HF_TOKEN` for Megatron and TorchTitan trainer tests. A follow-up **coverage** step combines unit and E2E coverage. +- **`run-unittest-jax`:** JAX runner. Installs `requirements-jax.txt`, runs the same shell test script, then `python ./tests/run_unit_tests.py --jax` with CI environment variables (for example `JAX_SKIP_UT=1` and `DATA_PATH` as defined in the workflow). + +The torch job picks its tier from `PRIMUS_CI_FULL` and prints the resolved tier in the job summary. See [Test tiers](#test-tiers-slim-on-prs-full-on-weekends). + +The **`build-docker`** job builds images after lint passes; unit test jobs depend on **`code-lint`**, not on **`build-docker`**. + +## 6. Pre-commit hooks + +Install once per clone: + +```bash +pip install pre-commit +pre-commit install +``` + +Run manually on the whole tree: + +```bash +pre-commit run --all-files +``` + +Hooks include: `trailing-whitespace`, `end-of-file-fixer`, `check-yaml`, `check-added-large-files`, `check-merge-conflict`, `isort`, `autoflake`, `black`, and `shellcheck` (as configured in `.pre-commit-config.yaml`). These align with the **`code-lint`** job in CI; see [Contributing Guide](contributing.md) for manual equivalents. diff --git a/docs/06-developer-guide/tooling.md b/docs/06-developer-guide/tooling.md new file mode 100644 index 000000000..982755a41 --- /dev/null +++ b/docs/06-developer-guide/tooling.md @@ -0,0 +1,24 @@ +# Tooling + +Primus ships a set of auxiliary tools for analysis, benchmarking, visualization, installation, and diagnostics. They live under [`tools/`](../../tools/README.md) in the repository; each tool keeps its own README with detailed usage instructions. This page is a lightweight index so the tooling is discoverable from the documentation. + +## Available tools + +| Tool | Directory | What it does | Docs | +|------|-----------|--------------|------| +| **IRLens** | `tools/IRLens/` | Parses XLA HLO text dumps and prints an execution skeleton with control flow, separating communication vs compute ops. | [README](../../tools/IRLens/README.md) | +| **model_stats** | `tools/model_stats/` | Generates charts from the model config registry under `primus/configs/models`. | [README](../../tools/model_stats/README.md) | +| **Pipeline Visualization** | `tools/visualization/pp_vis/` | Visualizes pipeline-parallelism schedules from dumped data or PP-simulator JSON via a local web UI. | [README](../../tools/visualization/pp_vis/README.md) | +| **Auto Benchmark** | `tools/auto_benchmark/` | Interactive benchmark menu for Megatron/TorchTitan on MI300X/MI355X with metrics collection. | [README](../../tools/auto_benchmark/Primus_Auto_Benchmark_README.md) | +| **Backend Gap Report / Engineering Dashboard** | `tools/backend_gap_report/` | Generation and publishing toolchain for the shared Primus engineering dashboard and backend-gap reports. | [README](../../tools/backend_gap_report/README.md) | +| **Installation (venv)** | `tools/installation/` | Reproduces the Primus training Docker environment in a Python virtual environment (no Docker, no sudo). | [README](../../tools/installation/README.md) | +| **Daily Report** | `tools/daily/` | Benchmark summary CSV generation used by CI workflows. | — | +| **Docker Helpers** | `tools/docker/` | Container startup and proxy scripts. | — | +| **Profile Trace** | `tools/profile_trace/` | Trace-file merging utility. | — | + +## Related documentation + +- [Primus tools](../02-user-guide/primus-tools.md)—the full catalog of Primus tools (CLI, tuning agent, ecosystem) with how-to starting points. +- [Tools overview](../../tools/README.md)—the top-level index maintained alongside the code. +- [Profiling and observability](../04-technical-guides/profiling-and-observability.md)—how these tools fit into performance analysis. +- [Benchmarking](../02-user-guide/benchmarking.md)—running the benchmark suites the tools summarize. diff --git a/docs/README.md b/docs/README.md index 48c6c271e..dd4da3e9b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,70 +1,148 @@ -# Primus Documentation +# Primus documentation -Welcome to the Primus documentation! This guide will help you get started with training large-scale foundation models on AMD GPUs. +Documentation for **Primus**, a large-scale foundation model training framework for AMD GPUs. -## 📚 Documentation Structure +--- + +## Choose your starting point + +| I am a... | Start here | +|-----------|------------| +| **New user** | [Getting started](./01-getting-started/overview.md) | +| **User** running training jobs | [User guide](./02-user-guide/pretraining.md) | +| **User** writing YAML configurations | [Configuration reference](./03-configuration-reference/megatron-parameters.md) | +| **Engineer** tuning performance | [Technical guides](./04-technical-guides/performance-tuning.md) | +| **Operator** deploying to production | [Operations](./05-operations/deployment.md) | +| **Contributor** to the codebase | [Developer guide](./06-developer-guide/architecture.md) | + +--- + +## Documentation structure + +### [Getting started](./01-getting-started/) + +Start here if you are new to Primus. -### 🚀 Getting Started +- [Project overview](./01-getting-started/overview.md): what Primus does, who it is for, key capabilities +- [Installation guide](./01-getting-started/installation.md): prerequisites, Docker/bare-metal/Slurm setup +- [Quickstart](./01-getting-started/quickstart.md): first training run in 5 minutes +- [Glossary](./01-getting-started/glossary.md): terms, acronyms, and domain concepts -Start here if you're new to Primus: +### [User guide](./02-user-guide/) -- **[Quick Start Guide](./quickstart.md)** - Get up and running in 5 minutes -- **[CLI User Guide](./cli/PRIMUS-CLI-GUIDE.md)** - Complete command-line reference -- **[CLI Architecture](./cli/CLI-ARCHITECTURE.md)** - Design philosophy and deep dive +Core workflows and day-to-day usage. -### 📖 User Guides +- [CLI reference](./02-user-guide/cli-reference.md): `primus-cli` modes, flags, and subcommands +- [Configuration system](./02-user-guide/configuration-system.md): YAML configuration model, presets, overrides, inheritance +- [Pretraining](./02-user-guide/pretraining.md): pretraining **concepts**: backends, YAML structure, parallelism, configuration inventory +- [Backend training recipes](./02-user-guide/training-recipes.md): pretraining **commands**: copy-paste, GPU-arch-specific run commands +- [Post-training](./02-user-guide/posttraining.md): SFT and LoRA fine-tuning via Megatron Bridge +- [Benchmarking](./02-user-guide/benchmarking.md): GEMM, RCCL, and dense-GEMM benchmark suites +- [Preflight](./02-user-guide/preflight.md): cluster diagnostics and environment validation +- [Projection](./02-user-guide/projection.md): memory and performance projection tools +- [Tuning agent](./02-user-guide/tuning-agent.md): LLM-driven search for an optimal training configuration (uses projection as an oracle) +- [Primus tools](./02-user-guide/primus-tools.md): catalog of all Primus tools and ecosystem projects with how-to starting points -Guides for common workflows and features: +### [Configuration reference](./03-configuration-reference/) -- **[Configuration Guide](./configuration.md)** - YAML/TOML configuration, recommended patterns, and examples -- **[Slurm & Container Usage](./slurm-container.md)** - Distributed training and containerization workflows -- **[Experiment Management](./experiments.md)** - Organizing and tracking your training runs +Parameter references for Primus presets, backend-facing keys, and commonly used environment variables. -### 🔧 Technical References +- [Megatron parameters](./03-configuration-reference/megatron-parameters.md): Megatron-LM backend YAML parameters and Primus overrides +- [TorchTitan parameters](./03-configuration-reference/torchtitan-parameters.md): Primus TorchTitan preset keys and common JobConfig fields +- [MaxText parameters](./03-configuration-reference/maxtext-parameters.md): Primus MaxText overlay defaults and common fields +- [Megatron Bridge parameters](./03-configuration-reference/megatron-bridge-parameters.md): Megatron Bridge recipe, SFT, and pretraining fields surfaced through Primus +- [Environment variables](./03-configuration-reference/environment-variables.md): practical reference for commonly encountered environment variables -In-depth technical documentation: +### [Technical guides](./04-technical-guides/) -- **[Post-Training Guide](./posttraining.md)** - Fine-tuning with SFT and LoRA using Primus CLI -- **[Native SFT & LoRA Quick Start](./README_NATIVE_SFT_LORA_EN.md)** - Megatron-native SFT/LoRA launch guide (BF16/FP8/FP4), no Megatron-Bridge runtime dependency -- **[Performance Projection](./projection.md)** - Project training performance and memory to multi-node configurations -- **[Tuning Agent](./tuning_agent.md)** - LLM-driven search for an optimal training config — parallelism plus batching, schedule, memory, MoE-comm, and precision knobs (drives the projection tool as an oracle) -- **[Preflight](./preflight.md)** - Cluster diagnostics (host/GPU/network info + perf tests) -- **[Benchmark Suite](./benchmark.md)** - GEMM, RCCL, end-to-end benchmarks and profiling -- **[Supported Models](./backends/overview.md#supported-models)** - Supported LLM architectures and feature compatibility matrix -- **[Advanced Features](./advanced.md)** - Mixed precision, parallelism strategies, optimization techniques -- **[Backend Patch Notes](./backends/overview.md)** - Primus-specific arguments for Megatron, TorchTitan, etc. -- **[Backend Extension Guide](./backends/extending-backends.md)** - How to add a new backend using the current adapter/trainer architecture - - **[Megatron Model Extension Guide](./backends/adding-megatron-models.md)** - How to add a new Megatron model config - - **[TorchTitan Model Extension Guide](./backends/adding-torchtitan-models.md)** - How to add a new TorchTitan model config +Deep technical topics for advanced users. -### 💡 Help & Support +- [Parallelism strategies](./04-technical-guides/parallelism-strategies.md): DP, TP, PP, SP, CP, EP, FSDP explained +- [Parallelism configuration](./04-technical-guides/parallelism-configuration.md): per-backend parallelism setup and batch size relationships +- [Collective operations](./04-technical-guides/collective-operations.md): NCCL/RCCL operations and their role in each parallelism strategy +- [Performance tuning](./04-technical-guides/performance-tuning.md): HipBLASLt, Primus-Turbo, FP8, MoE optimization +- [MoE training deep-dive](./04-technical-guides/moe-training.md): bottlenecks and Primus-Turbo optimizations for Mixture-of-Experts models +- [MegaMoE fused MoE layer](./04-technical-guides/mega-moe.md): FlyDSL-based fused MoE layer for EP-only bf16 training, setup and reproduction +- [Data preparation](./04-technical-guides/data-preparation.md): tokenization, data formats, mock data +- [Checkpoint management](./04-technical-guides/checkpoint-management.md): formats, save/load, distributed checkpointing +- [Multi-node networking](./04-technical-guides/multi-node-networking.md): InfiniBand, RoCE, AINIC configuration +- [Profiling and observability](./04-technical-guides/profiling-and-observability.md): Torch profiler, TraceLens, memory snapshots, projection, pp_vis +- [Logging and experiment tracking](./04-technical-guides/logging-and-experiment-tracking.md): TensorBoard, WandB, MLflow setup per backend +- [Fault tolerance and elastic training](./04-technical-guides/fault-tolerance-and-elastic-training.md): graceful exit, auto-resume, in-process restart, torchft +- [Determinism and reproducibility](./04-technical-guides/determinism-and-reproducibility.md): deterministic mode, seeds, trade-offs +- [Diffusion models](./04-technical-guides/diffusion-models/README.md): Flux diffusion architecture, data pipeline, and FP8 / MXFP4 training +- [Native SFT and LoRA](./04-technical-guides/native-sft-lora.md): Megatron-native SFT/LoRA runbook (BF16 / FP8 / FP4), no Megatron-Bridge dependency -Get help and find answers: +### [Operations](./05-operations/) -- **[FAQ](./faq.md)** - Frequently asked questions and troubleshooting -- **[Examples](../examples/README.md)** - Real-world training examples and templates -- **[Preflight Tool](../primus/tools/preflight/README.md)** - Cluster sanity checker to verify environment readiness +Production deployment and operational guidance. -## 🎯 Quick Navigation by Use Case +- [Deployment](./05-operations/deployment.md): container, Slurm, and Kubernetes deployment +- [Monitoring and logging](./05-operations/monitoring-logging.md): WandB, TensorBoard, MLflow, Primus logging +- [Troubleshooting](./05-operations/troubleshooting.md): common failures, diagnostics, and fixes +- [Security](./05-operations/security.md): secrets handling, container security, dependencies + +### [Developer guide](./06-developer-guide/) + +For contributors and maintainers. + +- [Architecture](./06-developer-guide/architecture.md): system design, runtime, backends, patch system +- [Contributing](./06-developer-guide/contributing.md): development setup, code style, PR process +- [Testing](./06-developer-guide/testing.md): test types, running tests, CI pipeline +- [Extending backends](./06-developer-guide/extending-backends.md): adding new training backends +- [Adding models](./06-developer-guide/adding-models.md): adding model configurations per backend +- [Model support matrix](./06-developer-guide/model-support-matrix.md): supported models per backend and GPU +- [CLI architecture](./06-developer-guide/cli-architecture.md): CLI internals: subcommand discovery, dispatch, and launch wrappers +- [Backend patch notes](./06-developer-guide/backend-patch-notes.md): Primus-specific backend arguments and the files they patch +- [Tooling](./06-developer-guide/tooling.md): auxiliary analysis, benchmarking, visualization, and diagnostics tools + +--- + +## Common use cases ### I want to... -- **Train a model locally** → [Quick Start](./quickstart.md) + [CLI User Guide](./cli/PRIMUS-CLI-GUIDE.md) -- **Run distributed training on Slurm** → [Slurm & Container Usage](./slurm-container.md) -- **Configure my training run** → [Configuration Guide](./configuration.md) -- **Project performance to multi-node** → [Performance Projection](./projection.md) -- **Auto-tune my training config (parallelism + knobs)** → [Tuning Agent](./tuning_agent.md) -- **Benchmark performance** → [Benchmark Suite](./benchmark.md) -- **Understand the CLI design** → [CLI Architecture](./cli/CLI-ARCHITECTURE.md) -- **Troubleshoot issues** → [FAQ](./faq.md) +| Goal | Document | +|------|----------| +| Understand what Primus is | [Overview](./01-getting-started/overview.md) | +| Browse all Primus tools | [Primus tools](./02-user-guide/primus-tools.md) | +| Install Primus | [Installation](./01-getting-started/installation.md) | +| Run my first training | [Quickstart](./01-getting-started/quickstart.md) | +| Get an exact run command for my model/GPU | [Backend training recipes](./02-user-guide/training-recipes.md) | +| Write a training YAML configuration | [Configuration system](./02-user-guide/configuration-system.md) | +| Look up a Megatron parameter | [Megatron parameters](./03-configuration-reference/megatron-parameters.md) | +| Look up a TorchTitan parameter | [TorchTitan parameters](./03-configuration-reference/torchtitan-parameters.md) | +| Look up an environment variable | [Environment variables](./03-configuration-reference/environment-variables.md) | +| Understand parallelism strategies | [Parallelism strategies](./04-technical-guides/parallelism-strategies.md) | +| Configure parallelism for my model | [Parallelism configuration](./04-technical-guides/parallelism-configuration.md) | +| Tune training performance | [Performance tuning](./04-technical-guides/performance-tuning.md) | +| Train a Mixture-of-Experts model | [MoE training deep-dive](./04-technical-guides/moe-training.md) | +| Use the fused MegaMoE layer | [MegaMoE fused MoE layer](./04-technical-guides/mega-moe.md) | +| Train a diffusion (Flux) model | [Diffusion models](./04-technical-guides/diffusion-models/README.md) | +| Fine-tune with native SFT / LoRA | [Native SFT and LoRA](./04-technical-guides/native-sft-lora.md) | +| Auto-tune my training configuration | [Tuning agent](./02-user-guide/tuning-agent.md) | +| Profile a training run | [Profiling and observability](./04-technical-guides/profiling-and-observability.md) | +| Track experiments (WandB/MLflow/TensorBoard) | [Logging and experiment tracking](./04-technical-guides/logging-and-experiment-tracking.md) | +| Survive node failures on long runs | [Fault tolerance and elastic training](./04-technical-guides/fault-tolerance-and-elastic-training.md) | +| Reproduce results bit-for-bit | [Determinism and reproducibility](./04-technical-guides/determinism-and-reproducibility.md) | +| Prepare training data | [Data preparation](./04-technical-guides/data-preparation.md) | +| Deploy to a Slurm cluster | [Deployment](./05-operations/deployment.md) | +| Debug a training failure | [Troubleshooting](./05-operations/troubleshooting.md) | +| Contribute to Primus | [Contributing](./06-developer-guide/contributing.md) | +| Understand the code architecture | [Architecture](./06-developer-guide/architecture.md) | +| Add a new training backend | [Extending backends](./06-developer-guide/extending-backends.md) | + +--- -## 🔗 External Resources +## External resources -- [Primus-Turbo](https://github.com/AMD-AGI/Primus-Turbo) - High-performance operators & modules -- [Primus-SaFE](https://github.com/AMD-AGI/Primus-SaFE) - Stability & platform layer -- [AMD ROCm Documentation](https://rocm.docs.amd.com/) -- [TorchTitan Documentation](https://github.com/pytorch/torchtitan) +- [Primus-Turbo](https://github.com/AMD-AGI/Primus-Turbo): high-performance operators and kernels +- [Primus-SaFE](https://github.com/AMD-AGI/Primus-SaFE): external stability/platform layer; this repository does not include a production integration guide +- [AMD ROCm documentation](https://rocm.docs.amd.com/) +- [Megatron-LM](https://github.com/NVIDIA/Megatron-LM) +- [TorchTitan](https://github.com/pytorch/torchtitan) +- [MaxText](https://github.com/AI-Hypercomputer/maxtext) --- -**Need help?** Check the [FAQ](./faq.md) or open an issue on [GitHub](https://github.com/AMD-AGI/Primus/issues). +**Need help?** Open an issue on [GitHub](https://github.com/AMD-AGI/Primus/issues). diff --git a/docs/backend-gap/dashboard-data/reports/megatron-upstream-main-2026-04-30.json b/docs/backend-gap/dashboard-data/reports/megatron-upstream-main-2026-04-30.json deleted file mode 100644 index 4c0f1313f..000000000 --- a/docs/backend-gap/dashboard-data/reports/megatron-upstream-main-2026-04-30.json +++ /dev/null @@ -1,87 +0,0 @@ -{ - "id": "megatron-upstream-main-2026-04-30", - "title": "Primus Megatron vs upstream main", - "backend": { - "key": "megatron", - "label": "Megatron" - }, - "generated_at": "2026-04-30", - "status": "verified", - "scope": "Current Megatron-LM bundled in Primus vs upstream NVIDIA/Megatron-LM origin/main.", - "local": { - "source_path": "third_party/Megatron-LM", - "version": "0.16.0rc0", - "commit": "d3528a21", - "commit_date": "2026-03-06" - }, - "upstream": { - "repo": "https://github.com/NVIDIA/Megatron-LM", - "ref": "origin/main", - "version": "0.18.0", - "commit": "0d98cb83", - "commit_date": "2026-04-29" - }, - "stats": { - "commit_gap": 403, - "diff_files": 1300, - "insertions": 615916, - "deletions": 326835 - }, - "integration": { - "backend_files": 136, - "tracked_files": 365, - "integration_model": "third_party/Megatron-LM + Primus outer trainer / adapter / patches" - }, - "highlights": [ - "Primus pins Megatron Core source version 0.16.0rc0 while upstream main declares 0.18.0.", - "Upstream main is 403 commits ahead with broad FSDP, MoE, Hybrid/Mamba, inference, RL, MiMo, and resharding changes.", - "Primus has a large Megatron integration layer with direct dependencies on upstream training, distributed, optimizer, MoE, and pipeline internals." - ], - "dashboard_summary": { - "headline": "Megatron is 403 commits behind upstream main, with broad API and capability drift across training, MoE, FSDP, inference, and Hybrid paths.", - "recommendation": "plan sync", - "why_it_matters": [ - "Primus pins Megatron Core source version 0.16.0rc0 while upstream main declares 0.18.0.", - "The gap spans 1300 changed files, so this is a broad upstream movement.", - "Package dependency metadata is mostly unchanged; the main risk is API and capability drift.", - "Primus trainer, adapter, optimizer, MoE, and patch layers depend on upstream internals." - ], - "feature_deltas": [ - "Megatron-FSDP fixes for mixed precision, MXFP8, uneven DTensor, frozen parameters, and async save", - "MoE shared expert overlap, FlexDispatcher support, router score function, and A2A combine backprop overlap", - "Hybrid/Mamba model path expansion and Mamba-to-Hybrid naming movement", - "Inference CUDA graphs, prefix caching, per-block MoE routing storage, and text generation controller updates", - "MiMo, RL, resharding, and broader test coverage updates" - ], - "dependency_deltas": [ - "pyproject.toml project dependencies are unchanged in the compared range.", - "megatron/core/requirements.txt remains torch and packaging.", - "Source-declared Megatron Core version moved from 0.16.0rc0 to 0.18.0.", - "Selected workflow and documentation paths changed, but the primary drift is upstream API/capability movement." - ], - "integration_risks": [ - "Primus MegatronTrainer imports and wraps megatron.training, checkpointing, initialization, distributed, optimizer, and dataset internals.", - "Primus patches Megatron argument parsing, training flow, scheduler, recompute, Muon optimizer, and runtime hooks.", - "Primus Turbo paths depend on transformer engine spec provider, MoE token dispatcher, transformer config, and tensor-parallel internals.", - "FSDP, MoE/router, inference, and Hybrid/Mamba upstream movement overlap with existing Primus patch surfaces." - ] - }, - "artifacts": [ - { - "label": "Detailed Report (PDF)", - "path": "./reports/megatron/upstream-main/report.pdf", - "format": "pdf", - "language": "en", - "kind": "detail", - "primary": true - }, - { - "label": "One-Page Summary (PDF)", - "path": "./reports/megatron/upstream-main/summary.pdf", - "format": "pdf", - "language": "en", - "kind": "summary", - "primary": false - } - ] -} diff --git a/docs/backend-gap/dashboard-data/reports/torchtitan-upstream-main-2026-04-21.json b/docs/backend-gap/dashboard-data/reports/torchtitan-upstream-main-2026-04-21.json deleted file mode 100644 index 83fd07bee..000000000 --- a/docs/backend-gap/dashboard-data/reports/torchtitan-upstream-main-2026-04-21.json +++ /dev/null @@ -1,88 +0,0 @@ -{ - "id": "torchtitan-upstream-main-2026-04-21", - "title": "Primus TorchTitan vs upstream main", - "backend": { - "key": "torchtitan", - "label": "TorchTitan" - }, - "generated_at": "2026-04-21", - "status": "verified", - "scope": "Current TorchTitan bundled in Primus vs upstream pytorch/torchtitan origin/main.", - "local": { - "source_path": "third_party/torchtitan", - "version": "0.1.0", - "commit": "5fb7cc2e", - "commit_date": "2025-10-15" - }, - "upstream": { - "repo": "https://github.com/pytorch/torchtitan", - "ref": "origin/main", - "version": "0.2.2", - "commit": "fc54b897", - "commit_date": "2026-04-20" - }, - "stats": { - "commit_gap": 493, - "diff_files": 447, - "insertions": 56071, - "deletions": 17716 - }, - "integration": { - "backend_files": 90, - "tracked_files": 147, - "integration_model": "third_party/torchtitan + Primus outer adapter / trainer / patches" - }, - "highlights": [ - "Primus bundles TorchTitan 0.1.0 while upstream main is at 0.2.2.", - "Upstream main tracks the latest nightly on CUDA cu130 and ROCm 7.1, while the current Primus baseline is centered on cu126.", - "Primus has a non-trivial outer integration layer with direct dependencies on upstream internal paths." - ], - "dashboard_summary": { - "headline": "TorchTitan is 493 commits behind upstream main, with dependency-channel and Primus integration impact.", - "recommendation": "urgent sync", - "why_it_matters": [ - "Primus bundles TorchTitan 0.1.0 while upstream main is at 0.2.2.", - "Upstream main moved to nightly cu130 and ROCm 7.1 while the Primus baseline remains centered on cu126.", - "The gap includes 447 changed files, so this is not a narrow dependency bump.", - "Primus trainer, adapter, and patch layers directly depend on upstream internal paths." - ], - "feature_deltas": [ - "GraphTrainer precompile and cudagraph paths", - "MoE token dispatcher and DeepEP / HybridEP distributed runtime updates", - "Fused QKV GQAttention and FlexAttention context-parallel work", - "FSDP2 fully_shard and broader distributed runtime updates", - "New model families and reorganized shared model abstractions" - ], - "dependency_deltas": [ - "README nightly channel moved from nightly/cu126 to nightly/cu130.", - "ROCm workflow coverage now targets nightly/rocm7.1.", - "torchdata is explicitly installed as 0.12.0.dev20260327 in workflow.", - "datasets lower bound moved from >=2.21.0 to >=3.6.0 with CI constraints.", - "safetensors, einops, pillow, av, torchvision, numpy, and pyrefly were added or expanded." - ], - "integration_risks": [ - "Primus trainer imports torchtitan.config.job_config.JobConfig and torchtitan.train.Trainer.", - "Turbo attention patches reference upstream llama3, llama4, and deepseek_v3 internals.", - "FP8 and MX patches depend on torchtitan.components.quantization.float8 and mx.", - "MoE grouped-mm patch depends on torchtitan.models.moe.moe." - ] - }, - "artifacts": [ - { - "label": "Detailed Report (PDF)", - "path": "./reports/torchtitan/upstream-main/report.pdf", - "format": "pdf", - "language": "en", - "kind": "detail", - "primary": true - }, - { - "label": "One-Page Summary (PDF)", - "path": "./reports/torchtitan/upstream-main/summary.pdf", - "format": "pdf", - "language": "en", - "kind": "summary", - "primary": false - } - ] -} diff --git a/docs/backend-gap/reports/megatron/upstream-main/report.md b/docs/backend-gap/reports/megatron/upstream-main/report.md deleted file mode 100644 index 94c0e6672..000000000 --- a/docs/backend-gap/reports/megatron/upstream-main/report.md +++ /dev/null @@ -1,122 +0,0 @@ -# Primus Megatron vs Upstream `main` Comparison Report - -> Date: 2026-04-30 -> Scope: Current Megatron-LM bundled in `Primus` vs upstream `NVIDIA/Megatron-LM` `origin/main` - -## High-Level Comparison - -| Item | Current Megatron-LM in Primus | Upstream `NVIDIA/Megatron-LM` `main` | -| --- | --- | --- | -| Source-declared Megatron Core version | `0.16.0rc0` from `megatron/core/package_info.py` | `0.18.0` from `origin/main:megatron/core/package_info.py` | -| Pinned commit | `d3528a21` | `0d98cb83` | -| Commit date | 2026-03-06 | 2026-04-29 | -| Commit gap | Behind by `403` commits | - | -| Git relation | `merge-base(HEAD, origin/main) = HEAD` | - | -| Diff size | `1300 files changed, 615916 insertions, 326835 deletions` | - | -| Integration model | `third_party/Megatron-LM` + Primus outer trainer / adapter / patches | Upstream mainline | -| Integration footprint | `primus/backends/megatron/` has about `136` files; report-covered Primus Megatron directories have about `365` files | No Primus integration layer | -| Private submodule commits | None | - | - -## Dependency and Package Metadata Comparison - -| Item | Current Megatron-LM in Primus | Upstream `main` | -| --- | --- | --- | -| `pyproject.toml` project dependencies | `torch>=2.6.0`, `numpy`, `packaging>=24.2` | No diff in the compared range | -| `megatron/core/requirements.txt` | `torch`, `packaging` | No diff in the compared range | -| Source-declared version source | `megatron/core/package_info.py`: `0.16.0rc0` | `0.18.0` with git SHA suffix logic | -| Dev dependency groups | Present in `pyproject.toml` | No direct diff in the compared range | -| Workflow / docs surface | Existing CI and docs | About `42` changed entries across selected workflow/docs paths | - -## Directory and Capability Differences - -### Megatron-FSDP and Distributed Runtime - -Upstream `main` has continued Megatron-FSDP and distributed-runtime work: - -- Unified and refactored Megatron-FSDP documentation. -- Fixed FusedAdam `use_decoupled_grad` handling in Megatron-FSDP. -- Added and fixed mixed-precision / MXFP8 / uneven DTensor / frozen parameter paths. -- Added support around DCP and FSDP async save. -- Refined parameter layout, all-gather / reduce-scatter overlap, and precision-aware optimizer behavior. - -### MoE, Router, and Expert Parallelism - -The upstream gap includes active MoE and expert-parallel changes: - -- Improved shared expert overlap and FlexDispatcher support. -- Added a new router score function. -- Added NVFP4 native weights for DDP. -- Fixed non-quantized MoE dispatch padding. -- Added overlap for A2A combine backprop with wgrad GEMM. -- Added broader MoE inference and prefix-cache related updates. - -### Hybrid / Mamba and Inference - -Upstream `main` has expanded the Hybrid/Mamba and inference surface: - -- Added `megatron/core/models/hybrid/`. -- Renamed Mamba model / stack concepts toward Hybrid naming. -- Added YARN support and DeepSeek Sparse Attention paths for Hybrid/Mamba work. -- Added CUDA graph support for MTP inference and prefix caching. -- Added per-block MoE routing storage for prefix caching. -- Reorganized order of operations in inference context and text generation controller. - -### MiMo, RL, and Resharding - -Upstream has broader non-core-training surface area: - -- Updated `examples/mimo/` and `megatron/core/models/mimo/`. -- Added `ColocatedBridgeCommunicator` and `MimoOptimizer` related paths. -- Updated RL agents, token throughput / packing metrics, and RL inference flows. -- Added and evolved `megatron/core/resharding/` APIs and copy services. - -## Change Hotspots - -| Area | Representative changes | -| --- | --- | -| `megatron/core/distributed/fsdp/` | Megatron-FSDP fixes, docs, DTensor conversion, mixed precision, MXFP8, async save | -| `megatron/core/transformer/moe/` | Shared expert overlap, router score function, MoE dispatch fixes | -| `megatron/core/inference/` | CUDA graph / prefix caching / hybrid inference / text generation controller updates | -| `megatron/core/models/hybrid/` | Added Hybrid model/block/layer allocation/spec files | -| `megatron/core/models/mimo/` | MiMo communication, config, optimizer, and submodule updates | -| `megatron/core/resharding/` | Added / evolved resharding planner, execution, transforms, and copy services | -| `examples/rl/` and `megatron/rl/` | RL agents, inference API, packing metrics, and reward fixes | -| `tests/` | Broad functional and unit-test churn across MoE, inference, FSDP, SSM, resharding, and training | - -## Primus Outer Integration Layer - -### Related Directories - -The Primus outer integration layer is mainly distributed across: - -- `primus/backends/megatron/` -- `primus/modules/trainer/megatron/` -- `primus/configs/models/megatron/` -- `examples/megatron/` -- `tests/unit_tests/backends/megatron/` - -### Directly Referenced Upstream Paths - -| Primus code location | Direct upstream dependency path | -| --- | --- | -| `primus/modules/trainer/megatron/trainer.py` | `megatron.core.distributed`, `megatron.core.optimizer`, `megatron.training.*`, dataset builders, checkpointing, initialization, parallel state | -| `primus/modules/trainer/megatron/utils.py` | `megatron.core.parallel_state`, `megatron.training.global_vars`, pipeline-parallel schedules, transformer block/layer helpers | -| `primus/backends/megatron/megatron_base_trainer.py` | `megatron.training.arguments`, `megatron.training.initialize` | -| `primus/backends/megatron/core/extensions/transformer_engine_spec_provider.py` | `megatron.core.extensions.transformer_engine`, `megatron.core.transformer.moe.experts`, `megatron.core.models.backends` | -| `primus/backends/megatron/core/extensions/primus_turbo.py` | `megatron.core.tensor_parallel`, `megatron.core.transformer.moe.token_dispatcher`, `megatron.core.transformer.transformer_config`, `megatron.training.global_vars` | -| `primus/backends/megatron/patches/turbo/te_spec_provider_patches.py` | `megatron.core.extensions`, `megatron.core.models.gpt.gpt_layer_specs`, `megatron.core.models.gpt.moe_module_specs`, `megatron.core.transformer.multi_token_prediction` | -| `primus/backends/megatron/patches/muon_optimizer_patches.py` | `megatron.training.training.get_megatron_optimizer` namespace | - -## Evidence Sources - -- `third_party/Megatron-LM/megatron/core/package_info.py` -- `third_party/Megatron-LM/pyproject.toml` -- `third_party/Megatron-LM/megatron/core/requirements.txt` -- `third_party/Megatron-LM/README.md` -- `third_party/Megatron-LM/.github/workflows/*` -- [NVIDIA/Megatron-LM](https://github.com/NVIDIA/Megatron-LM) -- `primus/backends/megatron/*` -- `primus/modules/trainer/megatron/*` -- `primus/configs/models/megatron/*` -- `examples/megatron/*` -- `tests/unit_tests/backends/megatron/*` diff --git a/docs/backend-gap/reports/megatron/upstream-main/summary.md b/docs/backend-gap/reports/megatron/upstream-main/summary.md deleted file mode 100644 index 57819f857..000000000 --- a/docs/backend-gap/reports/megatron/upstream-main/summary.md +++ /dev/null @@ -1,69 +0,0 @@ -# Primus Megatron Upstream Gap One-Page Summary - -> Date: 2026-04-30 - -## High-Level Comparison - -| Item | Current Megatron-LM in Primus | Upstream `NVIDIA/Megatron-LM` `main` | Impact | -| --- | --- | --- | --- | -| Source-declared Megatron Core version | `0.16.0rc0` from `megatron/core/package_info.py` | `0.18.0` | Spans multiple upstream version steps | -| Pinned commit | `d3528a21` | `0d98cb83` | Current submodule is materially behind | -| Commit gap | Behind by `403` commits | - | Not a small patch-level gap | -| Diff size | `1300 files changed, 615916 insertions, 326835 deletions` | - | Very broad upstream churn | -| Integration model | `third_party/Megatron-LM` + Primus trainer / adapter / patches | Upstream mainline | Upgrade is more than a submodule bump | -| Integration footprint | `primus/backends/megatron/` has about `136` files; report-covered Primus Megatron directories have about `365` files | No Primus integration layer | Upgrade blast radius is large | -| Dependency metadata | `pyproject.toml` and `megatron/core/requirements.txt` unchanged in compared range | Same dependency metadata | Main concern is API / capability drift, not dependency metadata drift | - -## Dependency / Package Facts - -| Item | Current Megatron-LM in Primus | Upstream `main` | -| --- | --- | --- | -| `pyproject.toml` project dependencies | `torch>=2.6.0`, `numpy`, `packaging>=24.2` | No direct diff | -| `megatron/core/requirements.txt` | `torch`, `packaging` | No direct diff | -| Source-declared version source | `megatron/core/package_info.py`: `0.16.0rc0` | `0.18.0` with git SHA suffix logic | - -## Representative Upstream Changes - -| Area | Representative changes | -| --- | --- | -| Megatron-FSDP | Documentation refactor; mixed precision, MXFP8, uneven DTensor, frozen parameter, async save, and optimizer fixes | -| MoE / Router | Shared expert overlap, FlexDispatcher support, new router score function, non-quantized dispatch padding fix, A2A combine backprop overlap | -| Hybrid / Mamba | Added `megatron/core/models/hybrid`; renamed Mamba concepts toward Hybrid; added YARN and DeepSeek Sparse Attention paths | -| Inference | CUDA graph support for MTP inference, prefix caching, per-block MoE routing storage, text generation controller reordering | -| MiMo / RL | MiMo communication and optimizer updates; RL agents, inference flows, token throughput and packing metrics | -| Resharding | Updated planner, execution, transforms, and copy services | -| Tests | Broad functional and unit-test changes across MoE, inference, FSDP, SSM, resharding, and training | - -## Primus Outer Integration Layer - -The Primus Megatron integration layer is broad and directly coupled to upstream internals: - -- `primus/backends/megatron/` -- `primus/modules/trainer/megatron/` -- `primus/configs/models/megatron/` -- `examples/megatron/` -- `tests/unit_tests/backends/megatron/` - -Direct upstream dependency paths include: - -- `megatron.training.*` -- `megatron.core.distributed.*` -- `megatron.core.optimizer.*` -- `megatron.core.transformer.moe.*` -- `megatron.core.extensions.transformer_engine` -- `megatron.core.models.gpt.*` -- `megatron.core.pipeline_parallel.*` - -## Recommendation - -Treat this as a planned sync, not a trivial dependency bump. The largest risks are API compatibility in trainer initialization, distributed/FSDP paths, MoE/router behavior, inference/Hybrid changes, and Primus patch compatibility. - -## Evidence Sources - -- `third_party/Megatron-LM/megatron/core/package_info.py` -- `third_party/Megatron-LM/pyproject.toml` -- `third_party/Megatron-LM/megatron/core/requirements.txt` -- [NVIDIA/Megatron-LM](https://github.com/NVIDIA/Megatron-LM) -- `primus/backends/megatron/*` -- `primus/modules/trainer/megatron/*` -- `tests/unit_tests/backends/megatron/*` diff --git a/docs/backend-gap/reports/torchtitan/upstream-main/report.md b/docs/backend-gap/reports/torchtitan/upstream-main/report.md deleted file mode 100644 index 8e9ea2df3..000000000 --- a/docs/backend-gap/reports/torchtitan/upstream-main/report.md +++ /dev/null @@ -1,147 +0,0 @@ -# Primus TorchTitan vs Upstream `main` Comparison Report - -> Date: 2026-04-21 -> Scope: Current TorchTitan bundled in `Primus` vs upstream `pytorch/torchtitan` `origin/main` - -## High-Level Comparison - -| Item | Current TorchTitan in Primus | Upstream `pytorch/torchtitan` `main` | -| --- | --- | --- | -| Submodule version | `0.1.0` | `0.2.2` | -| Pinned commit | `5fb7cc2e` | `fc54b897` | -| Commit date | 2025-10-15 | 2026-04-20 | -| Commit gap | Behind by `493` commits | - | -| Git relation | `merge-base(HEAD, origin/main) = HEAD` | - | -| Diff size | `447 files changed, 56071 insertions, 17716 deletions` | - | -| Integration model | `third_party/torchtitan` + Primus outer layer (`adapter / trainer / patches`) | Upstream mainline | -| Integration footprint | `primus/backends/torchtitan/` has about `90` files; total across report-covered directories is about `147` files | No Primus integration layer | -| Private submodule commits | None | - | -| Extra private requirements | `requirements-torchtitan.txt` has comments only, with no effective dependency entries | - | - -## Torch / TorchAO / Dependency Comparison - -### Install Channels and Version Semantics - -| Item | Current TorchTitan in Primus | Upstream `main` | -| --- | --- | --- | -| README nightly channel | `nightly/cu126` | `nightly/cu130` | -| Workflow install channel | `nightly/cu126` | `nightly/cu130`; ROCm uses `nightly/rocm7.1` | -| Workflow `torch-version` parameter | No explicit fixed version | Empty string in `set-matrix.yaml` | -| `v0.1.0` release anchor | `torch-2.8.0.dev20250617+cu126` / `torchao-0.12.0.dev20250617+cu126` | - | -| `v0.2.2` release anchor | - | `torch-2.12.0.dev20260220+cu126` / `torchao-0.17.0.dev20260220+cu126` | - -### Python Dependency Differences - -| Dependency | Current TorchTitan in Primus | Upstream `main` | -| --- | --- | --- | -| `torchdata` | `>=0.8.0` | Explicitly installed as `0.12.0.dev20260327` in workflow | -| `datasets` | `>=2.21.0` | `>=3.6.0`, constrained to `<4.8.0` in CI | -| `tokenizers` | No fixed lower bound | `>=0.15.0` | -| `safetensors` | Not present | Added | -| `wandb` | Dev-only dependency | Moved into runtime dependencies | -| `einops` | Not present | Added | -| `pillow` | Not present | Added | -| `av` | Not present | Added for VLM-related dependencies | -| `torchvision` | Not present | Added in VLM / CPU tests | -| `expecttest` | Pinned at `0.1.6` | `>=0.2.0` | -| `pyrefly` | Not present | `==0.45.1` | -| `numpy` | Not present | Added to dev / CI dependencies | -| `tyro` | No fixed lower bound | Raised to `>=1.0.5` in CI dependencies | -| `tomli` | Present in runtime dependencies | Removed from runtime dependencies | - -## Directory and Capability Differences - -### Model Directories - -The current TorchTitan in Primus still follows an earlier model layout. Upstream `main` has added new model families and shared abstractions: - -- Added `torchtitan/models/common/` with shared modules: `attention / decoder / embedding / feed_forward / linear / moe / param_init / rmsnorm / rope / token_dispatcher` -- Added `torchtitan/models/gpt_oss/` -- Added `torchtitan/models/qwen3_vl/` -- Added `torchtitan/models/flux/`; `flux` was moved from `experiments/flux` -- `llama3 / llama4 / qwen3 / deepseek_v3` were reorganized into a more unified shape around `config_registry.py`, `parallelize.py`, and `state_dict_adapter.py` - -### `experiments/` Directory - -The current TorchTitan in Primus keeps an earlier experiments layout. Upstream `main` added or moved the following: - -- Added `torchtitan/experiments/autoparallel/` -- Added `torchtitan/experiments/graph_trainer/` -- Added `torchtitan/experiments/rl/` -- Added `torchtitan/experiments/transformers_modeling_backend/` -- Added `torchtitan/experiments/ft/`; content from `components/ft` moved here -- Main content from `torchtitan/experiments/flux/` moved into `torchtitan/models/flux/` -- Multiple files in `torchtitan/experiments/simple_fsdp/` were removed or moved -- Multiple files in `torchtitan/experiments/torchcomms/` were removed - -### `distributed/` and `components/` - -The current TorchTitan in Primus keeps older distributed/components layouts. Upstream `main` added or continuously evolved these paths: - -- Added `torchtitan/distributed/compile.py` -- Added `torchtitan/distributed/context_parallel.py` -- Added `torchtitan/distributed/deepep/` -- Added `torchtitan/distributed/fsdp.py` -- `torchtitan/distributed/tensor_parallel.py` continuously updated -- `torchtitan/distributed/pipeline_parallel.py` continuously updated -- `torchtitan/distributed/expert_parallel.py` continuously updated -- Added `torchtitan/components/quantization/module_utils.py` -- `torchtitan/components/quantization/float8.py` continuously updated -- `torchtitan/components/quantization/mx.py` continuously updated -- `torchtitan/components/metrics.py` continuously updated -- `torchtitan/components/optimizer.py` continuously updated -- `torchtitan/components/tokenizer.py` continuously updated - -## Change Hotspots - -| Area | Representative changes | -| --- | --- | -| `tests/unit_tests/` | Broader unit-test coverage | -| `.github/workflows/` | Added `integration_test_4gpu_rl.yaml`, `integration_test_8gpu_autoparallel.yaml`, `integration_test_8gpu_graph_trainer.yaml`, `integration_test_8gpu_rl_h100.yaml`, `integration_test_8gpu_transformers_modeling_backend.yaml`, `set-matrix.yaml` | -| `torchtitan/experiments/graph_trainer/` | Added `compile.py / cudagraph.py / precompile.py / passes.py / storage.py`; covers `llama3 / deepseek_v3 / qwen3`; includes `tests/test_profiler.py` | -| `torchtitan/models/common/` | Added `attention / decoder / embedding / feed_forward / linear / moe / param_init / rmsnorm / rope / token_dispatcher` | -| `docs/` | Added `docs/mxfp8.md`, `docs/bf16_optimizer_states.md`, and updated docs such as `release / debugging / metrics / datasets / checkpoint` | -| `torchtitan/models/flux/` | `flux` moved from `experiments/flux` into `models/flux` | -| `torchtitan/distributed/` | Added `compile.py`, `context_parallel.py`, `deepep/deepep.py`, `deepep/hybridep.py`, `fsdp.py` | -| `torchtitan/experiments/rl/` | Added `actors/`, `models/vllm_wrapper.py`, `simple_grpo_sum_digits.py` | -| `torchtitan/components/` | Added `module_utils`; continuous updates in `float8 / mx / metrics / optimizer / tokenizer / validate` | -| `torchtitan/models/gpt_oss/` | Added `gpt_oss` | -| `torchtitan/models/qwen3_vl/` | Added `qwen3_vl` | -| `torchtitan/experiments/autoparallel/` | Added `llama3`, `deepseek_v3`, `local_map_deepseek_v3` | -| `torchtitan/experiments/transformers_modeling_backend/` | Added transformers-based modeling backend experiments | - -## Primus Outer Integration Layer - -### Related Directories - -The Primus outer integration layer is mainly distributed across: - -- `primus/backends/torchtitan/` -- `primus/modules/trainer/torchtitan/` -- `primus/configs/modules/torchtitan/` -- `examples/torchtitan/` -- `runner/helpers/hooks/train/pretrain/torchtitan/` -- `tests/unit_tests/backends/torchtitan/` -- `tests/trainer/test_torchtitan_trainer.py` - -### Directly Referenced Upstream Paths - -| Primus code location | Direct upstream dependency path | -| --- | --- | -| `primus/modules/trainer/torchtitan/pre_trainer.py` | `torchtitan.config.job_config.JobConfig`, `torchtitan.train.Trainer` | -| `primus/backends/torchtitan/patches/turbo/attention_patches.py` | `torchtitan.models.llama3.model.model`, `torchtitan.models.llama4.model.model`, `torchtitan.models.deepseek_v3.model.model` | -| `primus/backends/torchtitan/patches/turbo/fp8_linear_patches.py` | `torchtitan.components.quantization.float8` | -| `primus/backends/torchtitan/patches/turbo/mx_linear_patches.py` | `torchtitan.components.quantization.mx` | -| `primus/backends/torchtitan/patches/turbo/moe_grouped_mm_patches.py` | `torchtitan.models.moe.moe` | - -## Evidence Sources - -- `third_party/torchtitan/pyproject.toml` -- `third_party/torchtitan/README.md` -- `third_party/torchtitan/docs/release.md` -- `third_party/torchtitan/.github/workflows/*` -- [pytorch/torchtitan](https://github.com/pytorch/torchtitan) -- [pytorch/torchtitan/docs/release.md](https://github.com/pytorch/torchtitan/blob/main/docs/release.md) -- `primus/backends/torchtitan/*` -- `primus/modules/trainer/torchtitan/*` -- `docs/backends/torchtitan/patch-notes.md` diff --git a/docs/backend-gap/reports/torchtitan/upstream-main/summary.md b/docs/backend-gap/reports/torchtitan/upstream-main/summary.md deleted file mode 100644 index 69ae788cd..000000000 --- a/docs/backend-gap/reports/torchtitan/upstream-main/summary.md +++ /dev/null @@ -1,82 +0,0 @@ -# Primus TorchTitan Upstream Gap One-Page Summary - -> Date: 2026-04-21 - -## High-Level Comparison - -| Item | Current TorchTitan in Primus | Upstream `pytorch/torchtitan` `main` | Impact | -| --- | --- | --- | --- | -| Submodule version | `0.1.0` | `0.2.2` | Spans multiple upstream release iterations | -| Pinned commit | `5fb7cc2e` | `fc54b897` | Current submodule is significantly behind | -| Commit gap | Behind by `493` commits | - | Not a small patch-level gap anymore | -| Integration model | `third_party/torchtitan` + Primus outer layer (`adapter / trainer / patches`) | Upstream mainline | Upgrade is more than a submodule bump | -| Integration footprint | `primus/backends/torchtitan/` has about `90` files; total across summary-covered directories is about `147` files | No Primus integration layer | Upgrade blast radius is large | -| Private submodule commits | None | - | Git history remains traceable and clean | -| Extra private requirements | `requirements-torchtitan.txt` has comments only, with no effective dependency entries | - | Primus mainly reuses upstream dependencies | -| Torch / TorchAO version semantics | `nightly/cu126`; `v0.1.0` anchors `torch-2.8.0.dev20250617+cu126` / `torchao-0.12.0.dev20250617+cu126` | GitHub current `main` tracks the latest nightly at the time (`CUDA cu130`, `ROCm rocm7.1`) | The key comparison is `cu126` generation vs `cu130/rocm7.1` generation | -| `torchdata` | `>=0.8.0` | `0.12.0.dev20260327` (workflow) | Data stack baseline increased | -| `datasets` | `>=2.21.0` | `>=3.6.0, <4.8.0` | Compatibility and streaming behavior changed | -| `tokenizers` | No fixed lower bound | `>=0.15.0` | Tokenizer baseline increased | -| Runtime added deps | No runtime `safetensors / wandb / einops / pillow` | Added | Runtime environment is heavier | -| Multimodal added deps | No `av / torchvision` | Added | Multimodal environment requirements increased | -| New upstream capabilities | Existing training main path | `graph_trainer / autoparallel / rl / qwen3_vl / gpt_oss` | Upstream capability surface is much broader | - -## Torch / TorchAO / Dependencies - -| Item | Current TorchTitan in Primus | Upstream `main` | -| --- | --- | --- | -| README nightly channel | `nightly/cu126` | `nightly/cu130` | -| Workflow install channel | `nightly/cu126` | `nightly/cu130`; ROCm uses `nightly/rocm7.1` | -| Workflow `torch-version` parameter | No explicit fixed version | Empty string in `set-matrix.yaml` | -| `v0.1.0` release anchor | `torch-2.8.0.dev20250617+cu126` / `torchao-0.12.0.dev20250617+cu126` | - | -| `torchdata` | `>=0.8.0` | Explicitly installed as `0.12.0.dev20260327` in workflow | -| `datasets` | `>=2.21.0` | `>=3.6.0, <4.8.0` | -| `tokenizers` | No fixed lower bound | `>=0.15.0` | -| `safetensors` | No | Added | -| `wandb` | Dev-only dependency | Moved into runtime dependencies | -| `einops` | No | Added | -| `pillow` | No | Added | -| `av` | No | Added | -| `torchvision` | No | Added | - -## Representative Upstream Changes - -| Area | Representative changes | -| --- | --- | -| `models/` | Added `gpt_oss`, `qwen3_vl`; moved `flux` into `models/flux`; added shared `models/common` layer | -| `distributed/` | Added `compile.py`, `context_parallel.py`, `deepep/`, `fsdp.py` | -| `experiments/` | Added `autoparallel`, `graph_trainer`, `rl`, `transformers_modeling_backend`, `ft` | -| `components/` | Added `module_utils`; continuous updates in `float8 / mx / metrics / optimizer / tokenizer / validate` | -| `.github/workflows/` | Added `integration_test_4gpu_rl.yaml`, `integration_test_8gpu_autoparallel.yaml`, `integration_test_8gpu_graph_trainer.yaml`, `integration_test_8gpu_transformers_modeling_backend.yaml`, `set-matrix.yaml` | -| `docs/` | Added `docs/mxfp8.md`, `docs/bf16_optimizer_states.md`, and updated docs such as `release / debugging / metrics / datasets / checkpoint` | - -## Primus Outer Integration Layer - -The Primus outer integration layer is mainly distributed across: - -- `primus/backends/torchtitan/` -- `primus/modules/trainer/torchtitan/` -- `primus/configs/modules/torchtitan/` -- `examples/torchtitan/` -- `runner/helpers/hooks/train/pretrain/torchtitan/` -- `tests/unit_tests/backends/torchtitan/` -- `tests/trainer/test_torchtitan_trainer.py` - -Directly referenced upstream paths include: - -- `torchtitan.config.job_config.JobConfig` -- `torchtitan.train.Trainer` -- `torchtitan.models.llama3.model.model` -- `torchtitan.models.llama4.model.model` -- `torchtitan.models.deepseek_v3.model.model` -- `torchtitan.components.quantization.float8` -- `torchtitan.components.quantization.mx` -- `torchtitan.models.moe.moe` - -## Evidence Sources - -- `third_party/torchtitan/README.md` -- `third_party/torchtitan/docs/release.md` -- `third_party/torchtitan/.github/workflows/*` -- [pytorch/torchtitan](https://github.com/pytorch/torchtitan) -- [pytorch/torchtitan/docs/release.md](https://github.com/pytorch/torchtitan/blob/main/docs/release.md) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 000000000..76cefc389 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,47 @@ +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +import re +from pathlib import Path + + +def _get_version(): + init = Path(__file__).parent.parent / "primus" / "__init__.py" + match = re.search(r'^__version__\s*=\s*["\']([^"\']+)["\']', init.read_text(), re.MULTILINE) + if not match: + raise ValueError("Could not find __version__ in primus/__init__.py") + return match.group(1) + + +# Project info +version = _get_version() +release = version +project = f"AMD Primus {version}" +author = "Advanced Micro Devices, Inc." +copyright = "Copyright (c) %Y Advanced Micro Devices, Inc. All rights reserved." + +# Theme-related configs +html_theme = "rocm_docs_theme" +html_theme_options = { + "flavor": "ai-ecosystem", + "link_main_doc": True, + "repository_url": "https://github.com/AMD-AGI/Primus", + "use_repository_button": True, + "use_issues_button": True, + "use_download_button": True, +} +html_title = project + +# Sphinx extension-related configs +extensions = ["rocm_docs"] +external_toc_path = "./sphinx/_toc.yml" +external_projects_current_project = "primus" + +# Publish the llms.txt index at the docs site root and let +# rocm-docs-core generate llms-full.txt after each build (the llms.txt standard, +# https://llmstxt.org/). See the rocm-docs-core guide: +# https://rocm.docs.amd.com/projects/rocm-docs-core/en/latest/user_guide/llms.html +rocm_docs_generate_llms = True diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 000000000..1f8761f24 --- /dev/null +++ b/docs/license.md @@ -0,0 +1,4 @@ +# License + +```{include} ../LICENSE +``` diff --git a/docs/preflight.md b/docs/preflight.md deleted file mode 100644 index b4290e04e..000000000 --- a/docs/preflight.md +++ /dev/null @@ -1,78 +0,0 @@ -# Preflight - -`preflight` is Primus’ cluster diagnostic tool. It can generate a **fast info report** (host/GPU/network) and can also run **performance tests** (GEMM + intra/inter-node comm) to help spot misconfiguration or outliers before large distributed runs. - -- **User-facing entry**: `primus-cli … -- preflight [args]` -- **Implementation entrypoint**: `primus/cli/subcommands/preflight.py` - -## Quick start - -### Info report only (fast) - -```bash -primus-cli direct -- preflight --host --gpu --network -``` - -### Full preflight (info + perf tests) - -```bash -primus-cli direct -- preflight -``` - -### Perf tests only - -```bash -primus-cli direct -- preflight --perf-test -``` - -## Common usage (Slurm) - -Info report only (fast): - -```bash -primus-cli slurm srun -N 4 -- preflight --host --gpu --network -``` - -Full preflight (info + perf tests): - -```bash -primus-cli slurm srun -N 4 -- preflight -``` - -Perf tests only: - -```bash -primus-cli slurm srun -N 4 -- preflight --perf-test -``` - -## CLI flags - -Selection: -- `--host`: host info (CPU, memory, PCIe) -- `--gpu`: GPU info -- `--network`: network info -- `--perf-test`: run perf tests only (GEMM + comm). This is slower. - -Reporting: -- `--dump-path`: output directory (default: `output/preflight`) -- `--report-file-name`: base report name (default: `preflight_report`) -- `--disable-pdf`: disable PDF generation - -Perf-test extras: -- `--plot`: generate plots (only used with `--perf-test`) - -Backward compatibility: -- `--check-host/--check-gpu/--check-network` are supported as aliases for `--host/--gpu/--network`. - -## Outputs - -By default, outputs are written under `output/preflight`. - -Typical report files: -- `preflight_report.md` / `preflight_report.pdf`: **info report** (host/GPU/network) -- `preflight_report_perf.md` / `preflight_report_perf.pdf`: **perf report** (GEMM + comm tests) - -## Notes - -- For multi-node runs, use `primus-cli slurm …` (or your preferred launcher) so distributed environment variables are set correctly. -- If you only want a quick environment snapshot, prefer `--host --gpu --network`. diff --git a/docs/sphinx/_toc.yml.in b/docs/sphinx/_toc.yml.in new file mode 100644 index 000000000..c033923bf --- /dev/null +++ b/docs/sphinx/_toc.yml.in @@ -0,0 +1,145 @@ +# Variables of the form ${} are substituted, currently the following +# list is supported: +# - ${branch} (or {branch}) the name of the current branch +# - ${url} (or {url}) github url of the current project +# - ${project:} base url of the documentation of +# based on intersphinx_mapping. +# These comments will also be removed. +defaults: + numbered: false +root: 01-getting-started/overview.md +subtrees: + - caption: Getting started + entries: + - file: 01-getting-started/quickstart.md + title: Quickstart + - file: 01-getting-started/installation.md + title: Installation and setup + - file: 01-getting-started/glossary.md + title: Glossary + + - caption: User guide + entries: + - file: 02-user-guide/primus-tools.md + title: Primus tools + - file: 02-user-guide/cli-reference.md + title: CLI reference + - file: 02-user-guide/configuration-system.md + title: Configuration system + - file: 02-user-guide/pretraining.md + title: Pretraining workflows + - file: 02-user-guide/training-recipes.md + title: Backend training recipes + - file: 02-user-guide/posttraining.md + title: Post-training workflows + - file: 02-user-guide/node-smoke-test-instruction.md + title: Node-smoke test instruction + - file: 02-user-guide/preflight.md + title: Preflight diagnostics + - file: 02-user-guide/preflight-without-container.md + title: Run preflight without a container + - file: 02-user-guide/benchmarking.md + title: Benchmark suite + - file: 02-user-guide/projection.md + title: Memory and performance projection + - file: 02-user-guide/tuning-agent.md + title: Tuning agent + + - caption: Configuration reference + entries: + - file: 03-configuration-reference/megatron-parameters.md + title: Megatron backend + - file: 03-configuration-reference/torchtitan-parameters.md + title: TorchTitan backend + - file: 03-configuration-reference/megatron-bridge-parameters.md + title: Megatron Bridge backend + - file: 03-configuration-reference/maxtext-parameters.md + title: MaxText backend + - file: 03-configuration-reference/environment-variables.md + title: Environment variables + + - caption: Technical guides + entries: + - file: 04-technical-guides/parallelism-strategies.md + title: Parallelism strategies + - file: 04-technical-guides/parallelism-configuration.md + title: Parallelism configuration + - file: 04-technical-guides/multi-node-networking.md + title: Multi-node networking + - file: 04-technical-guides/collective-operations.md + title: Collective operations (NCCL/RCCL) + - file: 04-technical-guides/data-preparation.md + title: Data preparation + - file: 04-technical-guides/checkpoint-management.md + title: Checkpoint management + - file: 04-technical-guides/moe-training.md + title: MoE training + - file: 04-technical-guides/mega-moe.md + title: MegaMoE fused MoE layer + - file: 04-technical-guides/performance-tuning.md + title: Performance tuning + - file: 04-technical-guides/profiling-and-observability.md + title: Profiling and observability + - file: 04-technical-guides/logging-and-experiment-tracking.md + title: Logging and experiment tracking + - file: 04-technical-guides/fault-tolerance-and-elastic-training.md + title: Fault tolerance and elastic training + - file: 04-technical-guides/determinism-and-reproducibility.md + title: Determinism and reproducibility + - file: 04-technical-guides/native-sft-lora.md + title: Native SFT LoRA + - file: 04-technical-guides/diffusion-models/architecture_overview.md + title: Diffusion model architecture + subtrees: + - entries: + - file: 04-technical-guides/diffusion-models/flux_architecture.md + title: Flux architecture + - file: 04-technical-guides/diffusion-models/fp8_training.md + title: FP8 training + - file: 04-technical-guides/diffusion-models/mxfp4_training.md + title: MXFP4 training + - file: 04-technical-guides/diffusion-models/data_preprocessing.md + title: Data preprocessing + - file: 04-technical-guides/diffusion-models/energon_integration.md + title: Energon integration + - file: 04-technical-guides/diffusion-models/adding_new_models.md + title: Adding new models + - file: 04-technical-guides/diffusion-models/api_reference.md + title: API reference + + - caption: Operations + entries: + - file: 05-operations/deployment.md + title: Deployment + - file: 05-operations/monitoring-logging.md + title: Monitoring and logging + - file: 05-operations/troubleshooting.md + title: Troubleshooting + - file: 05-operations/security.md + title: Security considerations + + - caption: Developer guide + entries: + - file: 06-developer-guide/architecture.md + title: Architecture overview + - file: 06-developer-guide/cli-architecture.md + title: CLI architecture + - file: 06-developer-guide/contributing.md + title: Contributing + - file: 06-developer-guide/adding-models.md + title: Adding model configurations + - file: 06-developer-guide/extending-backends.md + title: Extending backends + - file: 06-developer-guide/model-support-matrix.md + title: Model support matrix + - file: 06-developer-guide/testing.md + title: Testing + - file: 06-developer-guide/tooling.md + title: Tooling + - file: 06-developer-guide/backend-patch-notes.md + title: Backend patch notes + + - caption: About + entries: + - file: license.md + title: License diff --git a/docs/sphinx/requirements.in b/docs/sphinx/requirements.in new file mode 100644 index 000000000..2e037fb21 --- /dev/null +++ b/docs/sphinx/requirements.in @@ -0,0 +1 @@ +rocm-docs-core[llms]==1.38.0 diff --git a/docs/sphinx/requirements.txt b/docs/sphinx/requirements.txt new file mode 100644 index 000000000..f7587f5c5 --- /dev/null +++ b/docs/sphinx/requirements.txt @@ -0,0 +1,285 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile docs/sphinx/requirements.in +# +accessible-pygments==0.0.5 + # via pydata-sphinx-theme +alabaster==1.0.0 + # via sphinx +asttokens==3.0.2 + # via stack-data +attrs==26.1.0 + # via + # jsonschema + # jupyter-cache + # referencing +babel==2.18.0 + # via + # pydata-sphinx-theme + # sphinx +beautifulsoup4==4.15.0 + # via pydata-sphinx-theme +breathe==4.36.0 + # via rocm-docs-core +certifi==2026.6.17 + # via requests +cffi==2.1.0 + # via + # cryptography + # pynacl +charset-normalizer==3.4.9 + # via requests +click==8.4.2 + # via + # jupyter-cache + # sphinx-external-toc +comm==0.2.3 + # via ipykernel +cryptography==49.0.0 + # via pyjwt +debugpy==1.8.21 + # via ipykernel +decorator==5.3.1 + # via ipython +docutils==0.22.4 + # via + # myst-parser + # pydata-sphinx-theme + # sphinx + # sphinx-markdown-builder +executing==2.2.1 + # via stack-data +fastjsonschema==2.21.2 + # via + # nbformat + # rocm-docs-core +gitdb==4.0.12 + # via gitpython +gitpython==3.1.53 + # via rocm-docs-core +greenlet==3.5.3 + # via sqlalchemy +idna==3.18 + # via requests +imagesize==2.0.0 + # via sphinx +importlib-metadata==9.0.0 + # via + # jupyter-cache + # myst-nb +ipykernel==7.3.0 + # via myst-nb +ipython==9.15.0 + # via + # ipykernel + # myst-nb +ipython-pygments-lexers==1.1.1 + # via ipython +jedi==0.20.0 + # via ipython +jinja2==3.1.6 + # via + # myst-parser + # sphinx +jsonschema==4.26.0 + # via nbformat +jsonschema-specifications==2025.9.1 + # via jsonschema +jupyter-cache==1.0.1 + # via myst-nb +jupyter-client==8.9.1 + # via + # ipykernel + # nbclient +jupyter-core==5.9.1 + # via + # ipykernel + # jupyter-client + # nbclient + # nbformat +markdown-it-py==4.2.0 + # via + # mdit-py-plugins + # myst-parser +markupsafe==3.0.3 + # via jinja2 +matplotlib-inline==0.2.2 + # via + # ipykernel + # ipython +mdit-py-plugins==0.6.1 + # via myst-parser +mdurl==0.1.2 + # via markdown-it-py +myst-nb==1.4.0 + # via rocm-docs-core +myst-parser==5.1.0 + # via myst-nb +nbclient==0.11.0 + # via + # jupyter-cache + # myst-nb +nbformat==5.10.4 + # via + # jupyter-cache + # myst-nb + # nbclient +nest-asyncio2==1.7.2 + # via ipykernel +packaging==26.2 + # via + # ipykernel + # pydata-sphinx-theme + # sphinx +parso==0.8.7 + # via jedi +pexpect==4.9.0 + # via ipython +platformdirs==4.11.0 + # via jupyter-core +prompt-toolkit==3.0.52 + # via ipython +psutil==7.2.2 + # via + # ipykernel + # ipython +ptyprocess==0.7.0 + # via pexpect +pure-eval==0.2.3 + # via stack-data +pycparser==3.0 + # via cffi +pydata-sphinx-theme==0.15.4 + # via + # rocm-docs-core + # sphinx-book-theme +pygithub==2.9.1 + # via rocm-docs-core +pygments==2.20.0 + # via + # accessible-pygments + # ipython + # ipython-pygments-lexers + # pydata-sphinx-theme + # sphinx +pyjwt[crypto]==2.13.0 + # via pygithub +pynacl==1.6.2 + # via pygithub +python-dateutil==2.9.0.post0 + # via jupyter-client +pyyaml==6.0.3 + # via + # jupyter-cache + # myst-nb + # myst-parser + # rocm-docs-core + # sphinx-external-toc +pyzmq==27.1.0 + # via + # ipykernel + # jupyter-client +referencing==0.37.0 + # via + # jsonschema + # jsonschema-specifications +requests==2.34.2 + # via + # pygithub + # sphinx +rocm-docs-core[llms]==1.38.0 + # via -r docs/sphinx/requirements.in +roman-numerals==4.1.0 + # via sphinx +rpds-py==2026.6.3 + # via + # jsonschema + # referencing +six==1.17.0 + # via python-dateutil +smmap==5.0.3 + # via gitdb +snowballstemmer==3.1.1 + # via sphinx +soupsieve==2.9.1 + # via beautifulsoup4 +sphinx==9.1.0 + # via + # breathe + # myst-nb + # myst-parser + # pydata-sphinx-theme + # rocm-docs-core + # sphinx-book-theme + # sphinx-copybutton + # sphinx-design + # sphinx-external-toc + # sphinx-markdown-builder + # sphinx-multitoc-numbering + # sphinx-notfound-page +sphinx-book-theme==1.1.4 + # via rocm-docs-core +sphinx-copybutton==0.5.2 + # via rocm-docs-core +sphinx-design==0.7.0 + # via rocm-docs-core +sphinx-external-toc==1.1.0 + # via rocm-docs-core +sphinx-markdown-builder==0.6.10 + # via rocm-docs-core +sphinx-multitoc-numbering==0.1.3 + # via sphinx-external-toc +sphinx-notfound-page==1.1.0 + # via rocm-docs-core +sphinxcontrib-applehelp==2.0.0 + # via sphinx +sphinxcontrib-devhelp==2.0.0 + # via sphinx +sphinxcontrib-htmlhelp==2.1.0 + # via sphinx +sphinxcontrib-jsmath==1.0.1 + # via sphinx +sphinxcontrib-qthelp==2.0.0 + # via sphinx +sphinxcontrib-serializinghtml==2.0.0 + # via sphinx +sqlalchemy==2.0.51 + # via jupyter-cache +stack-data==0.6.3 + # via ipython +tabulate==0.10.0 + # via + # jupyter-cache + # sphinx-markdown-builder +tornado==6.5.7 + # via + # ipykernel + # jupyter-client +traitlets==5.15.1 + # via + # ipykernel + # ipython + # jupyter-client + # jupyter-core + # matplotlib-inline + # nbclient + # nbformat +typing-extensions==4.16.0 + # via + # beautifulsoup4 + # jupyter-client + # myst-nb + # pydata-sphinx-theme + # pygithub + # referencing + # sqlalchemy +urllib3==2.7.0 + # via + # pygithub + # requests +wcwidth==0.8.2 + # via prompt-toolkit +zipp==4.1.0 + # via importlib-metadata diff --git a/docs/weekly_reports/2026-W17-primus-weekly.md b/docs/weekly_reports/2026-W17-primus-weekly.md deleted file mode 100644 index 08a1db41e..000000000 --- a/docs/weekly_reports/2026-W17-primus-weekly.md +++ /dev/null @@ -1,147 +0,0 @@ -# Primus Weekly Engineering Report — 2026-W17 - -## 1. Time Window - -- Start: Monday 2026-04-20 00:00:00 Asia/Shanghai (GMT+8) -- End: Friday 2026-04-24 16:56 Asia/Shanghai (GMT+8) (report generation time) -- Branch observed: `origin/main` - -## 2. Executive Summary - -- **9 PRs merged to `main`** in the weekly window (Mon 2026-04-20 00:00 GMT+8 → Fri 2026-04-24 16:56 GMT+8). -- Category breakdown: **Bug Fix: 3**, **Performance Optimization: 2**, **CI/Infra: 2**, **Docs: 2**; Turbo/Dependency Version Update, Refactor, Other: 0. -- **No Primus-Turbo version bump this week.** Current `PRIMUS_TURBO_COMMIT` pins in both `ci.yaml` (`333b68d7`) and `benchmark.yaml` (`a4488f6c`) are unchanged since March; month-to-date Turbo drift on `main` is zero. -- **Megatron-LM upstream drift: `plan sync` recommended.** Pin is `d3528a21` (2026-03-06); upstream `main` HEAD is `a1165fab` with **365 commits ahead**, including MoE routing/dispatch improvements, MTP layers in token-per-expert logging, `MambaModel` → `HybridModel` rename, NVRx async compatibility, DDP parameter-layout refactor, TE `release_v2.14`, and several inference and checkpoint fixes. -- **torchtitan upstream drift: `urgent sync` recommended.** Pin is `5fb7cc2e` (2025-10-15); upstream `main` HEAD is `d40df991` with **514 commits ahead**, spanning GraphTrainer precompile (SAC + FSDP bucketing, CooR for DeepSeek-V3, CUDA-graph annotation pass), MoE token-dispatcher rewrite, Fused QKV GQAttention, FlexAttention + CP, FSDP2 `fully_shard`, and VLM workflow dependency pinning. -- **Primus-Turbo month-to-date drift: `monitor` (no action needed).** Both CI and benchmark pins are identical to their 2026-03-30 values. -- This week was dominated by **Megatron backend hardening** against upstream API drift: #672 realigns the patched MoE overlap `TransformerLayerSchedulePlan` after the upstream `post_attn` removal; #671 makes the muon optimizer wrapper runtime-signature aware against `get_megatron_optimizer()` drift; #674 rewrites `recompute_layer_patches` to be byte-identical to upstream `TransformerBlock._checkpointed_forward` and seeds a SHA256 fingerprint guard. -- Perf-category PRs: **#673** (`pp_warmup` optimization — parallel fwd+bwd warm-up on every PP rank, with a bit-identical loss-parity UT); **#684** introduces the opt-in `PRIMUS_EXIT_FAST` env to skip Python interpreter teardown after a successful Megatron trainer `cleanup()`, shaving ~22s of wall-time tail per process on MI355X DSV3 EP8 and ~2m off the MI300X Megatron-LM E2E UT suite. -- Tooling/infra: **#687** lands the shared backend-gap dashboard publishing toolchain (`tools/backend_gap_report/`) plus the initial torchtitan baseline report; this same shared site is being extended this week to also surface the weekly-report series. **#678** switches the JAX CI runner to a TAS node. Docs: **#683** published the initial W17 run, and **#690** refreshed it mid-week. - -## 3. Weekly PR Update Table - -| PR | Merged Time (GMT+8) | Category | Key Update | -| --- | --- | --- | --- | -| [#690](https://github.com/AMD-AGI/Primus/pull/690) `[Primus weekly report] 2026-W17` (author: `cursor[bot]`) | 2026-04-24 14:16 | Docs | Mid-week refresh of the automated W17 weekly report: extends the window through Fri 2026-04-24 09:10 GMT+8, brings the total to 6 PRs at the time of that run, and refreshes upstream drift snapshots for Megatron-LM and torchtitan. Superseded by this report. | -| [#684](https://github.com/AMD-AGI/Primus/pull/684) `feat(megatron): add PRIMUS_EXIT_FAST to exit training much faster` (author: `lhzhang333`) | 2026-04-24 11:17 | Performance Optimization | Adds an opt-in `PRIMUS_EXIT_FAST=1` env to `MegatronBaseTrainer.cleanup()`: after a successful cleanup and only when `on_error=False`, Primus calls `os._exit(0)` to skip normal Python interpreter/torchrun teardown. Measured ~22s saved on MI355X DSV3 4L EP8 post-train tail and ~2m saved on the MI300X Megatron-LM E2E UT suite; documented as experimental (not recommended for production graceful-shutdown paths). | -| [#687](https://github.com/AMD-AGI/Primus/pull/687) `feat(backend-gap): add dashboard publishing toolchain and torchtitan baseline report` (author: `WangLingxun`) | 2026-04-24 10:37 | CI/Infra | Ships the shared backend-gap dashboard publishing toolchain under `tools/backend_gap_report/` (static site shell + `build_dashboard_index.py` + `build_site_bundle.py`), seeds project-level skill docs, and publishes the initial TorchTitan-vs-upstream-main report set (`docs/backend-gap/reports/torchtitan/upstream-main/{report,summary}.md` + `dashboard-data/reports/torchtitan-upstream-main-2026-04-21.json`). This is the shared dashboard that this week's weekly-report flow extends to surface `Weekly Reports` as a first-class section. | -| [#672](https://github.com/AMD-AGI/Primus/pull/672) `fix(megatron): drop stale post_attn usage from patched MoE overlap schedule` (author: `WangLingxun`) | 2026-04-23 15:13 | Bug Fix | Aligns Primus `TransformerModelChunkSchedulePlan` with the current upstream `TransformerLayerSchedulePlan` after Megatron removed the `post_attn` node, eliminating a runtime `AttributeError` under `patch_moe_overlap=True`; preserves `ep_overlap_early_attn_memory_release` ordering and adds a regression test for the patched MoE-overlap schedule-plan path. | -| [#671](https://github.com/AMD-AGI/Primus/pull/671) `Fix/megatron muon optimizer signature` (author: `WangLingxun`) | 2026-04-23 15:07 | Bug Fix | Hardens the muon optimizer wrapper against `get_megatron_optimizer()` runtime signature drift; uses `inspect.signature` to support both keyword and mixed positional/keyword call patterns; resolves muon-specific args only when a muon optimizer is detected and passes non-muon optimizers through unchanged; adds regression tests for keyword vs positional `config_overrides`, muon vs non-muon, and correct parameter binding under different signatures. | -| [#678](https://github.com/AMD-AGI/Primus/pull/678) `[CICD]switch jax runner to tas node` (author: `llying-001`) | 2026-04-23 10:32 | CI/Infra | Switches the JAX unit-test runner to a TAS node in `.github/workflows/ci.yaml`. | -| [#683](https://github.com/AMD-AGI/Primus/pull/683) `[Primus weekly report] 2026-W17` (author: `cursor[bot]`) | 2026-04-23 07:11 | Docs | Adds the initial automated W17 weekly engineering report (`docs/weekly_reports/2026-W17-primus-weekly.md`) covering the Mon 2026-04-20 → Wed 2026-04-22 window. Superseded by #690 and by the current run. | -| [#673](https://github.com/AMD-AGI/Primus/pull/673) `opt(megatron): optimize pp_warmup and add corresponding UT` (author: `lhzhang333`) | 2026-04-22 20:32 | Performance Optimization | Rewrites `run_pp_warmup` to fabricate per-rank synthetic activations/output grads using `get_tensor_shapes`, so every PP rank runs one warm-up fwd+bwd **in parallel** (bypassing p2p) and exercises all CUDA/TE/FP8/NCCL lazy init paths concurrently; adds rigorous state-isolation (RNG + grad buffers) plus an end-to-end UT validating bit-for-bit loss parity and iter-1 speedup on `PP_SIZE>1`. | -| [#674](https://github.com/AMD-AGI/Primus/pull/674) `fix(megatron): adapt recompute_layer_patches to the upstream Megatron and add UT` (author: `lhzhang333`) | 2026-04-22 16:30 | Bug Fix | Rewrites inner `custom`/`checkpoint_handler` closures to be byte-identical to Megatron's latest `TransformerBlock._checkpointed_forward`; keeps the delegation fast-path when `recompute_layer_ids` is unset; seeds the upstream-source SHA256 fingerprint guard; adds UTs for pipeline-stage offset mapping and the FP8-no-grad skip rule; removes the stale `PrimusTransformerBlock` subclass. | - -## 4. Megatron-LM Drift Overview - -- Upstream: `https://github.com/NVIDIA/Megatron-LM.git` (`main`) -- Pinned in Primus `main` (`third_party/Megatron-LM`): `d3528a21301db2d12e92912b3ec025dc8a2ed4d6` — *fix(moe): fix TE general_gemm API change (#3582)*, 2026-03-06 -- Upstream `main` HEAD: `a1165fabcad97eae3778f386839c233dfabf3f8b` — *Inference: Fix broken functional tests on gitlab (#4454)* (2026-04-24) -- Commit gap: **upstream is 365 commits ahead of Primus pin** -- Month-to-date movement on Primus side: pin advanced from `3bec9aa9` (2026-02-26) → `d3528a21` (2026-03-06) inside PR #654 merged 2026-04-10 (282-commit upstream catch-up). No further submodule SHA change in April. -- Recommendation: **plan sync** — several releases' worth of MoE, precision, Mamba→Hybrid rename, and FSDP/DistOpt changes have accumulated; schedule a controlled bump rather than urgent. This week's Primus-side fixes (#671, #672, #674) already pre-harden Primus against concrete upstream API drift points that must be validated during the sync. - -### Notable upstream areas that have moved since the pin - -- **MoE routing / dispatch**: new router score function (#3673), shared-expert overlap improvements including FlexDispatcher support (#2207), fix for unnecessary permute padding in non-quantized dispatch (#4038); MTP layers now counted in token-per-expert logging (#4412). -- **Mamba / Hybrid models**: `MambaModel`/`MambaStack` → `HybridModel`/`HybridStack` rename including the outside-of-`megatron/core` rename (#4159, follow-up to #4099); Mamba inference optimization (#4414); QK layernorm for DPA in `MambaModel` (#4067); port DeepSeek Sparse Attention to `MambaModel` (#3553); fine-grained activation offloading (#4173). -- **Low precision / TE**: NVFP4 native DDP weights (#4005); Enable FP8 DPA for MXFP8 recipe (#4066); TransformerEngine bumped to `release_v2.14` (#4331). -- **Checkpointing**: add `--async-ckpt-use-cpu-shm` (#4355); remove cross-rank sync during checkpoint load & deprecate `state_dict_loader.load_state_dict` (#2864); fix potential coredump on save (#1871); RL onload optimizer after logprobs (#4235). -- **FSDP / Distributed Optimizer / DDP**: Megatron-FSDP 0.5.0; `expt_device_mesh` fix for MoE-only (#3831); MFSDP `decoupled_grad`/DistOpt mechanics (#4133); layerwise-optimizer fixes (#4272, #4138); DDP refactor extracting parameter-layout computation into an optimizer classmethod (#3812); MFSDP log mcore detection only after imports succeed (#4400). -- **Inference / RL / misc**: NVRx async compatibility + defer resiliency import (#4420); misc inference fixes (#4397, #4454); RL token-throughput & packing metrics (#3877); nvtx_decorator checks `_nvtx_enabled` at call time (#4184); NullTokenizer for pretraining to reduce I/O (#4057); rampup batch-size scheduler replaced with custom step schedule (#4411, reverted earlier work #3779 via #4404). - -### Megatron-LM upstream feature delta table - -| Area | New Upstream Capability | Evidence (PR/Commit) | Potential Impact to Primus | -| --- | --- | --- | --- | -| MoE | Router new score function;
shared-expert overlap for FlexDispatcher;
MoE DPA / permute-padding fixes;
MTP layers in token-per-expert logging | NVIDIA/Megatron-LM #3673, #2207, #4038, #4412 | Could unlock additional MoE recipes (DeepSeek / Mixtral variants) already referenced in Primus `examples/megatron/configs/MI300X/deepseek_v*`. | -| Mamba / Hybrid | `MambaModel` → `HybridModel` rename (outside-core rename landed);
Mamba inference opt;
DeepSeek Sparse Attention port;
fine-grained activation offloading | NVIDIA/Megatron-LM #4099, #4159, #4414, #3553, #4173 | **Breaking import rename** — Primus patch system (`primus/backends/megatron`) must audit any `MambaModel`/`MambaStack` references before the next bump. | -| Low precision / TE | NVFP4 native DDP weights;
FP8 DPA for MXFP8;
TE bumped to `release_v2.14` | NVIDIA/Megatron-LM #4005, #4066, #4331 | Requires matching TE/AITER versions in Primus Dockerfile; likely coordinated with next Turbo+aiter bump. | -| Checkpoint I/O | Async checkpoint CPU-SHM;
removed cross-rank sync load;
save coredump fix;
RL optimizer onload ordering | NVIDIA/Megatron-LM #4355, #2864, #1871, #4235 | Expected improvement for Primus pretrain at scale; validate with Primus async-ckpt configs. | -| FSDP / DistOpt / DDP | Megatron-FSDP 0.5.0;
MFSDP `decoupled_grad`/DistOpt fixes;
layerwise-optimizer fixes;
DDP parameter-layout refactor | NVIDIA/Megatron-LM #3831, #4133, #4272, #4138, #3812, #4400 | Primus FSDP/DDP launch paths (`primus/modules/trainer/megatron/*`) should be re-benchmarked post-sync; this directly touches paths exercised by this week's #673 `pp_warmup` optimization. | -| Inference / resiliency | NVRx async compatibility;
misc inference fixes;
nvtx decorator call-time gating | NVIDIA/Megatron-LM #4420, #4397, #4454, #4184 | Aligns with Primus inference-adjacent tooling; low-risk but worth validating Primus resiliency paths during the controlled bump. | -| Schedule plan API | `post_attn` node already removed from `TransformerLayerSchedulePlan` (consumed in Primus by #672) | Primus/#672, upstream schedule-plan change | Primus-side fix has already shipped; treat as confirmed drift that future bumps must keep in sync. | - -## 5. torchtitan Drift Overview - -- Upstream: `https://github.com/pytorch/torchtitan.git` (`main`) -- Pinned in Primus `main` (`third_party/torchtitan`): `5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021` — *Deepseek-V3 toml file minor fix (#1894)*, 2025-10-15 -- Upstream `main` HEAD: `d40df991ac535108e428b0746a08b74a3cf6afc7` — *[GraphTrainer] Skip bucketing pass in precompile and re-enable tests (#3079)* (2026-04-24) -- Commit gap: **upstream is 514 commits ahead of Primus pin** -- Month-to-date movement on Primus side: none (submodule SHA unchanged in April). -- Recommendation: **urgent sync** — the pin is six months stale; upstream has undergone major refactors (GraphTrainer precompile, MoE token dispatcher, FlexAttention CP, FSDP2) that block adopting any new Primus torchtitan-backend features. This week, the shared backend-gap dashboard (#687) publishes the first tracked torchtitan-vs-upstream baseline report, which can be used to drive the sync plan. - -### Notable upstream areas that have moved since the pin - -- **GraphTrainer / precompile**: CooR precompile for DeepSeek V3 (#2916) and `aot_fx_trace` compile mode (#2975); precompile integration tests in CI (#3043); regional-inductor precompile (#2883); `enable_cudagraph` config flag (#3049); FSDP bucketing pass toggled (#3044), re-enabled with SAC improvements (#3060), and most recently skipped again in precompile with tests re-enabled (#3079); CUDA-graph kernel annotation pass (#2926); CPU offload pass for activation memory savings (#3064, reland); SAC pass refactor using `module_fqn` for layer boundaries (#3050). -- **MoE**: token dispatcher introduced replacing token reorderer (#2842); EP setup moved from trainer to config registry (#2960); revert `torch.bmm` → scatter-add (#2775); remove unnecessary MoE padding (#2774). -- **Attention / FlexAttention**: Fused QKV GQAttention (#2878); combine `q_norm`+`k_norm` into `qk_norm` (#2872); FlexAttention bitwise-deterministic tests (#2903, #2989); 2-tier compilation with FlexAttention (#2929); refactor inner attention module (#2761); CP + block_causal + FlexAttention position fix (#2780). -- **FSDP2 & compile**: replace `amp` + `replicate` with `fully_shard` (#2900); lazy import of FSDP mesh helpers for older PyTorch (#2888); SimpleFSDP wrapper shared across same-type modules (#2754); migrate to `.compile()` API (#2688); full DTensor for Qwen3 and Llama4 at TP region (#2149). -- **RL / trainer refactor**: RL trainer and generator refactors (#2985, #3001); rename inference example + consolidate vllm logical elements (#3045); drop `get_model_state_dict` in `push_model_state_dict` (#3066). -- **Datasets / CI / ROCm**: shuffle `HuggingFaceTextDataset` on re-loop and replay on resume (#3023); VLM 8-GPU workflow pins `torchvision` alongside `torch` (#3047); tj-actions version bumps (#3048); MI350 label used for all ROCm workflows (#2740); JIT/AOT tests gated off upstream partitioner regression (#3061). - -### torchtitan upstream feature delta table - -| Area | New Upstream Capability | Evidence (PR/Commit) | Potential Impact to Primus | -| --- | --- | --- | --- | -| GraphTrainer / precompile | CooR precompile for DeepSeek V3;
precompile for `aot_fx_trace` + SAC/FSDP bucketing improvements;
regional-inductor precompile;
`enable_cudagraph` flag;
CUDA-graph kernel annotation pass;
CPU-offload activation pass | pytorch/torchtitan #2916, #2975, #3060, #3079, #2883, #3049, #2926, #3064 | Major perf/UX upgrade for torchtitan-backed training in Primus; currently unavailable behind stale pin. | -| MoE | New token dispatcher replacing token reorderer;
EP setup moved to config registry | pytorch/torchtitan #2842, #2960 | API surface change for torchtitan MoE configs in `primus/backends/torchtitan/**`; patch notes will need an update after sync. | -| Attention | Fused QKV GQAttention;
`qk_norm` consolidation;
FlexAttention bitwise-deterministic tests;
2-tier compilation with FlexAttention;
CP + block_causal + FlexAttention position fix | pytorch/torchtitan #2878, #2872, #2903, #2929, #2780 | Potential perf uplift for Primus torchtitan attention path; determinism tests useful for CI. | -| FSDP2 / compile / TP | `fully_shard` replaces amp+replicate;
`.compile()` migration;
shared SimpleFSDP wrapper;
full DTensor for Qwen3 / Llama4 TP | pytorch/torchtitan #2900, #2688, #2754, #2149 | Breaking public-API adjustments; Primus torchtitan launcher and patches must be re-validated. | -| RL trainer | RL trainer + generator refactors;
drop `get_model_state_dict` in `push_model_state_dict`;
inference-example rename + vllm consolidation | pytorch/torchtitan #2985, #3001, #3066, #3045 | Relevant to any Primus post-training / RL integrations on the torchtitan backend. | -| Datasets / CI / ROCm | Shuffle HF text dataset on re-loop;
`torchvision` pin in VLM workflow;
MI350 label across workflows;
tj-actions version bumps | pytorch/torchtitan #3023, #3047, #2740, #3048 | Good hygiene reference for Primus torchtitan CI and MI-series Docker dependency pinning. | - -## 6. Primus-Turbo Monthly Drift Overview - -- Drift type: **in-repo**, not upstream — compares Turbo version/SHA referenced on Primus `main` now vs the latest commit at or before `month_start_ts = 2026-04-01 00:00 Asia/Shanghai` (`2026-03-31 16:00 UTC`). -- Turbo is **not a submodule** in Primus. Canonical version source: - - `.github/workflows/ci.yaml` → `PRIMUS_TURBO_COMMIT` (also wired through `.github/workflows/docker/Dockerfile`) - - `.github/workflows/benchmark.yaml` → `PRIMUS_TURBO_COMMIT` -- Reference commit at month start on `main`: `76651575` (*[WIP][Megatron-LM] feat: reduce extra qkv transpose in attn (#625)*, 2026-03-31 14:19 GMT+8). The underlying Turbo pins at that commit are the same as the current values. -- Current state on `origin/main`: - - `ci.yaml` `PRIMUS_TURBO_COMMIT`: `333b68d7c81b722b21b4aad10cd250c45f15027c` — *fix sm_scale none bug (#263)* - - `ci.yaml` `PRIMUS_TURBO_AITER_COMMIT`: `e83f9903c07001a0ec29e85d223f6e6cdbe00859` - - `benchmark.yaml` `PRIMUS_TURBO_COMMIT`: `a4488f6cdb15cfff4383c61af7922bb50803f0ea` — *feat: update triton impl for mi300 & mi355 (#252)* -- Month-start state on `main`: all three values identical to current. -- **No Primus-Turbo drift in this comparison window.** -- Recommendation: **monitor**. Note the *pre-existing* skew between the two YAML pins (CI pin `333b68d` is 5 commits ahead of benchmark pin `a4488f6c` in Primus-Turbo history) — tracked separately and unchanged this month. - -### Notable areas changed since month start - -- **No changes this window** — both `ci.yaml` and `benchmark.yaml` Turbo pins on `main` are byte-identical to their 2026-03-30 values. -- **No Turbo drift in this comparison window.** - -### Primus-Turbo monthly drift table - -| Component | Current Version/SHA | Month-start Version/SHA | Delta Summary | Key Changes | Evidence | -| --- | --- | --- | --- | --- | --- | -| `PRIMUS_TURBO_COMMIT` (CI build) | `333b68d7c81b722b21b4aad10cd250c45f15027c` (*fix sm_scale none bug (#263)*) | `333b68d7c81b722b21b4aad10cd250c45f15027c` | No drift (0 commits) | No changes this window. | [`.github/workflows/ci.yaml` L17](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/ci.yaml#L17) | -| `PRIMUS_TURBO_AITER_COMMIT` (CI build) | `e83f9903c07001a0ec29e85d223f6e6cdbe00859` | `e83f9903c07001a0ec29e85d223f6e6cdbe00859` | No drift | No changes this window. | [`.github/workflows/ci.yaml` L18](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/ci.yaml#L18) | -| `PRIMUS_TURBO_COMMIT` (benchmark) | `a4488f6cdb15cfff4383c61af7922bb50803f0ea` (*feat: update triton impl for mi300 & mi355 (#252)*) | `a4488f6cdb15cfff4383c61af7922bb50803f0ea` | No drift | No changes this window. | [`.github/workflows/benchmark.yaml` L9](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/benchmark.yaml#L9) | - -## 7. Source Links - -- Primus main branch: https://github.com/AMD-AGI/Primus/tree/main -- Primus weekly PR listing (window): https://github.com/AMD-AGI/Primus/pulls?q=is%3Apr+is%3Amerged+base%3Amain+merged%3A%3E%3D2026-04-19T16%3A00%3A00Z -- PR #671: https://github.com/AMD-AGI/Primus/pull/671 -- PR #672: https://github.com/AMD-AGI/Primus/pull/672 -- PR #673: https://github.com/AMD-AGI/Primus/pull/673 -- PR #674: https://github.com/AMD-AGI/Primus/pull/674 -- PR #678: https://github.com/AMD-AGI/Primus/pull/678 -- PR #683: https://github.com/AMD-AGI/Primus/pull/683 -- PR #684: https://github.com/AMD-AGI/Primus/pull/684 -- PR #687: https://github.com/AMD-AGI/Primus/pull/687 -- PR #690: https://github.com/AMD-AGI/Primus/pull/690 -- Megatron-LM pin: https://github.com/NVIDIA/Megatron-LM/commit/d3528a21301db2d12e92912b3ec025dc8a2ed4d6 -- Megatron-LM upstream HEAD (at report time): https://github.com/NVIDIA/Megatron-LM/commit/a1165fabcad97eae3778f386839c233dfabf3f8b -- Megatron-LM compare: https://github.com/NVIDIA/Megatron-LM/compare/d3528a21301db2d12e92912b3ec025dc8a2ed4d6...main -- torchtitan pin: https://github.com/pytorch/torchtitan/commit/5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021 -- torchtitan upstream HEAD (at report time): https://github.com/pytorch/torchtitan/commit/d40df991ac535108e428b0746a08b74a3cf6afc7 -- torchtitan compare: https://github.com/pytorch/torchtitan/compare/5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021...main -- Primus-Turbo CI pin: https://github.com/AMD-AGI/Primus-Turbo/commit/333b68d7c81b722b21b4aad10cd250c45f15027c -- Primus-Turbo benchmark pin: https://github.com/AMD-AGI/Primus-Turbo/commit/a4488f6cdb15cfff4383c61af7922bb50803f0ea -- Month-start reference commit on `main`: https://github.com/AMD-AGI/Primus/commit/76651575 - ---- - -*Generated automatically by the Primus weekly report automation. Factual statements are derived from `git log origin/main`, the pinned submodule SHAs in `third_party/`, and the `PRIMUS_TURBO_COMMIT` values in `.github/workflows/{ci,benchmark}.yaml` as observed at 2026-04-24 16:56 GMT+8. Upstream-HEAD SHAs and commit counts are snapshots at report generation time.* diff --git a/docs/weekly_reports/2026-W18-primus-weekly.md b/docs/weekly_reports/2026-W18-primus-weekly.md deleted file mode 100644 index 5277e0874..000000000 --- a/docs/weekly_reports/2026-W18-primus-weekly.md +++ /dev/null @@ -1,138 +0,0 @@ -# Primus Weekly Engineering Report — 2026-W18 - -## 1. Time Window - -- Start: Monday 2026-04-27 00:00:00 Asia/Shanghai (GMT+8) -- End: Friday 2026-05-01 09:10 Asia/Shanghai (GMT+8) (report generation time) -- Branch observed: `origin/main` - -## 2. Executive Summary - -- **2 PRs merged to `main`** in the weekly window (Mon 2026-04-27 00:00 GMT+8 → Fri 2026-05-01 09:10 GMT+8). -- Category breakdown: **Bug Fix: 1**, **Other (feature): 1**; Performance Optimization, Turbo/Dependency Version Update, CI/Infra, Refactor, Docs: 0. -- **No backend pin or Turbo pin changes this week.** `third_party/Megatron-LM` is still pinned at `d3528a21` and `third_party/torchtitan` at `5fb7cc2e`. `PRIMUS_TURBO_COMMIT` is still `333b68d7` in `ci.yaml` and `a4488f6c` in `benchmark.yaml`. The week's only `.github/workflows/ci.yaml` change (in #693) commented out the ROCm Docker Hub login lines and does not move any version pin. -- **Megatron-LM upstream drift: `plan sync` (unchanged).** Pin is `d3528a21` (2026-03-06); upstream `main` HEAD is `3460bba1` (2026-05-01) — **413 commits ahead** (+48 since W17 snapshot `a1165fab`). Notable new upstream activity in this window: AllgatherV inference dispatcher (#4258), permute fusion in hybrid EP (#4089), DeepSeek/MoE prep (per-block MoE routing storage for prefix caching #4301, MTP CUDA graphs #4260), Megatron-FSDP doc unification (#4418), inference graph standardization (#4485) and inference RL graph fix (#4323), heterogeneous TP/DP MIMO `ColocatedBridgeCommunicator` (#4368), checkpoint integrity verification (#4305), `--global-batch-size` removal in step-batch-size release tests (#4545), and a build move of `mamba-ssm`/`causal-conv1d` to optional `[ssm]` extras (#4517). -- **torchtitan upstream drift: `urgent sync` (unchanged).** Pin is `5fb7cc2e` (2025-10-15); upstream `main` HEAD is `70340f4e` (2026-04-30) — **566 commits ahead** (+52 since W17 snapshot `d40df991`). Major new upstream activity in this window: GraphTrainer unified activation-memory framework (#3118), full inductor compilation pass (#3141), async-TP / micro-pipeline TP graph pass (#3129), HybridEP integration with GraphTrainer (#3007, #3177), MoE-series consolidation (`All2AllTokenDispatcher` for EP=1 and EP>1 #3125, ETP deprecation #3167), `ChunkedCELoss` (#2937), Full DTensor config-based sharding for Llama3/Qwen3/Llama4/DSV3/GPT-OSS (#2963, #2969), `MeshDimName → MeshAxisName` rename (#3113), and a CI-side `replace-imports-with-any` pattern to keep CI green when optional packages are missing (#3180). -- **Primus-Turbo month-to-date drift (April → May 1 GMT+8): `monitor` (no action needed).** Both CI and benchmark `PRIMUS_TURBO_COMMIT` pins remain byte-identical to their values at month start (2026-04-01 00:00 GMT+8) and to their previously reported W17 values. -- This week was the **second pass on the upstream Primus-Turbo `TEGroupedMLP` integration on the Megatron backend** (#693): the `te_spec_provider` patch no longer disables PrimusTurbo when `use_turbo_grouped_mlp=True` + `moe_grouped_gemm=True` + TE>=1.9.0; `validate_args_on_rocm` now hard-rejects `use_turbo_grouped_mlp=True` combined with `moe_use_legacy_grouped_gemm=True`; the legacy GroupedMLP/`grouped_gemm_util` code is preserved under `primus/backends/megatron/core/transformer/moe/deprecated_2caa681a1/` for backward compatibility with older configs that still set `moe_use_legacy_grouped_gemm=True`; and `PrimusTurboDeepEPTokenDispatcher` no longer gates `deepep_use_cuda_num_tokens_per_expert` on the legacy GroupedGEMM flag. Several MI300X/MI355X model YAMLs (`deepseek_v2_lite`, `deepseek_v3`, `gpt_oss_20B`, `qwen3_30B_A3B`, `qwen3_235B_A22B`) had their stale `moe_use_legacy_grouped_gemm: true` lines dropped to align with the new validation. -- **New model family: LFM/LFM2** (#651) — Megatron backend gains LiquidAI LFM2 support: `primus/configs/models/megatron/lfm_base.yaml` + `lfm2_8B_A1B.yaml`, a Primus implementation of the LFM2 short-convolution layer (`Lfm2ShortConv`) wired through a new `primus/backends/megatron/patches/gpt_decoder_layer_specs_patches.py`, and three example MI355X pretrain YAMLs (`lfm2_8B_A1B-{BF16,FP8,FP8-te-precision}-pretrain.yaml`). Also includes a small docker-build CI fix (commenting out `rocmshared` Docker Hub logins in the build/push workflow). -- Tooling/infra/docs: no merged tooling, infra-only, refactor, or docs PRs in this window. The shared backend-gap dashboard from W17's #687 is left unmodified. - -## 3. Weekly PR Update Table - -| PR | Merged Time (GMT+8) | Category | Key Update | -| --- | --- | --- | --- | -| [#693](https://github.com/AMD-AGI/Primus/pull/693) `fix: keep legacy groupedgemm on megatron backend` (author: `zhenhuang12`) | 2026-04-28 15:27 | Bug Fix | Second-pass alignment of PrimusTurbo with the upstream `TEGroupedMLP` path on the Megatron backend. Removes the patch-level guard that disabled PrimusTurbo when `use_turbo_grouped_mlp + moe_grouped_gemm + TE>=1.9.0`, and adds a hard `validate_args_on_rocm` check that forbids `use_turbo_grouped_mlp=True` combined with `moe_use_legacy_grouped_gemm=True`. `PrimusTurboDeepEPTokenDispatcher` no longer requires `moe_use_legacy_grouped_gemm=True` to enable `deepep_use_cuda_num_tokens_per_expert`. The legacy GroupedMLP path is preserved under `primus/backends/megatron/core/transformer/moe/deprecated_2caa681a1/` for backward-compat with older configs that still set the legacy flag. Sync-Free MoE stage 2/3 now requires `use_turbo_grouped_mlp=True` (instead of the old legacy flag). Strips the now-stale `moe_use_legacy_grouped_gemm: true` lines from the MI300X/MI355X DeepSeek-V2/V3, GPT-OSS-20B, and Qwen3 30B/235B example YAMLs. | -| [#651](https://github.com/AMD-AGI/Primus/pull/651) `LFM model support` (author: `wenxie-amd`) | 2026-04-28 11:44 | Other | Adds initial Megatron-backend support for LiquidAI's LFM2 model family. Introduces `primus/configs/models/megatron/lfm_base.yaml` + `lfm2_8B_A1B.yaml`, a Primus implementation of the LFM2 short-convolution "attention" layer (`Lfm2ShortConv`) plus an LFM-aware GPT layer-spec builder, and a new Megatron patch (`primus/backends/megatron/patches/gpt_decoder_layer_specs_patches.py`) that routes `get_gpt_decoder_layer_specs` to the Primus implementation when LFM-specific layer types are configured. Ships three MI355X example pretrain configs: `lfm2_8B_A1B-BF16-pretrain.yaml`, `lfm2_8B_A1B-FP8-pretrain.yaml`, `lfm2_8B_A1B-FP8-te-precision.yaml`. Also includes a docker-build CI fix in `.github/workflows/ci.yaml` (comments out four `docker login -u rocmshared ... ROCM_DOCKER_HUB_TOKEN` lines in the image build/push job). | - -## 4. Megatron-LM Drift Overview - -- Upstream: `https://github.com/NVIDIA/Megatron-LM.git` (`main`) -- Pinned in Primus `main` (`third_party/Megatron-LM`): `d3528a21301db2d12e92912b3ec025dc8a2ed4d6` — *fix(moe): fix TE general_gemm API change (#3582)*, 2026-03-06 -- Upstream `main` HEAD: `3460bba1d6f945ec04f47fdb1dcee6da3259fcd8` — *Update copy-pr-bot.yaml [skip ci]* (2026-05-01) -- Last upstream functional change in this window: `83e7466c` — *Fixes for modelopt examples and SFTTokenizer for transformers v5 (#4450)* (2026-04-30) -- Commit gap: **upstream is 413 commits ahead of Primus pin** (+48 since the W17 snapshot `a1165fab`). -- Month-to-date movement on Primus side: pin unchanged in April; last submodule SHA bump on `main` was `3bec9aa9` → `d3528a21` inside PR #654 (merged 2026-04-10). -- Recommendation: **plan sync** (unchanged from W17). The accumulated upstream change set continues to grow (now MoE permute fusion in hybrid EP, AllgatherV inference dispatcher, MTP CUDA graphs, RL inference graph fixes, MFSDP doc unification) without changing Primus's existing sync risk profile. - -### Notable upstream areas that have moved since the pin - -- **MoE / EP**: permute fusion in hybrid EP (#4089) on top of W17's router, FlexDispatcher, and MTP-token-per-expert work; per-block MoE routing storage for prefix caching (#4301); previously reported router score function (#3673), shared-expert overlap incl. FlexDispatcher (#2207), permute-padding fix (#4038), MTP token-per-expert logging (#4412). -- **Inference / CUDA graphs**: AllgatherV inference dispatcher and old-dispatcher simplification (#4258); CUDA graphs for MTP inference (#4260); avoid nsys profile crash with CUDA graphs (#4541); standardize misc graph interface (#4485); fix inference graph override in RL flow (#4323); local-CG bugfixes for latent MoE loss-curve gaps (#4433); embedding/output layer in `full_iteration_inference` graph for hybrid models (#4440). -- **Mamba / Hybrid models**: avoid redundant HBM reloads in `causal_conv1d_update` shift loop (#4460); build move of `mamba-ssm`/`causal-conv1d` to optional `[ssm]` extra (#4517); on top of W17's `MambaModel`/`MambaStack` → `HybridModel`/`HybridStack` rename (#4099, #4159), Mamba inference opt (#4414), QK layernorm in `MambaModel` DPA (#4067), DeepSeek Sparse Attention port (#3553), fine-grained activation offloading (#4173), YARN support for hybrid_model (#4244). -- **Megatron-FSDP / DistOpt / DDP**: documentation unified and refactored (#4418); fix `FusedAdam.use_decoupled_grad` mis-set for Megatron-FSDP (#4427); add `reduce_scatter_with_fp32_accumulation` knob (#4410); on top of W17's MFSDP 0.5.0, MFSDP `decoupled_grad`/DistOpt fixes (#4133), layerwise-optimizer fixes (#4272, #4138), DDP parameter-layout refactor (#3812), MFSDP log gating (#4400). -- **Checkpoint / safety**: checkpoint integrity verification (#4305); SafeUnpickler class for safe pickle usage (#4319); SHA-256 prefix-cache hash replacing polynomial rolling hash (#4158); `weights_only=False` removal (#4434); `validate_access_integrity` knob on dist-ckpt load (#4422); fix checkpoint loading with rerun state machine (#4448); on top of W17's async-ckpt CPU-SHM (#4355) and cross-rank-sync removal (#2864). -- **Heterogeneous training / RL / misc**: `ColocatedBridgeCommunicator` for heterogeneous TP/DP MIMO training (#4368); ModelOpt examples + SFTTokenizer fixes for transformers v5 (#4450); ModelOpt list-format `quant_cfg` fix (#4187); YAML quant recipe in PTQ + first/last layer modifier removal (#4503); `--global-batch-size` removal from step-batch-size schedule release tests (#4545); training-migration container/serialization classes (#4227, #4309); upstream skill-doc updates (#4502, #4542). - -### Megatron-LM upstream feature delta table - -| Area | New Upstream Capability | Evidence (PR/Commit) | Potential Impact to Primus | -| --- | --- | --- | --- | -| MoE / EP | Permute fusion in hybrid EP;
per-block MoE routing storage for prefix caching;
(carries) router score function, FlexDispatcher overlap, MTP token-per-expert logging | NVIDIA/Megatron-LM #4089, #4301, #3673, #2207, #4412 | Primus DeepSeek/Mixtral configs (`examples/megatron/configs/MI300X/deepseek_v*`, `qwen3_*`) may pick up additional EP perf knobs; aligns with this week's Primus-side groupedgemm cleanup in #693. | -| Inference / CUDA graphs | New AllgatherV inference dispatcher;
CUDA graphs for MTP inference;
standardized misc graph interface;
RL inference-graph override fix;
local-CG bugfixes for latent MoE loss curves | NVIDIA/Megatron-LM #4258, #4260, #4485, #4323, #4433, #4541 | Inference/post-train paths for Primus DSV3/MoE configs benefit; expand validation when sync lands. | -| Mamba / Hybrid | `causal_conv1d_update` HBM-reload reduction;
`mamba-ssm`/`causal-conv1d` move to optional `[ssm]` extra;
(carries) outside-core `MambaModel` → `HybridModel` rename | NVIDIA/Megatron-LM #4460, #4517, #4159, #4244 | The `[ssm]` extra split affects Primus Dockerfile install layering; Primus must audit `MambaModel`/`MambaStack` references in `primus/backends/megatron` before the next bump. | -| FSDP / DistOpt / DDP | MFSDP doc unification;
FusedAdam `use_decoupled_grad` Megatron-FSDP fix;
`reduce_scatter_with_fp32_accumulation` knob;
(carries) MFSDP 0.5.0 + layout refactor | NVIDIA/Megatron-LM #4418, #4427, #4410, #3812, #4400 | Primus FSDP/DDP launch paths (`primus/modules/trainer/megatron/*`) should re-validate post-sync; this week's Primus-side validation (#693) makes the Turbo grouped-MLP path the new default for MoE, which interacts with these MFSDP changes. | -| Checkpoint / safety | Checkpoint integrity verification;
SafeUnpickler;
SHA-256 prefix-cache hash;
`weights_only=False` removal;
`validate_access_integrity` knob | NVIDIA/Megatron-LM #4305, #4319, #4158, #4434, #4422, #4448 | Tighter checkpoint hardening; Primus pretrain at scale should benefit; coordinate when bumping the pin. | -| Heterogeneous / RL / misc | `ColocatedBridgeCommunicator` (NMFW-17);
ModelOpt fixes for transformers v5;
YAML quant recipe in PTQ;
step-batch-size release-test fix | NVIDIA/Megatron-LM #4368, #4450, #4187, #4503, #4545 | New heterogeneous-training entry point may inform future Primus multi-pod configs; ModelOpt fixes are required when Primus moves to transformers v5. | -| Schedule plan API (carry-over) | `post_attn` node already removed from `TransformerLayerSchedulePlan` (consumed in Primus by W17 #672) | Primus/#672, upstream schedule-plan change | Primus-side fix already shipped; treat as confirmed drift that future bumps must keep in sync. | - -## 5. torchtitan Drift Overview - -- Upstream: `https://github.com/pytorch/torchtitan.git` (`main`) -- Pinned in Primus `main` (`third_party/torchtitan`): `5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021` — *Deepseek-V3 toml file minor fix (#1894)*, 2025-10-15 -- Upstream `main` HEAD: `70340f4ec31d9e1dbd448506cc4423934c3cd62f` — *[CI] Use replace-imports-with-any to avoid missing packages causing CI failures (#3180)* (2026-04-30) -- Commit gap: **upstream is 566 commits ahead of Primus pin** (+52 since the W17 snapshot `d40df991`). -- Month-to-date movement on Primus side: none (submodule SHA unchanged in April). -- Recommendation: **urgent sync** (unchanged from W17). The pin is now ~6.5 months stale; another major refactor wave landed upstream this week (GraphTrainer activation-memory framework, full inductor compilation pass, async-TP graph pass, MoE token-dispatcher consolidation, ETP deprecation, ChunkedCELoss, Full DTensor config-based sharding) that further widens the gap from the existing baseline report. - -### Notable upstream areas that have moved since the pin - -- **GraphTrainer / precompile**: unified framework for activation memory management (#3118); full inductor compilation pass (#3141); joint graph bucketing + prefetching that composes with SAC (#3056); async-TP / micro-pipeline TP graph pass (#3129); start deprecating jit/aot compile modes in graph_trainer (#2788); `apply_graph_ac` removed (#3147); `log_activation_memory_policy` for per-tensor inspection (#3062); on top of W17's CooR precompile for DSV3 (#2916), `aot_fx_trace` (#2975), regional-inductor precompile (#2883), `enable_cudagraph` (#3049), CUDA-graph annotation pass (#2926), CPU-offload activation pass (#3064), and SAC pass refactor (#3050). -- **HybridEP / MoE**: HybridEP enabled with GraphTrainer (#3007); HybridEP reads `comm_backend` from model config (#3177); `[MoE][3/n]` consolidate EP=1 and EP>1 to all use `All2AllTokenDispatcher` (#3125); `[MoE][4/n]` deprecate Expert Tensor Parallel (ETP) (#3167); on top of W17's token-dispatcher introduction (#2842) and EP-config-registry move (#2960). -- **Module / DTensor / TP**: Full DTensor config-based sharding infrastructure with Llama3 adoption (#2963) and follow-up for Qwen3, Llama4, DSV3, GPT-OSS (#2969); `MeshDimName → MeshAxisName` rename (#3113); remove `LocalMapInnerAttention`, use static `LocalMapSpec` (#2986); on top of W17's `fully_shard` migration (#2900), `.compile()` migration (#2688), and SimpleFSDP wrapper sharing (#2754). -- **Attention / loss**: `ChunkedCELoss` (#2937) plus disable chunked CE in graph trainer (#3115); revert "Remove MATH from ScaledDotProductAttention default backends" (#3135) after the original change (#3080); fused QKV support in Qwen3-VL state-dict adapter (#3102); on top of W17's Fused QKV GQAttention (#2878), `qk_norm` consolidation (#2872), FlexAttention bitwise-deterministic tests (#2903, #2989), 2-tier compilation with FlexAttention (#2929), CP+block_causal+FlexAttention position fix (#2780). -- **RL / inference / experiments**: RL env-rollout-based controller refactor (#3073); patched-Qwen3 parallel plan removed and merged into core torchtitan Qwen3 parallel plan (#3070); reset_prefix_cache (#3095); deprecate VLM experiment (#3151); remove stale autoparallel/deepseek_v3 experiment (#2271); on top of W17's RL trainer/generator refactors (#2985, #3001) and inference-example/vllm consolidation (#3045). -- **Datasets / CI / ROCm**: CI uses `replace-imports-with-any` to avoid missing-package failures (#3180); shuffle ChatDataset before splitting across nodes (#3131); reproducible training resume across epoch boundaries for map and streaming datasets (#3008); MoE loss comparison guard added to CI (#3081); ROCm experimental workflows toggled off again (#3140) after a brief revert (#3097); MI350 loss numbers updated (#3078); SAC test compatibility with PyTorch indexed storage (#3098); on top of W17's HF text-dataset reshuffle (#3023), VLM `torchvision` pin (#3047), MI350 label rollout (#2740), and tj-actions bumps (#3048). - -### torchtitan upstream feature delta table - -| Area | New Upstream Capability | Evidence (PR/Commit) | Potential Impact to Primus | -| --- | --- | --- | --- | -| GraphTrainer / precompile | Unified activation-memory framework;
full inductor compilation pass;
joint graph bucketing + prefetching that composes with SAC;
async-TP / micro-pipeline TP graph pass;
jit/aot compile-mode deprecation;
activation-memory policy logging | pytorch/torchtitan #3118, #3141, #3056, #3129, #2788, #3062 | Major continuing perf/UX upgrade; remains unavailable behind the stale pin. Will require coordinated patches in `primus/backends/torchtitan/**` when Primus syncs. | -| HybridEP / MoE | HybridEP integration with GraphTrainer;
HybridEP `comm_backend` from model config;
`All2AllTokenDispatcher` consolidation across EP=1 and EP>1;
ETP deprecation | pytorch/torchtitan #3007, #3177, #3125, #3167 | Direct impact on Primus torchtitan MoE configs and any planned EP topology in `primus/modules/trainer/torchtitan/*`; ETP removal is a breaking config-level change. | -| Module / DTensor / TP | Full DTensor config-based sharding for Llama3/Qwen3/Llama4/DSV3/GPT-OSS;
`MeshDimName → MeshAxisName` rename;
`LocalMapInnerAttention` removed in favor of static `LocalMapSpec` | pytorch/torchtitan #2963, #2969, #3113, #2986 | Public-API rename + refactor; Primus torchtitan launcher and patches must be re-validated. | -| Attention / loss | `ChunkedCELoss`;
graph-trainer chunked-CE gating;
SDP-default-backend revert (re-include MATH);
fused QKV in Qwen3-VL adapter | pytorch/torchtitan #2937, #3115, #3135, #3102 | Loss-side knob useful for memory-bound training; SDP backend revert reduces breakage risk for Primus tests when sync lands. | -| RL / experiments | RL env-rollout-based controller refactor;
upstream Qwen3 parallel plan absorbs patched RL plan;
VLM and stale autoparallel/dsv3 experiments deprecated/removed | pytorch/torchtitan #3073, #3070, #3151, #2271 | Relevant for Primus post-training/RL on torchtitan; may unblock removing internal RL patches after sync. | -| Datasets / CI / ROCm | `replace-imports-with-any` CI pattern;
reproducible epoch-boundary resume for map/streaming datasets;
ChatDataset cross-node shuffle;
MoE loss comparison CI guard;
MI350 loss-number refresh | pytorch/torchtitan #3180, #3008, #3131, #3081, #3078 | Useful CI/MI350 hygiene reference for Primus torchtitan CI; the loss-comparison guard pattern can be mirrored in Primus torchtitan UTs. | - -## 6. Primus-Turbo Monthly Drift Overview - -- Drift type: **in-repo**, not upstream — compares Turbo version/SHA referenced on Primus `main` now vs the latest commit at or before `month_start_ts = 2026-04-01 00:00 Asia/Shanghai` (`2026-03-31 16:00 UTC`). -- Turbo is **not a submodule** in Primus. Canonical version source: - - `.github/workflows/ci.yaml` → `PRIMUS_TURBO_COMMIT` (also wired through `.github/workflows/docker/Dockerfile`) - - `.github/workflows/benchmark.yaml` → `PRIMUS_TURBO_COMMIT` -- Reference commit at month start on `main`: `76651575` (*[WIP][Megatron-LM] feat: reduce extra qkv transpose in attn (#625)*, 2026-03-31 14:19 GMT+8). The underlying Turbo pins at that commit are byte-identical to today's values. -- Current state on `origin/main`: - - `ci.yaml` `PRIMUS_TURBO_COMMIT`: `333b68d7c81b722b21b4aad10cd250c45f15027c` — *fix sm_scale none bug (#263)* - - `ci.yaml` `PRIMUS_TURBO_AITER_COMMIT`: `e83f9903c07001a0ec29e85d223f6e6cdbe00859` - - `benchmark.yaml` `PRIMUS_TURBO_COMMIT`: `a4488f6cdb15cfff4383c61af7922bb50803f0ea` — *feat: update triton impl for mi300 & mi355 (#252)* -- Month-start state on `main`: all three values identical to current. -- **No Primus-Turbo drift in this comparison window.** -- Recommendation: **monitor**. The pre-existing skew between the two YAML pins (CI pin `333b68d` is 5 commits ahead of benchmark pin `a4488f6c` in Primus-Turbo history) is unchanged this month. This week's Primus-side change in #693 modifies how Primus *uses* Turbo's grouped-MLP path on the Megatron backend but does not bump any Turbo pin. - -### Notable areas changed since month start - -- **No changes this window** — both `ci.yaml` and `benchmark.yaml` Turbo pins on `main` are byte-identical to their 2026-03-30 values. -- **Primus-side Turbo integration moved**: PR #693 (this window) drops the legacy GroupedGEMM gating in `_is_primus_turbo_enabled` and `PrimusTurboDeepEPTokenDispatcher` so that Turbo's `TEGroupedMLP` path is now the default, but no `PRIMUS_TURBO_COMMIT` was bumped. -- **AITER pin unchanged**: `PRIMUS_TURBO_AITER_COMMIT` is identical to the month-start value. -- **Benchmark pin unchanged**: `benchmark.yaml` `PRIMUS_TURBO_COMMIT` is identical to the month-start value. -- **No Turbo drift in this comparison window.** - -### Primus-Turbo monthly drift table - -| Component | Current Version/SHA | Month-start Version/SHA | Delta Summary | Key Changes | Evidence | -| --- | --- | --- | --- | --- | --- | -| `PRIMUS_TURBO_COMMIT` (CI build) | `333b68d7c81b722b21b4aad10cd250c45f15027c` (*fix sm_scale none bug (#263)*) | `333b68d7c81b722b21b4aad10cd250c45f15027c` | No drift (0 commits) | No changes this window. Primus-side use of Turbo grouped-MLP changed in #693 without bumping the pin. | [`.github/workflows/ci.yaml` L17](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/ci.yaml#L17) | -| `PRIMUS_TURBO_AITER_COMMIT` (CI build) | `e83f9903c07001a0ec29e85d223f6e6cdbe00859` | `e83f9903c07001a0ec29e85d223f6e6cdbe00859` | No drift | No changes this window. | [`.github/workflows/ci.yaml` L18](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/ci.yaml#L18) | -| `PRIMUS_TURBO_COMMIT` (benchmark) | `a4488f6cdb15cfff4383c61af7922bb50803f0ea` (*feat: update triton impl for mi300 & mi355 (#252)*) | `a4488f6cdb15cfff4383c61af7922bb50803f0ea` | No drift | No changes this window. | [`.github/workflows/benchmark.yaml` L9](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/benchmark.yaml#L9) | - -## 7. Source Links - -- Primus main branch: https://github.com/AMD-AGI/Primus/tree/main -- Primus weekly PR listing (window): https://github.com/AMD-AGI/Primus/pulls?q=is%3Apr+is%3Amerged+base%3Amain+merged%3A%3E%3D2026-04-26T16%3A00%3A00Z -- PR #651: https://github.com/AMD-AGI/Primus/pull/651 -- PR #693: https://github.com/AMD-AGI/Primus/pull/693 -- Megatron-LM pin: https://github.com/NVIDIA/Megatron-LM/commit/d3528a21301db2d12e92912b3ec025dc8a2ed4d6 -- Megatron-LM upstream HEAD (at report time): https://github.com/NVIDIA/Megatron-LM/commit/3460bba1d6f945ec04f47fdb1dcee6da3259fcd8 -- Megatron-LM compare: https://github.com/NVIDIA/Megatron-LM/compare/d3528a21301db2d12e92912b3ec025dc8a2ed4d6...main -- torchtitan pin: https://github.com/pytorch/torchtitan/commit/5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021 -- torchtitan upstream HEAD (at report time): https://github.com/pytorch/torchtitan/commit/70340f4ec31d9e1dbd448506cc4423934c3cd62f -- torchtitan compare: https://github.com/pytorch/torchtitan/compare/5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021...main -- Primus-Turbo CI pin: https://github.com/AMD-AGI/Primus-Turbo/commit/333b68d7c81b722b21b4aad10cd250c45f15027c -- Primus-Turbo benchmark pin: https://github.com/AMD-AGI/Primus-Turbo/commit/a4488f6cdb15cfff4383c61af7922bb50803f0ea -- Month-start reference commit on `main`: https://github.com/AMD-AGI/Primus/commit/76651575 -- Last week's report (W17): https://github.com/AMD-AGI/Primus/blob/main/docs/weekly_reports/2026-W17-primus-weekly.md - ---- - -*Generated automatically by the Primus weekly report automation. Factual statements are derived from `git log origin/main`, the pinned submodule SHAs in `third_party/`, and the `PRIMUS_TURBO_COMMIT` values in `.github/workflows/{ci,benchmark}.yaml` as observed at 2026-05-01 09:10 GMT+8. Upstream-HEAD SHAs and commit counts are snapshots at report generation time.* diff --git a/docs/weekly_reports/2026-W19-primus-weekly.md b/docs/weekly_reports/2026-W19-primus-weekly.md deleted file mode 100644 index ce44f7996..000000000 --- a/docs/weekly_reports/2026-W19-primus-weekly.md +++ /dev/null @@ -1,151 +0,0 @@ -# Primus Weekly Engineering Report — 2026-W19 - -## 1. Time Window - -- Start: Monday 2026-05-04 00:00:00 Asia/Shanghai (GMT+8) -- End: Friday 2026-05-08 09:01 Asia/Shanghai (GMT+8) (report generation time) -- Branch observed: `origin/main` - -## 2. Executive Summary - -- **3 PRs merged to `main`** in the weekly window (Mon 2026-05-04 00:00 GMT+8 → Fri 2026-05-08 09:01 GMT+8). -- Category breakdown: **Bug Fix: 2**, **Turbo/Dependency Version Update: 1**; Performance Optimization, CI/Infra, Refactor, Docs, Other: 0. -- **Backend dependency pin change this week (Primus-Turbo + Triton).** PR #694 bumped `PRIMUS_TURBO_COMMIT` from `333b68d7` (CI) / `a4488f6c` (benchmark) to a single shared SHA `ef5b58ea3de0a2956d57dba518be466b7a092442` ([Primus-Turbo #320](https://github.com/AMD-AGI/Primus-Turbo/pull/320), *feat(grouped_gemm): switch default backend to Triton for BF16 and FP8*, 2026-04-29), bumped `PRIMUS_TURBO_AITER_COMMIT` from `e83f9903` to `857f4d1577`, and introduced a new `TRITON_COMMIT=88b227e23f0445f3f695bad05bbf1a363b4f50e0` build-arg that compiles Triton from `release/3.7.x` source inside the docker image (wired through both the main and JAX docker builds). The CI runner now also exports `PRIMUS_TURBO_ATTN_V3_ATOMIC_FP32=1`. The same PR adds a Primus-Turbo Attention adapter for torchtitan Qwen3 (new `primus/backends/torchtitan/models/qwen3/model/model.py`, plus `patch_turbo_attention` extending the Qwen3 path). -- **Submodule SHAs unchanged.** `third_party/Megatron-LM` is still pinned at `d3528a21` and `third_party/torchtitan` at `5fb7cc2e` on `origin/main`. -- **Megatron-LM upstream drift: `plan sync` (unchanged).** Pin is `d3528a21` (2026-03-06); upstream `main` HEAD is `7cdf652c` (2026-05-07) — **458 commits ahead** (+45 since the W18 snapshot `3460bba1`). Notable new upstream activity in this window: removal of the legacy `transformer`/`modules` and legacy GPT code (#4207, #4322), inference cache of input/position ID views (#4634), inference vLLM grouped-gemm kernel tuning + shared-expert overlap in latent MoE (#4603), checkpoint conversion between `GPT_model` and `Hybrid_model` (#4482), `nvidia_resiliency_ext` fault-injection support (#4370), InJob-restart-on-failure support (#4594), partial-cudagraphs+HybridEP DDP-hook fix (#4500), gradient corruption fix for layerwise param all-gather overlap (#4609), Flextron code (#4429), FlashInfer sampling (#2456), and a vLLM grouped-gemm MoE inference backend (#4566). -- **torchtitan upstream drift: `urgent sync` (unchanged).** Pin is `5fb7cc2e` (2025-10-15); upstream `main` HEAD is `4ebb0895` (2026-05-07) — **610 commits ahead** (+44 since the W18 snapshot `70340f4e`). Major new upstream activity in this window: GraphTrainer memory-policy registry + extra-graph-passes hook (#3215), graph-pass timing/log of names + total time (#3261), HybridEP-with-GraphTrainer integration tests (#3184), regional Inductor RMSNorm fusion (#3132), bucketing pass enabled in precompile path (re-landed #3213/#3107), CPU-offloading prefetch pass (#3166), `[graph_trainer] FSDP AG RS overlap` (#3156), MXFP8 GroupedExperts swap fix (#3199), float8 quantization of GroupedExperts during conversion (#3233), `AllToAllTokenDispatcher` token-count `sp_size` padding (#3193), CP `AllGather` wrong-dimension fix (#3206), llama4 `fsdp_mesh_info` bug fix (#3231), Qwen3 MoE bitwise-deterministic tests + weight-tying gradient fix (#3174), structured-logging observability (#3176), CI parallel integration tests (#3144), and a 50s→15s vLLM compile-time win (#3145). -- **Primus-Turbo monthly drift: pin advanced — recommendation is now `monitor (new pin landed)`.** Both CI and benchmark `PRIMUS_TURBO_COMMIT` are now `ef5b58ea` (2026-04-29), which is **32 commits ahead** of the month-start CI pin `333b68d7` (2026-03-27) and **37 commits ahead** of the month-start benchmark pin `a4488f6c` (2026-03-19). The headline upstream changes between month-start and the new pin are: switch grouped-gemm default backend to Triton for BF16 and FP8 (#320), `[Attention] Torch.compile custom wrappers` (#310), MoE EPBackend Protocol + EPBufferConfig refactor (#297), `attn add bhsd layout` (#304), Symmetric-Memory rewrite (#276), `feat(moe): add back deepep_use_comm_stream` (#314), and several MXFP8/FP8 quantization fixes (#306, #307, #308). The CI `PRIMUS_TURBO_AITER_COMMIT` was also bumped (`e83f9903` → `857f4d15`), and a new `TRITON_COMMIT=88b227e` build-arg compiles Triton from source. -- **Megatron-side Primus interface debt closed (#675):** custom HF tokenizer types now route through Megatron's official `build_tokenizer` first with a fallback that injects `unique_identifiers`; data path args are normalized for `str`/`list`/`tuple` using upstream-compatible semantics; legacy `core_gpt_dataset_config_from_args` is realigned with upstream `pretrain_gpt` via `get_blend_and_blend_per_split`; missing upgraded fields (`object_storage_cache_path`, `per_dataset_sequences_path`, `dataloader_fast_cache_load`, etc.) are wired to prevent silent mock-fallback drift. -- **Preflight reliability fix (#668):** the preflight IP-address probe replaces a substring `in` check with a precise regex match in `primus/tools/preflight/network/network_probe.py`, eliminating a class of false-positives where a target IP is a substring of another active IP. -- **No backend-gap report regenerated this week.** No submodule SHA changed (`third_party/Megatron-LM`, `third_party/torchtitan`, `third_party/Megatron-Bridge`, `third_party/Emerging-Optimizers`, `third_party/HummingbirdXT`, `third_party/maxtext` are all unchanged). The Primus-Turbo pin bump is a tracked-config change but Primus-Turbo is not currently surfaced as a separate backend under `docs/backend-gap/`, so the existing torchtitan baseline report (`5fb7cc2e` vs upstream `main`) is unaffected. - -## 3. Weekly PR Update Table - -| PR | Merged Time (GMT+8) | Category | Key Update | -| --- | --- | --- | --- | -| [#694](https://github.com/AMD-AGI/Primus/pull/694) `Build Triton from source and bump Primus-Turbo` (author: `kyle-256`) | 2026-05-07 17:19 | Turbo/Dependency Version Update | Bumps `PRIMUS_TURBO_COMMIT` to a single shared SHA `ef5b58ea3de0a2956d57dba518be466b7a092442` in both `.github/workflows/ci.yaml` and `.github/workflows/benchmark.yaml` (CI was at `333b68d7`, benchmark was at `a4488f6c`). Also bumps `PRIMUS_TURBO_AITER_COMMIT` (`e83f9903` → `857f4d1577`). Introduces a new `TRITON_COMMIT=88b227e23f0445f3f695bad05bbf1a363b4f50e0` env, wired through both the main and JAX docker builds, plus Dockerfile steps that clone `triton-lang/triton@release/3.7.x`, check out the pinned commit, and `pip install --no-build-isolation -v .`. Adds `PRIMUS_TURBO_ATTN_V3_ATOMIC_FP32=1` to the torch CI runner job env. Adds Primus-Turbo Attention support for torchtitan Qwen3 by introducing `primus/backends/torchtitan/models/qwen3/model/model.py` (subclasses upstream `Attention` and routes through `inner_attention` with FlexAttention/non-FlexAttention branches) and extending `patch_turbo_attention` to swap `torchtitan.models.qwen3.model.model.Attention` to the Primus version. Also drops the recursive `submodules: "recursive"` from the Primus-Turbo checkout step in favor of an explicit `git submodule sync --recursive && git submodule update --init --recursive` after cleaning `3rdparty/composable_kernel` to avoid stale submodule state. | -| [#668](https://github.com/AMD-AGI/Primus/pull/668) `Fix/preflight: fix IP address matching` (author: `alexsu52`) | 2026-05-07 17:26 | Bug Fix | Replaces the substring `in` check used to validate a target IP address against the host's active IPs with a precise regex match (`re.search(r"\b\b", ...)`) in `primus/tools/preflight/network/network_probe.py`. Eliminates a class of false-positives where a target IP is a substring of another active IP on the same machine (e.g. `10.0.0.1` falsely matching `10.0.0.10`). Single-file, 2-insertion change. | -| [#675](https://github.com/AMD-AGI/Primus/pull/675) `fix(megatron): align tokenizer and dataset path interfaces with upgraded Megatron` (author: `WangLingxun`) | 2026-05-06 14:00 | Bug Fix | Aligns Primus Megatron-backend behavior with upstream Megatron after the recent upgrade. Routes custom HF tokenizer types through Megatron's official `build_tokenizer` first; on failure, falls back to a Primus path and injects `unique_identifiers` when the constructed tokenizer is missing it (fixes `'HuggingFaceTokenizer' object has no attribute 'unique_identifiers'` during dataset construction). Normalizes `--data-path` style args for `str`, `list`, and `tuple` inputs using upstream-compatible semantics, eliminating `.split()` crashes on list input. Realigns the legacy `core_gpt_dataset_config_from_args` path with upstream `pretrain_gpt` via `get_blend_and_blend_per_split`. Wires missing upgraded fields (`object_storage_cache_path`, `per_dataset_sequences_path`, `dataloader_fast_cache_load`, etc.) into the trainer-side `GPTDatasetConfig` build to prevent real-data config drift and unintended mock-dataset fallback. Touches `primus/backends/megatron/patches/args/data_path_split_patches.py`, `primus/backends/megatron/training/tokenizer/tokenizer.py`, and `primus/modules/trainer/megatron/trainer.py`. | - -## 4. Megatron-LM Drift Overview - -- Upstream: `https://github.com/NVIDIA/Megatron-LM.git` (`main`) -- Pinned in Primus `main` (`third_party/Megatron-LM`): `d3528a21301db2d12e92912b3ec025dc8a2ed4d6` — *fix(moe): fix TE general_gemm API change (#3582)*, 2026-03-06 -- Upstream `main` HEAD: `7cdf652c16a0283b42ee13b7700481d0cfe2f291` — *ci: Update Gitlab base image to 26.04 pytorch (#4688)* (2026-05-07) -- Last upstream functional change in this window: `1df264c4` — *Inference: Cache input + position ID views (#4634)* (2026-05-07) -- Commit gap: **upstream is 458 commits ahead of Primus pin** (+45 since the W18 snapshot `3460bba1`). -- Submodule SHA on Primus side: unchanged in W19; last submodule SHA bump on `main` was `3bec9aa9` → `d3528a21` inside PR #654 (merged 2026-04-10). -- Recommendation: **plan sync** (unchanged from W17/W18). Upstream continues to land large structural removals (legacy `transformer`/`modules`, legacy GPT code) and inference perf/correctness work, growing the eventual sync diff but not changing Primus's overall sync risk profile this week. - -### Notable upstream areas that have moved since the pin - -- **Legacy code removal**: legacy `transformer`/`modules` removed (#4207); legacy GPT code removed (#4322); these are large structural deletes that will dominate the eventual Primus sync diff. -- **Inference**: cache input + position ID views (#4634); propagate errors for failed inference requests (#4679); fix crash with evicted requests + tpot (#4645); FlashInfer sampling (#2456); make `last_token_logits` graphable (#4552); shared-expert overlap with `allgatherv` in inference (#4570); MoE dispatcher buffer-size fix from actual tensor sizes (#4576); inference EP-sync re-enable + simplify (#4587); `Move inference context bookkeeping to CPU with ContextGPUView` (#4306); on top of W18's AllgatherV inference dispatcher (#4258), MTP CUDA graphs (#4260) and inference graph standardization (#4485). -- **MoE / EP**: Tune vLLM grouped gemm + `moe_sum` + shared-expert overlap in latent MoEs (#4603); Add vLLM grouped gemm backend for MoE inference (#4566); fix partial cudagraphs + HybridEP DDP-hook trigger (#4500); fix EP sync regression (#4607); chunked MLP during training (#3656); on top of W18's permute fusion in hybrid EP (#4089) and per-block MoE routing storage for prefix caching (#4301). -- **Resiliency / training stability**: `nvidia_resiliency_ext` fault-injection (#4370); InJob restart on failures (#4594); fix gradient corruption with layerwise param all-gather overlap (#4609); fix Hang in tests (#4575); KD teacher loading moved after `Float16Module` (#4394); guard vocab `reduce_scatter` on TP > 1 (#4565); `Remove invalid timeout argument for dist.barrier` (#4512). -- **Hybrid / Mamba / SSM**: Checkpoint conversion between `GPT_model` and `Hybrid_model` (#4482); Handle SSM sharded tensor merge OOM with CPU fallback (#4442); fix `mtp_use_repeated_layer` behavior for GPT models (#3965); on top of W18's `causal_conv1d_update` HBM-reload reduction (#4460), `mamba-ssm`/`causal-conv1d` move to optional `[ssm]` extra (#4517). -- **Tokenizer / data / experiment**: convert tokenizer args to config (#4406); `Finalize all builders in preprocess_data, not just the last key` (#4573); Named validation sets (#4578); Adding code for Flextron (#4429); refit buffer fix (#4580); skill-doc refactor (#4574). - -### Megatron-LM upstream feature delta table - -| Area | New Upstream Capability | Evidence (PR/Commit) | Potential Impact to Primus | -| --- | --- | --- | --- | -| Legacy code removal | Legacy `transformer`/`modules` removed;
legacy GPT code removed | NVIDIA/Megatron-LM #4207, #4322 | Large structural deletes that will dominate the eventual Primus sync diff; Primus patches under `primus/backends/megatron/**` that touch the legacy paths must be audited before the next pin bump. | -| Inference / CUDA graphs | Cache input + position ID views;
propagate errors for failed inference requests;
FlashInfer sampling;
`Move inference context bookkeeping to CPU with ContextGPUView`;
(carries) AllgatherV inference dispatcher, MTP CUDA graphs | NVIDIA/Megatron-LM #4634, #4679, #2456, #4306, #4258, #4260 | Inference/post-train paths for Primus DSV3/MoE configs benefit; expand validation when sync lands. | -| MoE / EP | Tune vLLM grouped gemm + `moe_sum` + shared-expert overlap in latent MoEs;
Add vLLM grouped gemm MoE-inference backend;
partial-cudagraphs + HybridEP DDP-hook fix;
EP-sync regression fix;
chunked MLP during training | NVIDIA/Megatron-LM #4603, #4566, #4500, #4607, #3656 | Aligns with this week's Primus-side Turbo bump (#694) which already moves grouped-gemm default to Triton in the Turbo path; once Primus syncs, Megatron's vLLM grouped-gemm MoE backend becomes available for Primus DSV3/MoE inference experiments. | -| Resiliency / stability | `nvidia_resiliency_ext` fault injection;
InJob restart on failures;
gradient-corruption fix for layerwise param AG overlap;
Hang-in-tests fix | NVIDIA/Megatron-LM #4370, #4594, #4609, #4575 | Useful for Primus pretrain-at-scale on MI300X/MI355X clusters; coordinate with Primus preflight/launcher when bumping the pin. | -| Hybrid / Mamba / SSM | Checkpoint conversion between `GPT_model` and `Hybrid_model`;
SSM sharded-tensor merge OOM CPU fallback;
(carries) `MambaModel`/`MambaStack` → `HybridModel`/`HybridStack` rename | NVIDIA/Megatron-LM #4482, #4442, #4159 | The `[ssm]` extra split + Hybrid rename remains a known sync risk for Primus Mamba/Hybrid configs; Primus must audit `MambaModel`/`MambaStack` references before the next bump. | -| Tokenizer / data / experiment | Convert tokenizer args to config;
`Finalize all builders in preprocess_data`;
Named validation sets;
Flextron code | NVIDIA/Megatron-LM #4406, #4573, #4578, #4429 | This week's Primus tokenizer/data alignment (#675) deliberately matches upstream `pretrain_gpt`; the upstream tokenizer-args→config refactor is the next candidate Primus must adopt at sync time. | -| Schedule plan API (carry-over) | `post_attn` node already removed from `TransformerLayerSchedulePlan` (consumed in Primus by W17 #672) | Primus/#672, upstream schedule-plan change | Primus-side fix already shipped; treat as confirmed drift that future bumps must keep in sync. | - -## 5. torchtitan Drift Overview - -- Upstream: `https://github.com/pytorch/torchtitan.git` (`main`) -- Pinned in Primus `main` (`third_party/torchtitan`): `5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021` — *Deepseek-V3 toml file minor fix (#1894)*, 2025-10-15 -- Upstream `main` HEAD: `4ebb0895a18859ad0f0642901dba0cc888d82b97` — *Tests HybridEP integration with GraphTrainer (#3184)* (2026-05-07) -- Commit gap: **upstream is 610 commits ahead of Primus pin** (+44 since the W18 snapshot `70340f4e`). -- Submodule SHA on Primus side: unchanged in W19. PR #694 in this window adds a Primus-Turbo Qwen3 attention adapter on the **Primus outer adapter** layer (`primus/backends/torchtitan/models/qwen3/`) but does not bump the upstream pin. -- Recommendation: **urgent sync** (unchanged from W17/W18). The pin is now ~6.5 months stale; another wave of GraphTrainer / precompile / float8 / MoE landings this week further widens the gap. - -### Notable upstream areas that have moved since the pin - -- **GraphTrainer / precompile**: memory-policy registry + extra graph-passes hook (#3215); log graph-pass names + total time (#3261); regional Inductor RMSNorm fusion (#3132); bucketing pass enabled in precompile path (re-landed via #3213, originally #3107); CPU-offloading prefetch pass (#3166); FSDP AG RS overlap (#3156); FlexAttention precompile bitwise-deterministic tests re-landed via #3214 (originally #3178); `Port remat_using_tags_for_fwd_loss_bwd_graph pass locally` (#3260); `Skip identity-slice rewrite when start/end/step are dynamic Nodes` (#3195); annotate generated FX with user source lines (#3194); annotate loss region with `module_fqn` (#3207); on top of W18's GraphTrainer activation-memory framework (#3118), full inductor compilation pass (#3141), async-TP graph pass (#3129). -- **Compilation / vLLM**: improve compile time (~50s → ~15s for vLLM) (#3145); `Refactor pipeline parallel helpers for graph PP reuse` (#2724); `Re-land "Enable bucketing pass in precompile path"` (#3213). -- **MoE / float8 / mxfp8**: `[float8] Quantize GroupedExperts params during conversion` (#3233); `[mxfp8] Fix MXFP8GroupedExpertsConverter to actually swap GroupedExperts params` (#3199); `[MoE] Pad token count to a multiple of sp_size in AllToAllTokenDispatcher` (#3193); HybridEP-with-GraphTrainer integration tests (#3184); on top of W18's `All2AllTokenDispatcher` consolidation (#3125) and ETP deprecation (#3167). -- **Attention / Qwen3 / Llama4**: `[GraphTrainer] Add Qwen3 MoE bitwise deterministic tests and fix weight-tying gradient bug` (#3174); `[Qwen3VL] remove local_map for dtensor interpolation` (#2957); `[torchtitan][llama4_parallelize] fixing fsdp_mesh_info bug` (#3231); `refactor get_attention_masks to use positions instead of eos_id` (#3149); CP `AllGather on the wrong dimension` (#3206); `Wrong token count in validation when CP>1` (#3257). -- **Observability / RL / CI**: structured logging + training instrumentation (#3176); `[CI] Run integration tests in parallel` (#3144); `ci: regenerate qwen3_moe_rocm_mi350x.txt baseline with actual MI350 losses` (#3196); `[GraphTrainer] Disable cpu_offload_all CI test for upstream cuDNN regression` (#3252); RL CI loss=0/logprob=NaN fix (#3232); `[rl] Add a tag to trigger RL CI runs` (#3230); RL `expandable_segments:True` for monarch RDMA (#3221); torchtitan-ubuntu docker upgraded to 22.04 + CTK 13.0 (#3183). - -### torchtitan upstream feature delta table - -| Area | New Upstream Capability | Evidence (PR/Commit) | Potential Impact to Primus | -| --- | --- | --- | --- | -| GraphTrainer / precompile | Memory policy registry + extra graph passes hook;
graph-pass timing + name logging;
regional Inductor RMSNorm fusion;
bucketing pass in precompile path (re-land);
CPU-offloading prefetch;
FSDP AG RS overlap;
FlexAttention precompile bitwise-deterministic tests (re-land) | pytorch/torchtitan #3215, #3261, #3132, #3213, #3166, #3156, #3214 | Major continuing GraphTrainer infrastructure; remains unavailable behind the stale pin. Will require coordinated patches in `primus/backends/torchtitan/**` when Primus syncs. | -| Compilation / vLLM | 50s → 15s vLLM compile-time win;
pipeline-parallel helpers refactored for graph PP reuse | pytorch/torchtitan #3145, #2724 | Direct training-loop wall-clock win when Primus syncs the pin. | -| MoE / float8 / mxfp8 | float8 quantize GroupedExperts params during conversion;
MXFP8GroupedExpertsConverter swap fix;
`AllToAllTokenDispatcher` `sp_size` token-count padding;
HybridEP-with-GraphTrainer integration tests | pytorch/torchtitan #3233, #3199, #3193, #3184 | Useful for Primus MoE configs (DSV3, GPT-OSS, Qwen3) on the torchtitan backend; aligns conceptually with this week's Primus-side Qwen3 attention adapter (#694) but the upstream consolidation of MoE dispatch is still gated by the urgent sync. | -| Attention / Qwen3 / Llama4 | Qwen3 MoE bitwise-deterministic tests + weight-tying gradient bug fix;
`get_attention_masks` refactored to positions;
llama4 `fsdp_mesh_info` fix;
CP `AllGather` wrong-dim fix;
CP>1 validation token-count fix | pytorch/torchtitan #3174, #3149, #3231, #3206, #3257 | Touches the same code paths as this week's Primus-Turbo Qwen3 attention adapter (#694); after sync, Primus's Qwen3 patch will need to be re-validated against the upstream weight-tying fix. | -| Observability / RL / CI | Structured logging + training instrumentation;
parallel CI integration tests;
MI350 baseline regenerated with actual losses;
RL CI loss/logprob fix | pytorch/torchtitan #3176, #3144, #3196, #3232 | Reference patterns for Primus torchtitan CI; the MI350 baseline regen pattern is directly applicable to Primus MI350-class CI runners. | - -## 6. Primus-Turbo Monthly Drift Overview - -- Drift type: **in-repo**, not upstream — compares Turbo version/SHA referenced on Primus `main` now vs the latest commit at or before `month_start_ts = 2026-05-01 00:00 Asia/Shanghai` (`2026-04-30 16:00 UTC`). For consistency with the W17/W18 reports, the **April→May** comparison anchor is the prior month-start (2026-04-01 00:00 GMT+8); both anchors give the same conclusion this week because the pin only moved on 2026-05-07. -- Turbo is **not a submodule** in Primus. Canonical version source: - - `.github/workflows/ci.yaml` → `PRIMUS_TURBO_COMMIT`, `PRIMUS_TURBO_AITER_COMMIT`, `TRITON_COMMIT` (also wired through `.github/workflows/docker/Dockerfile`) - - `.github/workflows/benchmark.yaml` → `PRIMUS_TURBO_COMMIT` -- Reference Primus commit at month start (April) on `main`: `766515755e9c29c11ed55e213dbc82f6581ca31e` (*[WIP][Megatron-LM] feat: reduce extra qkv transpose in attn (#625)*, 2026-03-31 14:19 GMT+8). The Turbo pins at that commit are byte-identical to the values reported in W17/W18. -- Current state on `origin/main` (W19): - - `ci.yaml` `PRIMUS_TURBO_COMMIT`: `ef5b58ea3de0a2956d57dba518be466b7a092442` — *feat(grouped_gemm): switch default backend to Triton for BF16 and FP8 (#320)*, 2026-04-29 - - `ci.yaml` `PRIMUS_TURBO_AITER_COMMIT`: `857f4d15775a29af153a2c68a2f8e8a8d696c986` - - `ci.yaml` `TRITON_COMMIT` (new env var, introduced this week): `88b227e23f0445f3f695bad05bbf1a363b4f50e0` - - `benchmark.yaml` `PRIMUS_TURBO_COMMIT`: `ef5b58ea3de0a2956d57dba518be466b7a092442` -- Month-start (2026-04-01) state on `main`: - - `ci.yaml` `PRIMUS_TURBO_COMMIT`: `333b68d7c81b722b21b4aad10cd250c45f15027c` — *fix sm_scale none bug (#263)*, 2026-03-27 - - `ci.yaml` `PRIMUS_TURBO_AITER_COMMIT`: `e83f9903c07001a0ec29e85d223f6e6cdbe00859` - - `benchmark.yaml` `PRIMUS_TURBO_COMMIT`: `a4488f6cdb15cfff4383c61af7922bb50803f0ea` — *feat: update triton impl for mi300 & mi355 (#252)*, 2026-03-19 - - `ci.yaml` `TRITON_COMMIT`: not present (new env var introduced by #694 in this window). -- **Primus-Turbo pin advanced this week.** CI pin moved 32 commits ahead of month start; benchmark pin moved 37 commits ahead of month start; AITER pin changed; a new explicit Triton source build was added. -- Recommendation: **monitor (new pin landed)**. The two YAML pins are now in sync (CI and benchmark both `ef5b58ea`), removing the previously reported skew. Track the next Turbo upstream bump and validate the new Triton-backed grouped-gemm default path on Primus MoE configs. - -### Notable areas changed since month start - -- **Grouped GEMM default backend**: Primus-Turbo #320 switches the default grouped-gemm backend to Triton for BF16 and FP8; combined with the new `TRITON_COMMIT=88b227e` source build, this changes the kernel path used by Primus-Turbo's `TEGroupedMLP` integration on the Megatron backend (the Primus-side gating was already removed in W18 #693). -- **Attention `torch.compile` wrappers**: Primus-Turbo #310 adds `torch.compile` custom wrappers for the attention path; relevant for both the Megatron backend and the new torchtitan Qwen3 attention adapter introduced in #694. -- **MoE EPBackend Protocol + `EPBufferConfig`**: Primus-Turbo #297 refactors the dispatcher around an extensible `EPBackend` Protocol with an explicit `EPBufferConfig`; Primus's `PrimusTurboDeepEPTokenDispatcher` (`primus/backends/megatron/core/transformer/moe/`) consumes this surface and may need a follow-up after this bump. -- **Attention layouts**: Primus-Turbo #275 (sbhd) and #304 (bhsd) extend attention layout coverage; aligns with this week's Primus Qwen3 attention adapter. -- **Symmetric Memory rewrite**: Primus-Turbo #276 replaces `hip-python` and `AMDSymmetricMemory` with a new `SymmetricMemory`; relevant when running DeepEP/MoE paths on ROCm. -- **MXFP8/FP8 quantization fixes**: Primus-Turbo #298 (MXScalingRecipe rename + grouped-gemm FP8 API), #306 (`reduce_row_kernel` partial-tile indexing), #307 (mxfp8 int32 overflow), #308 (mxfp8 gemm WAR race) tighten correctness on FP8 paths used by Primus MoE configs. -- **DeepEP single-stream toggle**: Primus-Turbo #314 brings back `deepep_use_comm_stream` to force single-stream comm; useful escape hatch for DeepEP correctness debugging on ROCm. -- **AITER pin moved**: `PRIMUS_TURBO_AITER_COMMIT` changed from `e83f9903` (month-start) to `857f4d15` (current); the Turbo build pulls AITER at this SHA inside the docker image. -- **Triton built from source**: a new explicit `TRITON_COMMIT=88b227e` is wired through `ci.yaml`, `benchmark.yaml` does not pin Triton today; the Dockerfile clones `triton-lang/triton@release/3.7.x`, checks out `${TRITON_COMMIT}`, and `pip install --no-build-isolation -v .` to compile Triton from source. - -### Primus-Turbo monthly drift table - -| Component | Current Version/SHA | Month-start Version/SHA | Delta Summary | Key Changes | Evidence | -| --- | --- | --- | --- | --- | --- | -| `PRIMUS_TURBO_COMMIT` (CI build) | `ef5b58ea3de0a2956d57dba518be466b7a092442` (*feat(grouped_gemm): switch default backend to Triton for BF16 and FP8 (#320)*, 2026-04-29) | `333b68d7c81b722b21b4aad10cd250c45f15027c` (*fix sm_scale none bug (#263)*, 2026-03-27) | **+32 commits** | Grouped-GEMM default → Triton (#320);
`Torch.compile` attention wrappers (#310);
EPBackend Protocol + EPBufferConfig refactor (#297);
SymmetricMemory rewrite (#276);
MXFP8/FP8 quant fixes (#306, #307, #308);
`deepep_use_comm_stream` re-introduction (#314). | [`.github/workflows/ci.yaml` L17](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/ci.yaml#L17), [Primus-Turbo compare](https://github.com/AMD-AGI/Primus-Turbo/compare/333b68d7c81b722b21b4aad10cd250c45f15027c...ef5b58ea3de0a2956d57dba518be466b7a092442) | -| `PRIMUS_TURBO_AITER_COMMIT` (CI build) | `857f4d15775a29af153a2c68a2f8e8a8d696c986` | `e83f9903c07001a0ec29e85d223f6e6cdbe00859` | **Pin advanced** | AITER pin advanced by #694; build pulls AITER at the new SHA inside the docker image. | [`.github/workflows/ci.yaml` L18](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/ci.yaml#L18) | -| `PRIMUS_TURBO_COMMIT` (benchmark) | `ef5b58ea3de0a2956d57dba518be466b7a092442` (*feat(grouped_gemm): switch default backend to Triton for BF16 and FP8 (#320)*, 2026-04-29) | `a4488f6cdb15cfff4383c61af7922bb50803f0ea` (*feat: update triton impl for mi300 & mi355 (#252)*, 2026-03-19) | **+37 commits** | Same set of upstream changes as the CI pin plus the earlier interval (`a4488f6c`→`333b68d7`); benchmark and CI pins are now in sync. | [`.github/workflows/benchmark.yaml` L9](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/benchmark.yaml#L9), [Primus-Turbo compare](https://github.com/AMD-AGI/Primus-Turbo/compare/a4488f6cdb15cfff4383c61af7922bb50803f0ea...ef5b58ea3de0a2956d57dba518be466b7a092442) | -| `TRITON_COMMIT` (CI build, new) | `88b227e23f0445f3f695bad05bbf1a363b4f50e0` (`triton-lang/triton@release/3.7.x`) | not present | **New env var** | Compiles Triton from source inside the docker image; wired through both the main and JAX docker builds. | [`.github/workflows/ci.yaml` L21](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/ci.yaml#L21), [`Dockerfile`](https://github.com/AMD-AGI/Primus/blob/main/.github/workflows/docker/Dockerfile) | - -## 7. Source Links - -- Primus main branch: https://github.com/AMD-AGI/Primus/tree/main -- Primus weekly PR listing (window): https://github.com/AMD-AGI/Primus/pulls?q=is%3Apr+is%3Amerged+base%3Amain+merged%3A%3E%3D2026-05-03T16%3A00%3A00Z -- PR #694 (Build Triton from source and bump Primus-Turbo): https://github.com/AMD-AGI/Primus/pull/694 -- PR #675 (fix(megatron): align tokenizer and dataset path interfaces with upgraded Megatron): https://github.com/AMD-AGI/Primus/pull/675 -- PR #668 (Fix/preflight: fix IP address matching): https://github.com/AMD-AGI/Primus/pull/668 -- Megatron-LM pin: https://github.com/NVIDIA/Megatron-LM/commit/d3528a21301db2d12e92912b3ec025dc8a2ed4d6 -- Megatron-LM upstream HEAD (at report time): https://github.com/NVIDIA/Megatron-LM/commit/7cdf652c16a0283b42ee13b7700481d0cfe2f291 -- Megatron-LM compare: https://github.com/NVIDIA/Megatron-LM/compare/d3528a21301db2d12e92912b3ec025dc8a2ed4d6...main -- torchtitan pin: https://github.com/pytorch/torchtitan/commit/5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021 -- torchtitan upstream HEAD (at report time): https://github.com/pytorch/torchtitan/commit/4ebb0895a18859ad0f0642901dba0cc888d82b97 -- torchtitan compare: https://github.com/pytorch/torchtitan/compare/5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021...main -- Primus-Turbo (new shared) pin: https://github.com/AMD-AGI/Primus-Turbo/commit/ef5b58ea3de0a2956d57dba518be466b7a092442 -- Primus-Turbo CI pin (W18 → W19) compare: https://github.com/AMD-AGI/Primus-Turbo/compare/333b68d7c81b722b21b4aad10cd250c45f15027c...ef5b58ea3de0a2956d57dba518be466b7a092442 -- Primus-Turbo benchmark pin (W18 → W19) compare: https://github.com/AMD-AGI/Primus-Turbo/compare/a4488f6cdb15cfff4383c61af7922bb50803f0ea...ef5b58ea3de0a2956d57dba518be466b7a092442 -- Triton release branch (new pin): https://github.com/triton-lang/triton/commit/88b227e23f0445f3f695bad05bbf1a363b4f50e0 -- Month-start reference commit on `main` (April): https://github.com/AMD-AGI/Primus/commit/76651575 -- Last week's report (W18): https://github.com/AMD-AGI/Primus/blob/main/docs/weekly_reports/2026-W18-primus-weekly.md - ---- - -*Generated automatically by the Primus weekly report automation. Factual statements are derived from `git log origin/main`, the pinned submodule SHAs in `third_party/`, and the `PRIMUS_TURBO_COMMIT`/`PRIMUS_TURBO_AITER_COMMIT`/`TRITON_COMMIT` values in `.github/workflows/{ci,benchmark}.yaml` as observed at 2026-05-08 09:01 GMT+8. Upstream-HEAD SHAs and commit counts are snapshots at report generation time.* diff --git a/docs/weekly_reports/dashboard-data/reports/2026-W17.json b/docs/weekly_reports/dashboard-data/reports/2026-W17.json deleted file mode 100644 index ad97b3c16..000000000 --- a/docs/weekly_reports/dashboard-data/reports/2026-W17.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "report_id": "2026-W17", - "content_type": "weekly-report", - "title": "Primus Weekly Engineering Report — 2026-W17", - "report_path": "docs/weekly_reports/2026-W17-primus-weekly.md", - "report_github_url": "https://github.com/AMD-AGI/Primus/blob/main/docs/weekly_reports/2026-W17-primus-weekly.md", - "time_window": { - "timezone": "Asia/Shanghai", - "start": "2026-04-20T00:00:00+08:00", - "end": "2026-04-24T16:56:00+08:00" - }, - "generated_at": "2026-04-24T16:56:00+08:00", - "merged_pr_count": 9, - "category_breakdown": { - "Bug Fix": 3, - "Performance Optimization": 2, - "Turbo/Dependency Version Update": 0, - "CI/Infra": 2, - "Refactor": 0, - "Docs": 2, - "Other": 0 - }, - "megatron_status": { - "pin_commit": "d3528a21301db2d12e92912b3ec025dc8a2ed4d6", - "pin_date": "2026-03-06", - "upstream_repo": "https://github.com/NVIDIA/Megatron-LM", - "upstream_ref": "main", - "upstream_head": "a1165fabcad97eae3778f386839c233dfabf3f8b", - "upstream_head_date": "2026-04-24", - "commit_gap": 365, - "recommendation": "plan sync" - }, - "torchtitan_status": { - "pin_commit": "5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021", - "pin_date": "2025-10-15", - "upstream_repo": "https://github.com/pytorch/torchtitan", - "upstream_ref": "main", - "upstream_head": "d40df991ac535108e428b0746a08b74a3cf6afc7", - "upstream_head_date": "2026-04-24", - "commit_gap": 514, - "recommendation": "urgent sync" - }, - "primus_turbo_status": { - "drift_type": "monthly_in_repo", - "month_start_ts": "2026-04-01T00:00:00+08:00", - "current_ci_commit": "333b68d7c81b722b21b4aad10cd250c45f15027c", - "month_start_ci_commit": "333b68d7c81b722b21b4aad10cd250c45f15027c", - "current_benchmark_commit": "a4488f6cdb15cfff4383c61af7922bb50803f0ea", - "month_start_benchmark_commit": "a4488f6cdb15cfff4383c61af7922bb50803f0ea", - "current_aiter_commit": "e83f9903c07001a0ec29e85d223f6e6cdbe00859", - "month_start_aiter_commit": "e83f9903c07001a0ec29e85d223f6e6cdbe00859", - "drift_commits": 0, - "recommendation": "monitor" - }, - "recommendations": { - "megatron": "plan sync", - "torchtitan": "urgent sync", - "primus_turbo": "monitor" - }, - "key_findings": [ - "9 PRs merged to main in the 2026-W17 window (Bug Fix: 3, Performance Optimization: 2, CI/Infra: 2, Docs: 2).", - "Week dominated by Megatron backend hardening against upstream API drift: #672 realigns the patched MoE overlap schedule after upstream removed post_attn; #671 makes the muon optimizer wrapper signature-aware; #674 rewrites recompute_layer_patches byte-identical to upstream with a SHA256 fingerprint guard.", - "Performance: #673 parallelizes per-rank pp_warmup with a bit-identical loss-parity UT; #684 introduces opt-in PRIMUS_EXIT_FAST shaving ~22s off post-train teardown on MI355X DSV3 EP8 and ~2m off the MI300X Megatron-LM E2E UT suite.", - "Infra: #687 lands the shared backend-gap dashboard publishing toolchain under tools/backend_gap_report/ plus the initial torchtitan baseline report; this weekly run extends the same shared site to surface Weekly Reports as a first-class section.", - "Megatron-LM upstream drift: pin d3528a21 (2026-03-06) is 365 commits behind upstream HEAD a1165fab; MoE router, Mamba→Hybrid rename (outside core), TE v2.14, DDP parameter-layout refactor, NVRx async compat. Recommendation: plan sync.", - "torchtitan upstream drift: pin 5fb7cc2e (2025-10-15) is 514 commits behind upstream HEAD d40df991; GraphTrainer precompile, MoE token dispatcher, Fused QKV GQAttention, FlexAttention CP, FSDP2 fully_shard. Recommendation: urgent sync.", - "Primus-Turbo month-to-date drift: none. CI and benchmark PRIMUS_TURBO_COMMIT pins byte-identical to 2026-03-30 values. Recommendation: monitor." - ] -} diff --git a/docs/weekly_reports/dashboard-data/reports/2026-W18.json b/docs/weekly_reports/dashboard-data/reports/2026-W18.json deleted file mode 100644 index c995e49ca..000000000 --- a/docs/weekly_reports/dashboard-data/reports/2026-W18.json +++ /dev/null @@ -1,69 +0,0 @@ -{ - "report_id": "2026-W18", - "content_type": "weekly-report", - "title": "Primus Weekly Engineering Report — 2026-W18", - "report_path": "docs/weekly_reports/2026-W18-primus-weekly.md", - "report_github_url": "https://github.com/AMD-AGI/Primus/blob/main/docs/weekly_reports/2026-W18-primus-weekly.md", - "time_window": { - "timezone": "Asia/Shanghai", - "start": "2026-04-27T00:00:00+08:00", - "end": "2026-05-01T09:10:00+08:00" - }, - "generated_at": "2026-05-01T09:10:00+08:00", - "merged_pr_count": 2, - "category_breakdown": { - "Bug Fix": 1, - "Performance Optimization": 0, - "Turbo/Dependency Version Update": 0, - "CI/Infra": 0, - "Refactor": 0, - "Docs": 0, - "Other": 1 - }, - "megatron_status": { - "pin_commit": "d3528a21301db2d12e92912b3ec025dc8a2ed4d6", - "pin_date": "2026-03-06", - "upstream_repo": "https://github.com/NVIDIA/Megatron-LM", - "upstream_ref": "main", - "upstream_head": "3460bba1d6f945ec04f47fdb1dcee6da3259fcd8", - "upstream_head_date": "2026-05-01", - "commit_gap": 413, - "recommendation": "plan sync" - }, - "torchtitan_status": { - "pin_commit": "5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021", - "pin_date": "2025-10-15", - "upstream_repo": "https://github.com/pytorch/torchtitan", - "upstream_ref": "main", - "upstream_head": "70340f4ec31d9e1dbd448506cc4423934c3cd62f", - "upstream_head_date": "2026-04-30", - "commit_gap": 566, - "recommendation": "urgent sync" - }, - "primus_turbo_status": { - "drift_type": "monthly_in_repo", - "month_start_ts": "2026-04-01T00:00:00+08:00", - "current_ci_commit": "333b68d7c81b722b21b4aad10cd250c45f15027c", - "month_start_ci_commit": "333b68d7c81b722b21b4aad10cd250c45f15027c", - "current_benchmark_commit": "a4488f6cdb15cfff4383c61af7922bb50803f0ea", - "month_start_benchmark_commit": "a4488f6cdb15cfff4383c61af7922bb50803f0ea", - "current_aiter_commit": "e83f9903c07001a0ec29e85d223f6e6cdbe00859", - "month_start_aiter_commit": "e83f9903c07001a0ec29e85d223f6e6cdbe00859", - "drift_commits": 0, - "recommendation": "monitor" - }, - "recommendations": { - "megatron": "plan sync", - "torchtitan": "urgent sync", - "primus_turbo": "monitor" - }, - "key_findings": [ - "2 PRs merged to main in the 2026-W18 window (Bug Fix: 1, Other/feature: 1). No backend pin or Turbo pin changed this week.", - "PR #693 finishes the Megatron-backend Turbo TEGroupedMLP migration: removes the patch-level guard that disabled PrimusTurbo for use_turbo_grouped_mlp + moe_grouped_gemm + TE>=1.9.0; adds a hard validate_args_on_rocm rejection of use_turbo_grouped_mlp=True combined with moe_use_legacy_grouped_gemm=True; preserves the legacy GroupedMLP under primus/backends/megatron/core/transformer/moe/deprecated_2caa681a1/; drops stale moe_use_legacy_grouped_gemm: true lines from MI300X/MI355X DeepSeek-V2/V3, GPT-OSS-20B, and Qwen3 30B/235B example YAMLs.", - "PR #651 adds initial LFM/LFM2 Megatron support: lfm_base.yaml + lfm2_8B_A1B.yaml configs, Lfm2ShortConv layer, gpt_decoder_layer_specs_patches.py routing get_gpt_decoder_layer_specs to the Primus implementation, and three MI355X lfm2_8B_A1B example pretrain configs (BF16, FP8, FP8-te-precision). Also commented out four rocmshared docker login lines in the build/push CI job.", - "Megatron-LM upstream drift: pin d3528a21 (2026-03-06) is now 413 commits behind upstream HEAD 3460bba1 (2026-05-01), +48 since W17. New since W17: AllgatherV inference dispatcher (#4258), permute fusion in hybrid EP (#4089), MTP CUDA graphs (#4260), MFSDP doc unification (#4418), heterogeneous TP/DP MIMO ColocatedBridgeCommunicator (#4368), checkpoint integrity verification (#4305), mamba-ssm/causal-conv1d move to optional [ssm] extra (#4517). Recommendation: plan sync (unchanged).", - "torchtitan upstream drift: pin 5fb7cc2e (2025-10-15) is now 566 commits behind upstream HEAD 70340f4e (2026-04-30), +52 since W17. New since W17: GraphTrainer unified activation-memory framework (#3118), full inductor compilation pass (#3141), async-TP graph pass (#3129), HybridEP integration with GraphTrainer (#3007), [MoE] All2AllTokenDispatcher consolidation across EP=1/EP>1 (#3125), ETP deprecation (#3167), Full DTensor config-based sharding for Llama3/Qwen3/Llama4/DSV3/GPT-OSS (#2963, #2969), MeshDimName→MeshAxisName rename (#3113), ChunkedCELoss (#2937). Recommendation: urgent sync (unchanged).", - "Primus-Turbo month-to-date drift (April → May 1 GMT+8): none. CI and benchmark PRIMUS_TURBO_COMMIT pins byte-identical to 2026-03-30 values. Recommendation: monitor.", - "Backend-gap reports were not refreshed this week: no submodule SHA change, no PRIMUS_TURBO_* pin change, and no other tracked backend-version source moved in the weekly window." - ] -} diff --git a/docs/weekly_reports/dashboard-data/reports/2026-W19.json b/docs/weekly_reports/dashboard-data/reports/2026-W19.json deleted file mode 100644 index 81bca2e62..000000000 --- a/docs/weekly_reports/dashboard-data/reports/2026-W19.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "report_id": "2026-W19", - "content_type": "weekly-report", - "title": "Primus Weekly Engineering Report — 2026-W19", - "report_path": "docs/weekly_reports/2026-W19-primus-weekly.md", - "report_github_url": "https://github.com/AMD-AGI/Primus/blob/main/docs/weekly_reports/2026-W19-primus-weekly.md", - "time_window": { - "timezone": "Asia/Shanghai", - "start": "2026-05-04T00:00:00+08:00", - "end": "2026-05-08T09:01:00+08:00" - }, - "generated_at": "2026-05-08T09:01:00+08:00", - "merged_pr_count": 3, - "category_breakdown": { - "Bug Fix": 2, - "Performance Optimization": 0, - "Turbo/Dependency Version Update": 1, - "CI/Infra": 0, - "Refactor": 0, - "Docs": 0, - "Other": 0 - }, - "megatron_status": { - "pin_commit": "d3528a21301db2d12e92912b3ec025dc8a2ed4d6", - "pin_date": "2026-03-06", - "upstream_repo": "https://github.com/NVIDIA/Megatron-LM", - "upstream_ref": "main", - "upstream_head": "7cdf652c16a0283b42ee13b7700481d0cfe2f291", - "upstream_head_date": "2026-05-07", - "commit_gap": 458, - "recommendation": "plan sync" - }, - "torchtitan_status": { - "pin_commit": "5fb7cc2e3bbb9b9dc0ab7af34ed5cc58b5f32021", - "pin_date": "2025-10-15", - "upstream_repo": "https://github.com/pytorch/torchtitan", - "upstream_ref": "main", - "upstream_head": "4ebb0895a18859ad0f0642901dba0cc888d82b97", - "upstream_head_date": "2026-05-07", - "commit_gap": 610, - "recommendation": "urgent sync" - }, - "primus_turbo_status": { - "drift_type": "monthly_in_repo", - "month_start_ts": "2026-04-01T00:00:00+08:00", - "current_ci_commit": "ef5b58ea3de0a2956d57dba518be466b7a092442", - "month_start_ci_commit": "333b68d7c81b722b21b4aad10cd250c45f15027c", - "current_benchmark_commit": "ef5b58ea3de0a2956d57dba518be466b7a092442", - "month_start_benchmark_commit": "a4488f6cdb15cfff4383c61af7922bb50803f0ea", - "current_aiter_commit": "857f4d15775a29af153a2c68a2f8e8a8d696c986", - "month_start_aiter_commit": "e83f9903c07001a0ec29e85d223f6e6cdbe00859", - "current_triton_commit": "88b227e23f0445f3f695bad05bbf1a363b4f50e0", - "month_start_triton_commit": null, - "drift_commits": 32, - "benchmark_drift_commits": 37, - "recommendation": "monitor (new pin landed)" - }, - "recommendations": { - "megatron": "plan sync", - "torchtitan": "urgent sync", - "primus_turbo": "monitor (new pin landed)" - }, - "key_findings": [ - "3 PRs merged to main in the 2026-W19 window (Bug Fix: 2, Turbo/Dependency Version Update: 1).", - "Backend dependency pin change: PR #694 bumps PRIMUS_TURBO_COMMIT in both ci.yaml (333b68d7 → ef5b58ea, +32) and benchmark.yaml (a4488f6c → ef5b58ea, +37) to a single shared SHA, bumps PRIMUS_TURBO_AITER_COMMIT (e83f9903 → 857f4d15), and introduces a new TRITON_COMMIT=88b227e23f0445f3f695bad05bbf1a363b4f50e0 build-arg that compiles Triton from triton-lang/triton@release/3.7.x source inside the docker image. PR #694 also adds a Primus-Turbo Qwen3 attention adapter under primus/backends/torchtitan/models/qwen3/ and exports PRIMUS_TURBO_ATTN_V3_ATOMIC_FP32=1 on the torch CI runner.", - "PR #675 closes Megatron-side interface debt after the recent Megatron upgrade: routes custom HF tokenizer types through Megatron's official build_tokenizer with a unique_identifiers fallback; normalizes data-path args for str/list/tuple to upstream-compatible semantics; realigns legacy core_gpt_dataset_config_from_args with upstream pretrain_gpt via get_blend_and_blend_per_split; wires missing upgraded fields (object_storage_cache_path, per_dataset_sequences_path, dataloader_fast_cache_load) to prevent silent mock-dataset fallback.", - "PR #668 replaces the substring `in` check in the preflight IP-address probe with a precise regex match in primus/tools/preflight/network/network_probe.py, eliminating false-positives where a target IP is a substring of another active IP.", - "Megatron-LM upstream drift: pin d3528a21 (2026-03-06) is now 458 commits behind upstream HEAD 7cdf652c (2026-05-07), +45 since W18. Highlights since W18: legacy transformer/modules removed (#4207), legacy GPT code removed (#4322), inference cache of input + position ID views (#4634), vLLM grouped-gemm MoE inference backend (#4566), checkpoint conversion between GPT_model and Hybrid_model (#4482), nvidia_resiliency_ext fault injection (#4370), gradient corruption fix for layerwise param AG overlap (#4609). Recommendation: plan sync (unchanged).", - "torchtitan upstream drift: pin 5fb7cc2e (2025-10-15) is now 610 commits behind upstream HEAD 4ebb0895 (2026-05-07), +44 since W18. Highlights since W18: GraphTrainer memory-policy registry + extra graph-passes hook (#3215), regional Inductor RMSNorm fusion (#3132), bucketing pass enabled in precompile path (re-land #3213), CPU-offloading prefetch pass (#3166), `[graph_trainer] FSDP AG RS overlap` (#3156), MXFP8GroupedExpertsConverter swap fix (#3199), float8 GroupedExperts quantization (#3233), AllToAllTokenDispatcher sp_size padding (#3193), 50s→15s vLLM compile-time win (#3145). Recommendation: urgent sync (unchanged).", - "Primus-Turbo monthly drift: pin advanced this week. CI pin moved 32 commits ahead of month start; benchmark pin moved 37 commits ahead of month start; the two pins are now in sync at ef5b58ea (Primus-Turbo #320, default grouped-gemm backend → Triton for BF16/FP8). AITER pin moved (e83f9903 → 857f4d15). New TRITON_COMMIT introduced. Recommendation: monitor (new pin landed).", - "Backend-gap reports were not regenerated this week: no submodule SHA changed (Megatron-LM, torchtitan, Megatron-Bridge, Emerging-Optimizers, HummingbirdXT, maxtext are all unchanged on origin/main); the Primus-Turbo pin bump is a tracked-config backend-version change but Primus-Turbo is not currently surfaced as a separate backend under docs/backend-gap/, so the existing torchtitan baseline report (5fb7cc2e vs upstream main) is unaffected." - ] -} diff --git a/docs_deprecated/README.md b/docs_deprecated/README.md new file mode 100644 index 000000000..d2659d274 --- /dev/null +++ b/docs_deprecated/README.md @@ -0,0 +1,82 @@ +# Primus Documentation + +Welcome to the Primus documentation! This guide will help you get started with training large-scale foundation models on AMD GPUs. + +> **Comprehensive Documentation**: For the complete production documentation set, see [`docs/`](../docs/README.md). It includes configuration references, parallelism guides, environment variable documentation, and more. + +## Documentation Structure + +### Getting Started + +Start here if you're new to Primus: + +- **[Quick Start Guide](./quickstart.md)** - Get up and running in 5 minutes +- **[CLI User Guide](./cli/PRIMUS-CLI-GUIDE.md)** - Complete command-line reference +- **[CLI Architecture](../docs/06-developer-guide/cli-architecture.md)** - Design philosophy and deep dive + +### User Guides + +Guides for common workflows and features: + +- **[Configuration System](../docs/02-user-guide/configuration-system.md)** - YAML configuration, presets, overrides, and inheritance +- **[Deployment Guide](../docs/05-operations/deployment.md)** - Container, Slurm, and Kubernetes deployment + +### Technical References + +In-depth technical documentation: + +- **[Post-Training Guide](./posttraining.md)** - Fine-tuning with SFT and LoRA using Primus CLI +- **[Native SFT & LoRA Quick Start](../docs/04-technical-guides/native-sft-lora.md)** - Megatron-native SFT/LoRA launch guide (BF16/FP8/FP4), no Megatron-Bridge runtime dependency +- **[Performance Projection](./projection.md)** - Project training performance and memory to multi-node configurations +- **[Tuning Agent](../docs/02-user-guide/tuning-agent.md)** - LLM-driven search for an optimal training config — parallelism plus batching, schedule, memory, MoE-comm, and precision knobs (drives the projection tool as an oracle) +- **[Preflight](./preflight.md)** - Cluster diagnostics (host/GPU/network info + perf tests) +- **[Benchmark Suite](./benchmark.md)** - GEMM, RCCL, end-to-end benchmarks and profiling +- **[Supported Models](./backends/overview.md#supported-models)** - Supported LLM architectures and feature compatibility matrix +- **[Backend Patch Notes](./backends/overview.md)** - Primus-specific arguments for Megatron, TorchTitan, etc. +- **[Backend Extension Guide](./backends/extending-backends.md)** - How to add a new backend using the current adapter/trainer architecture + - **[Megatron Model Extension Guide](./backends/adding-megatron-models.md)** - How to add a new Megatron model config + - **[TorchTitan Model Extension Guide](./backends/adding-torchtitan-models.md)** - How to add a new TorchTitan model config +- **[Flux Diffusion Models](../docs/04-technical-guides/diffusion-models/README.md)** - Flux diffusion model architecture, training, and API reference +- **[FP8 Training Guide](../docs/04-technical-guides/diffusion-models/fp8_training.md)** - FP8 precision training on AMD MI300X/MI355X: configuration, benchmarks, and tuning + +### Production Documentation + +For comprehensive coverage, see the [Production Documentation](../docs/README.md): + +- **[Configuration References](../docs/03-configuration-reference/megatron-parameters.md)** - Per-backend YAML parameter documentation +- **[Environment Variables](../docs/03-configuration-reference/environment-variables.md)** - Complete environment variable reference +- **[Parallelism Strategies](../docs/04-technical-guides/parallelism-strategies.md)** - Distributed training parallelism explained +- **[Performance Tuning](../docs/04-technical-guides/performance-tuning.md)** - HipBLASLt, Primus-Turbo, FP8, MoE optimization +- **[Troubleshooting](../docs/05-operations/troubleshooting.md)** - Common issues and solutions +- **[Architecture](../docs/06-developer-guide/architecture.md)** - System design and code architecture + +### Help and Support + +- **[Troubleshooting Guide](../docs/05-operations/troubleshooting.md)** - Common issues and solutions +- **[Examples](../examples/README.md)** - Real-world training examples and templates +- **[Preflight Tool](../primus/tools/preflight/README.md)** - Cluster sanity checker to verify environment readiness + +## Quick Navigation by Use Case + +### I want to... + +- **Train a model locally** → [Quick Start](./quickstart.md) + [CLI User Guide](./cli/PRIMUS-CLI-GUIDE.md) +- **Run distributed training on Slurm** → [Deployment Guide](../docs/05-operations/deployment.md) +- **Configure my training run** → [Configuration System](../docs/02-user-guide/configuration-system.md) +- **Look up YAML parameters** → [Configuration References](../docs/03-configuration-reference/megatron-parameters.md) +- **Project performance to multi-node** → [Performance Projection](./projection.md) +- **Auto-tune my training config (parallelism + knobs)** → [Tuning Agent](../docs/02-user-guide/tuning-agent.md) +- **Benchmark performance** → [Benchmark Suite](./benchmark.md) +- **Understand the CLI design** → [CLI Architecture](../docs/06-developer-guide/cli-architecture.md) +- **Troubleshoot issues** → [Troubleshooting](../docs/05-operations/troubleshooting.md) + +## External Resources + +- [Primus-Turbo](https://github.com/AMD-AGI/Primus-Turbo) - High-performance operators and modules +- [Primus-SaFE](https://github.com/AMD-AGI/Primus-SaFE) - Stability and platform layer +- [AMD ROCm Documentation](https://rocm.docs.amd.com/) +- [TorchTitan Documentation](https://github.com/pytorch/torchtitan) + +--- + +**Need help?** Check the [FAQ](./faq.md) or open an issue on [GitHub](https://github.com/AMD-AGI/Primus/issues). diff --git a/docs/backends/adding-megatron-models.md b/docs_deprecated/backends/adding-megatron-models.md similarity index 100% rename from docs/backends/adding-megatron-models.md rename to docs_deprecated/backends/adding-megatron-models.md diff --git a/docs/backends/adding-torchtitan-models.md b/docs_deprecated/backends/adding-torchtitan-models.md similarity index 100% rename from docs/backends/adding-torchtitan-models.md rename to docs_deprecated/backends/adding-torchtitan-models.md diff --git a/docs/backends/extending-backends.md b/docs_deprecated/backends/extending-backends.md similarity index 99% rename from docs/backends/extending-backends.md rename to docs_deprecated/backends/extending-backends.md index 129e57a51..e0ffc0bc6 100644 --- a/docs/backends/extending-backends.md +++ b/docs_deprecated/backends/extending-backends.md @@ -95,7 +95,7 @@ from typing import Any, Dict from primus.core.backend.backend_adapter import BackendAdapter from primus.core.backend.backend_registry import BackendRegistry -from primus.modules.module_utils import log_rank_0 +from primus.core.utils.module_utils import log_rank_0 class DummyAdapter(BackendAdapter): @@ -154,7 +154,7 @@ Key points: from typing import Any from primus.core.trainer.base_trainer import BaseTrainer -from primus.modules.module_utils import log_rank_0 +from primus.core.utils.module_utils import log_rank_0 class DummyPretrainTrainer(BaseTrainer): diff --git a/docs/backends/maxtext/patch-notes.md b/docs_deprecated/backends/maxtext/patch-notes.md similarity index 100% rename from docs/backends/maxtext/patch-notes.md rename to docs_deprecated/backends/maxtext/patch-notes.md diff --git a/docs/backends/megatron/patch-notes.md b/docs_deprecated/backends/megatron/patch-notes.md similarity index 100% rename from docs/backends/megatron/patch-notes.md rename to docs_deprecated/backends/megatron/patch-notes.md diff --git a/docs/backends/overview.md b/docs_deprecated/backends/overview.md similarity index 100% rename from docs/backends/overview.md rename to docs_deprecated/backends/overview.md diff --git a/docs/backends/torchtitan/patch-notes.md b/docs_deprecated/backends/torchtitan/patch-notes.md similarity index 100% rename from docs/backends/torchtitan/patch-notes.md rename to docs_deprecated/backends/torchtitan/patch-notes.md diff --git a/docs/benchmark.md b/docs_deprecated/benchmark.md similarity index 100% rename from docs/benchmark.md rename to docs_deprecated/benchmark.md diff --git a/docs/cli/PRIMUS-CLI-GUIDE.md b/docs_deprecated/cli/PRIMUS-CLI-GUIDE.md similarity index 94% rename from docs/cli/PRIMUS-CLI-GUIDE.md rename to docs_deprecated/cli/PRIMUS-CLI-GUIDE.md index c48915e6f..82046e9ca 100644 --- a/docs/cli/PRIMUS-CLI-GUIDE.md +++ b/docs_deprecated/cli/PRIMUS-CLI-GUIDE.md @@ -69,8 +69,20 @@ Primus CLI supports three execution modes, each suitable for different scenarios # Environment check (info only) ./primus-cli direct -- preflight --host --gpu --network + +# Per-node smoke test (auto-selects `--single` since node_smoke runs one +# process per node by design; rank 0 also aggregates the per-node JSONs): +./primus-cli direct -- node_smoke --tier2-perf + +# Suppress launcher + tool stdout (--silent goes BEFORE `--`; errors and +# the launcher log file are preserved; not recommended for normal use): +./primus-cli direct --silent -- preflight --quick ``` +**Optional environment variables (direct mode)**: +- `VENV_ACTIVATE` — Path to a Python virtualenv `bin/activate` script. If set, sourced before launching; if unset, no-op (the container path uses the container's bundled Python and never sets this). +- `NNODES` / `NODE_RANK` / `MASTER_ADDR` / `MASTER_PORT` / `GPUS_PER_NODE` — Pre-export to override SLURM-derived values. Inside a SLURM allocation, they are auto-derived from `SLURM_NNODES` / `SLURM_NODEID` / `SLURM_NODELIST` when not pre-exported. + **Suitable for**: - ✅ Local development and debugging - ✅ Single-node training @@ -166,13 +178,23 @@ Primus CLI supports three execution modes, each suitable for different scenarios # Run distributed GEMM benchmark ./primus-cli slurm srun -N 2 -- benchmark gemm --M 16384 --N 16384 --K 16384 -# Multi-node environment check (info only) -# this will generate a fast info report of the host, GPU, and network +# Multi-node environment check (info only). Anything after `--` that isn't +# the keyword `container` or `direct` is treated as a primus subcommand and +# routed through the default container entry chain. ./primus-cli slurm srun -N 4 -- preflight --host --gpu --network # this will generate a full preflight report of the host, GPU, and network, as well as the performance tests ./primus-cli slurm srun -N 4 -- preflight --report-file-name preflight-report-4N +# Explicit entry-mode keyword: route through primus-cli-direct.sh instead of +# the container chain. Useful when nodes share a Python venv on a shared FS +# (see docs/preflight-direct.md for the setup). +./primus-cli slurm srun -N 4 -- direct -- preflight --quick + +# Per-node smoke via the direct entry (node_smoke auto-runs in single mode; +# rank 0 aggregates after every rank finishes): +./primus-cli slurm srun -N 4 -- direct -- node_smoke --tier2-perf + # if you are using AINIC in your cluster, use the appropriate configuration file # for preflight test, set docker image to rocm/primus:v26.3 in the configuration file ./primus-cli --config runner/use_ainic.yaml slurm srun -N 2 -- preflight --report-file-name preflight-report-2N @@ -761,7 +783,7 @@ Final result: | **Entry Script** | primus-cli-direct.sh | primus-cli-container.sh | primus-cli-slurm.sh | | **Environment Prep** | Load local GPU env | Start container + mount + devices | Allocate nodes + network config | | **Execution Location** | Current host | Inside container | Slurm-allocated nodes | -| **Final Call** | Direct torchrun execution | Execute direct.sh in container | Each node executes slurm-entry.sh → direct.sh | +| **Final Call** | Direct torchrun execution (single mode auto-selected for `node_smoke`) | Execute direct.sh in container | Each node executes slurm-entry.sh → container.sh or direct.sh (via `direct` keyword) | | **Distributed Support** | Single-node multi-GPU | Single-node multi-GPU | Multi-node multi-GPU | | **Use Case** | Dev debugging | Environment isolation | Production training | @@ -982,7 +1004,7 @@ export PRIMUS_LOG_LEVEL=DEBUG ## Reference Resources ### Related Documentation -- [CLI Architecture](./CLI-ARCHITECTURE.md) - Primus CLI architecture deep dive +- [CLI Architecture](../../docs/06-developer-guide/cli-architecture.md) - Primus CLI architecture deep dive - [Main Documentation](../README.md) - Complete Primus documentation index - [.primus.yaml](../../runner/.primus.yaml) - Default configuration example diff --git a/docs/cli/README.md b/docs_deprecated/cli/README.md similarity index 73% rename from docs/cli/README.md rename to docs_deprecated/cli/README.md index bdb5e618e..a7e322a46 100644 --- a/docs/cli/README.md +++ b/docs_deprecated/cli/README.md @@ -13,7 +13,7 @@ The Primus CLI provides a unified command-line interface for training, benchmark - Configuration files and options - Best practices and troubleshooting -- **[Architecture Deep Dive](./CLI-ARCHITECTURE.md)** +- **[Architecture Deep Dive](../../docs/06-developer-guide/cli-architecture.md)** - Design philosophy and principles - Three-layer architecture explained - Plugin system and extensibility @@ -43,6 +43,22 @@ If you're running from the Primus repo root (after `git clone ... && cd Primus`) primus-cli direct -- benchmark gemm -M 4096 -N 4096 -K 4096 ``` +### Data Preparation Commands + +```bash +# Prepare a raw WebDataset (smaller, on-the-fly encoding during training) +primus-cli direct -- data diffusion-raw \ + --config primus/configs/data/megatron/diffusion/preprocessing/example_huggingface.yaml + +# Prepare a pre-encoded dataset from HuggingFace +primus-cli direct -- data diffusion-encoded \ + --config primus/configs/data/megatron/diffusion/preprocessing/example_huggingface.yaml + +# Ingest MLPerf Flux1 pre-encoded data (streaming download + conversion) +primus-cli direct -- data diffusion-ingest \ + --config primus/configs/data/megatron/diffusion/preprocessing/mlperf_flux1.yaml +``` + ## 🎯 Three Execution Modes | Mode | Use Case | Command Example | @@ -54,7 +70,7 @@ primus-cli direct -- benchmark gemm -M 4096 -N 4096 -K 4096 ## 📖 Learn More - For detailed usage instructions, see the [User Guide](./PRIMUS-CLI-GUIDE.md) -- For architecture and design details, see [Architecture Deep Dive](./CLI-ARCHITECTURE.md) +- For architecture and design details, see [Architecture Deep Dive](../../docs/06-developer-guide/cli-architecture.md) - For the main Primus documentation, see [Primus README](../../README.md) ## 🔗 Related Documentation diff --git a/docs/install-on-host.md b/docs_deprecated/install-on-host.md similarity index 100% rename from docs/install-on-host.md rename to docs_deprecated/install-on-host.md diff --git a/docs_deprecated/node-smoke-test-instruction.md b/docs_deprecated/node-smoke-test-instruction.md new file mode 100644 index 000000000..639a8dd5b --- /dev/null +++ b/docs_deprecated/node-smoke-test-instruction.md @@ -0,0 +1,313 @@ +# Node-Smoke Test — Quick-Start Instructions + +A short get-started guide for the per-node preflight smoke test. For the full design / aggregator section reference / implementation history, see [node-smoke.md](./node-smoke.md). + +--- + +## 1. What it does + +A lightweight, distributed-rendezvous-free preflight check that runs on every node in parallel under SLURM. It produces a **single PASS/FAIL verdict per node** plus SLURM-ready `passing_nodes.txt` / `failing_nodes.txt` you can pipe straight into `srun --nodelist=` / `--exclude=`. + +Use it to **screen a cluster fast and exclude bad nodes before launching a real training job**. A bad GPU, NIC, wedged driver, or leaked process on any node will surface as a node FAIL — without a single global rendezvous, so a stuck node can't wedge its peers. + +--- + +## 2. Prerequisites + + +| Prerequisite | How | +| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| Python venv on a shared filesystem | Same venv used by `primus-cli direct -- preflight` (see `[preflight-direct.md](./preflight-direct.md)` §2). | +| `VENV_ACTIVATE` exported | `export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate` (optional inside the container path). | +| Inside an existing SLURM allocation | One task per node. Recommended: `runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 -- direct -- node_smoke ...`. Equivalent bare form: `srun ... --ntasks-per-node=1 runner/primus-cli direct -- node_smoke ...`. Either way the `direct -- node_smoke` path auto-selects `--single`, so each task spawns one Python process and per-GPU subprocesses are launched internally. | + + +No `MASTER_ADDR`, no `MASTER_PORT`, no global rendezvous required. + +--- + +## 3. Quick start + +**Git clone the Primus repository to a shared filesystem that all nodes can read.** + +```bash +git clone --recurse-submodules https://github.com/AMD-AIG-AIMA/Primus.git +cd Primus +git checkout dev/preflight-direct-test +``` + +**Note: remember to setup the Python virtual environment and NCCL / fabric environment variables as described in [§2 Prerequisites](#2-prerequisites).** + +> ⚠ **Set the NCCL / RCCL environment first** if you plan to run with `--tier2-perf` (the local 8-GPU RCCL all-reduce). Even though the smoke test never opens a cross-node rendezvous, the Tier 2 RCCL step calls `dist.init_process_group(backend="nccl", ...)`, and RCCL **enumerates every transport at init** (XGMI / PCIe P2P + IB + sockets). A misconfigured `NCCL_IB_HCA` / `NCCL_SOCKET_IFNAME` / `NCCL_IB_GID_INDEX` can stall init or make the all-reduce silently fall back to a slow path. The launcher's `base_env.sh` auto-detects these via `get_nccl_ib_hca.sh` + `get_ip_interface.sh`, **but auto-detect sometimes picks the wrong values inside a container** (devices masked by the network namespace, frontend NICs picked up instead of fabric NICs, etc.) so you usually want to check these settings and set them explicitly if auto-detection is wrong. +> +> Minimum-viable checklist before running with `--tier2-perf`: +> +> ```bash +> # Pin the RDMA / RoCE training NICs the container can actually see. +> # On a bare-metal host the auto-detect in base_env.sh usually picks +> # the right set; inside a container or on a multi-role node, list +> # them explicitly. Use the same set you would pass to a training job. +> export NCCL_IB_HCA="rdma0,rdma1,rdma2,rdma3,rdma4,rdma5,rdma6,rdma7" +> +> # Pick the RoCE v2 GID index for your fabric: +> # - Mellanox / Broadcom: typically 3 (base_env.sh default). +> # - Pensando Pollara (AINIC): 1. +> export NCCL_IB_GID_INDEX=3 +> +> # The bootstrap socket interface. Auto-detect prefers the first +> # non-loopback interface from `hostname -I`; override when that +> # picks a frontend NIC instead of the data-plane interface. +> export NCCL_SOCKET_IFNAME=eno0 +> export GLOO_SOCKET_IFNAME=eno0 +> ``` +> +> See `[preflight-direct.md` § 4 Cluster-specific NCCL configuration](./preflight-direct.md#4-cluster-specific-nccl-configuration) for the canonical Broadcom / Pensando Pollara values (the same `NCCL_*` set is used by both tools). If you skip `--tier2-perf`, the RCCL step is not executed and none of the above applies — Tier 1 (host limits, RDMA roll-call, leaked-process detection, etc.) does not depend on RCCL. +> +> Quick verification: `runner/primus-cli direct --dry-run -- node_smoke --tier2-perf` prints the resolved `NCCL_*` block under "Environment Variables" so you can confirm the values before launching for real. + +Recommended — through the `primus-cli slurm srun` wrapper (auto-resolves `MASTER_ADDR`/`NNODES`/`NODE_RANK`, applies `slurm.*` config defaults): + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate + +# Basic Tier 1 check (~5 s/GPU, ~30 s total) +runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + -- direct -- node_smoke + +# Tier 1 + Tier 2 perf sanity (GEMM TFLOPS, HBM GB/s, local 8-GPU RCCL) +runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + -- direct -- node_smoke --tier2-perf + +# Then re-run training, excluding any node the smoke test failed: +srun --exclude=$(paste -sd, output/preflight/failing_nodes.txt) ... your-real-job +``` + +Equivalent with bare `srun` (works the same; useful when composing with custom `srun` flags): + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke + +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf +``` + +Single-node sanity check (no SLURM): + +```bash +runner/primus-cli direct -- node_smoke +``` + +> **Both forms produce the same workload.** The wrapper form is recommended because it resolves the distributed env once on the launching node and propagates it via `--env`, and applies any `slurm.`* config defaults (partition / time / etc.). See `[preflight-direct.md` § Wrapper vs. bare-srun](./preflight-direct.md#wrapper-vs-bare-srun) for the precedence table. + +--- + +## 4. More examples (by configuration knob) + +> **Convention used below.** The examples in this section are written with bare `srun` for brevity. Anywhere you see `srun runner/primus-cli direct -- node_smoke ...`, the equivalent wrapper form is `runner/primus-cli slurm srun -- direct -- node_smoke ...`. Pick whichever matches your habits; both target the same launcher. + +### 4.1 Hard-fail on partial NIC enumeration + +Catches "7 of 8 RDMA NICs visible" — common cause of crashes after RoCE init. + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf --expected-rdma-nics 8 +``` + +### 4.2 Tighten Tier 2 perf thresholds + +Reject GPUs that come in below your acceptance bar. Defaults: GEMM 600 TFLOPS, HBM 2000 GB/s, local RCCL 100 GB/s. + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf \ + --gemm-tflops-min 700 --hbm-gbs-min 4500 --rccl-gbs-min 180 +``` + +### 4.3 Tighten host limits + +Fail nodes whose `RLIMIT_MEMLOCK` or `/dev/shm` is too small for production training. + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke \ + --ulimit-l-min-gb 64 --shm-min-gb 16 +``` + +### 4.4 Custom dump path + +Keep one report per smoke run instead of overwriting the default location. + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf \ + --dump-path /shared/smoke-archive/$(date +%Y%m%d-%H%M%S) +``` + +### 4.5 Allow / extend the foreign-process whitelist + +By default, leaked / foreign processes holding a GPU FAIL the node (most common cause of "training fails to launch on a healthy-looking node"). Allowed by default: `gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter`. + +```bash +# Add a site-specific monitoring agent to the whitelist +srun ... runner/primus-cli direct -- node_smoke \ + --allowed-procs gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter,my-monitor + +# Don't fail at all on foreign processes (still reported in the markdown) +srun ... runner/primus-cli direct -- node_smoke --allow-foreign-procs +``` + +#### Containers: `name='N/A'` false positives → use `--allow-foreign-procs` + +> ⚠ **Running node_smoke inside a container almost always trips this check.** `amd-smi process --json` reports `name="N/A"` for kernel/system PIDs like `gpuagent` whose `/proc//comm` it cannot read, and the fallback `_resolve_proc_name(pid)` inside `node_smoke` then also fails because the container's `/proc` typically does not expose host PIDs (private PID namespace without `--pid=host`, or a `hidepid=2` mount). The unresolved name doesn't match the allowlist (`gpuagent,rocm-smi-daemon,...`), so the check fires and the node FAILs — even though the only "foreign" processes are well-known system daemons holding zero HBM. +> +> **In the container path, pass `--allow-foreign-procs`:** +> +> ```bash +> srun ... runner/primus-cli direct -- node_smoke --tier2-perf --allow-foreign-procs +> ``` +> +> The processes are still listed in `smoke_report.md` under "Busy GPUs / leaked processes" so a real leak is still visible; only the FAIL verdict is downgraded. +> +> **Narrower alternative** if you want the check to still catch leaks with resolvable names (e.g. a leftover `python` rank), add the literal sentinel `N/A` to the allowlist: +> +> ```bash +> srun ... runner/primus-cli direct -- node_smoke --tier2-perf \ +> --allowed-procs gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter,N/A +> ``` +> +> The annotator runs `_resolve_proc_name` first, so whenever a real name *can* be resolved (on the host, or after fixing `/proc` visibility) it overrides "N/A" and the normal allowlist applies. The `N/A` entry only matches PIDs whose name genuinely could not be recovered — strictly narrower than `--allow-foreign-procs`. +> +> **Root-cause fix** (preferred long-term): grant the container access to host PIDs so `_resolve_proc_name` works and the report shows real names (`gpuagent`, etc.) instead of `N/A`. Typical fixes: +> +> - Launch with `--pid=host` (Docker / Podman) so host PIDs are directly addressable. +> - Mount `/proc` without `hidepid=2`. +> - Loosen `ptrace_scope` or grant `CAP_SYS_PTRACE`. +> +> Once any of those is in place, `_resolve_proc_name` finds the names, the default allowlist matches them, and you no longer need `--allow-foreign-procs`. + +### 4.6 Require specific tools + +Make missing CLI tools a hard FAIL (default: warn-only). + +```bash +srun ... runner/primus-cli direct -- node_smoke --require-tools amd-smi,rocm-smi,lsof +``` + +### 4.7 Skip dmesg scan (containers with no privileges) + +```bash +srun ... runner/primus-cli direct -- node_smoke --skip-dmesg +``` + +### 4.8 Re-aggregate from existing per-node JSONs (no re-run) + +Useful when you only want to refresh the markdown report, or when you've collected JSONs separately. + +```bash +# From any node, no allocation needed if you're just reading local files. +# The primus-cli wrapper always runs both phases, so use the standalone +# aggregate subcommand for "aggregate only" -- it reads the existing +# /smoke/*.json without re-running the per-node smoke step. +python -m primus.tools.preflight.node_smoke aggregate \ + --dump-path output/preflight --expected-nodes 6 --wait-timeout-sec 5 +``` + +### 4.9 Silent mode (for CI) + +Suppresses wrapper stdout, but the **final report path is still printed** and stderr / exit code are preserved. + +```bash +srun ... runner/primus-cli direct --silent -- node_smoke --tier2-perf +``` + +### 4.10 Combined "production-ready screen" + +A representative one-shot for a production cluster screen: + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct --silent -- node_smoke --tier2-perf \ + --expected-rdma-nics 8 \ + --gemm-tflops-min 700 --hbm-gbs-min 4500 --rccl-gbs-min 180 \ + --ulimit-l-min-gb 64 --shm-min-gb 16 \ + --require-tools amd-smi,rocm-smi,lsof \ + --dump-path /shared/smoke-archive/$(date +%Y%m%d-%H%M%S) +``` + +--- + +## 5. Outputs + +All written under `--dump-path` (default `output/preflight/`). + + +| File | Purpose | +| ------------------------- | -------------------------------------------------------------------------------------------------------------------- | +| `smoke/.json` | Per-node verdict + every collected metric. One file per node. | +| `smoke_report.md` | Human-readable cluster report (status table, drift sections, perf summary, failing-node detail). | +| `passing_nodes.txt` | Newline-separated short hostnames. Pipe into `srun --nodelist=`. | +| `failing_nodes.txt` | Newline-separated short hostnames. Pipe into `srun --exclude=`. | +| `expected_nodes.txt` | Auto-populated from `scontrol show hostnames "$SLURM_JOB_NODELIST"`. Lets the report name nodes that never reported. | + + +Read the cluster verdict at a glance: + +```bash +head -10 output/preflight/smoke_report.md +``` + +Feed bad nodes into a re-run: + +```bash +srun --exclude=$(paste -sd, output/preflight/failing_nodes.txt) ... your-real-job +``` + +--- + +## 6. Common knobs (cheat sheet) + + +| Flag | Default | When you'd change it | +| ---------------------------- | ------------------------------------------------ | ------------------------------------------------------------------------------------- | +| `--tier2-perf` | off | Always on for production screens — adds GEMM TFLOPS, HBM GB/s, local RCCL all-reduce. | +| `--gemm-tflops-min N` | 600 | Site-specific acceptance bar. | +| `--hbm-gbs-min N` | 2000 | Site-specific acceptance bar (MI300X healthy ≈ 4500–5000). | +| `--rccl-gbs-min N` | 100 | Site-specific acceptance bar. | +| `--expected-rdma-nics N` | unset | Hard-fail on partial NIC enumeration. | +| `--ulimit-l-min-gb GB` | 32 | Raise for production training profiles. | +| `--shm-min-gb GB` | 8 | Raise for large-batch / many-rank profiles. | +| `--allow-foreign-procs` | off | Co-tenant clusters or shared GPUs. | +| `--allowed-procs LIST` | `gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter` | Add site-specific monitoring agents. | +| `--require-tools LIST` | `""` | Fail-fast if a CLI tool is missing in PATH. | +| `--skip-dmesg` | off | Inside unprivileged containers. | +| `--dump-path DIR` | `output/preflight` | Archive each run separately. | +| `--silent` (wrapper) | off | CI / scripted runs. | +| `--aggregate-only` (wrapper) | off | Re-render report without re-running per-node checks. | + + +For the full flag list and the aggregator subcommand, see `python -m primus.tools.preflight.node_smoke run --help` and `... aggregate --help`, or `[node-smoke.md](./node-smoke.md)` §"Configuration knobs". + +--- + +## 7. Troubleshooting + + +| Symptom | Likely cause / fix | +| ----------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `[ERROR] [direct] VENV_ACTIVATE is set but file does not exist: ...` | Fix the path (`export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate`), or `unset VENV_ACTIVATE` to fall back to system / container Python. | +| Every node FAILs with `gpu_processes: ... name='N/A'` | Should no longer happen after the `/proc//comm` fallback fix. If it does, check that `/proc//comm` is readable on the node (`hidepid` mount?). Workaround: `--allow-foreign-procs`. | +| Some nodes never produce a JSON | Aggregator names them in `failing_nodes.txt` via `expected_nodes.txt`. If `scontrol` was unavailable, they'll appear as ``. | +| Tier 2 perf numbers below threshold on a known-good node | Almost always insufficient CPU cores on `srun` — pass `-c ` so RCCL proxy threads have CPU. | +| Re-run on a smaller nodelist still shows the previously removed nodes as PASS | Default behavior cleans stale JSONs on rank 0. If you passed `--no-clean-dump-path`, either remove it or `rm -rf output/preflight` between runs. | + + +--- + +## 8. See also + +- `[node-smoke.md](./node-smoke.md)` — full design, aggregator sections, configuration reference, implementation history. +- `[preflight-direct.md](./preflight-direct.md)` — the heavier `preflight` tool with global rendezvous and inter-node bandwidth tests. +- `[primus/cli/subcommands/node_smoke.py](../primus/cli/subcommands/node_smoke.py)` — the primus-cli subcommand wiring (two-phase dispatch: rank-N run + rank-0 aggregate). +- `[primus/tools/preflight/node_smoke/cli.py](../primus/tools/preflight/node_smoke/cli.py)` — canonical flag definitions and per-node / aggregate phase bodies. diff --git a/docs_deprecated/node-smoke.md b/docs_deprecated/node-smoke.md new file mode 100644 index 000000000..98471ea80 --- /dev/null +++ b/docs_deprecated/node-smoke.md @@ -0,0 +1,429 @@ +# Node-Local Smoke Test + +> **Just want to run it?** See [`node-smoke-test-instruction.md`](./node-smoke-test-instruction.md) for the short quick-start guide. This document is the full reference (architecture, every report section, every flag, design history). + +A lightweight, distributed-rendezvous-free preflight check that runs on every node in parallel under SLURM. Designed to **quickly identify broken nodes before a large training job commits to a global rendezvous**. Because training jobs allocate whole nodes, a under-performing GPU (or NIC, or wedged driver) takes the entire node out of rotation -- so the smoke test produces a single PASS/FAIL verdict per node and SLURM-ready `passing_nodes.txt` / `failing_nodes.txt` you can pipe straight into `srun --nodelist=` / `--exclude=`. + +- **Implementation**: `primus/tools/preflight/node_smoke/` (Python sub-package; entry point `python -m primus.tools.preflight.node_smoke`). +- **Recommended launcher**: `runner/primus-cli slurm srun -- direct -- node_smoke ...` (auto-resolves `MASTER_ADDR`/`NNODES`/`NODE_RANK` via `--env`, applies `slurm.*` config defaults, same pattern as `train` / `benchmark`). The shorter `runner/primus-cli direct -- node_smoke ...` form (bare `srun` + direct) is equivalent and useful for ad-hoc runs. +- **Companion**: see `docs/preflight.md` for the full preflight tool (with global rendezvous and richer perf tests). + +## Quick start + +Recommended — through the `primus-cli slurm srun` wrapper: + +```bash +# Inside an existing SLURM allocation (the normal case): +runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + -- direct -- node_smoke + +# With perf sanity (GEMM TFLOPS, HBM GB/s, local 8-GPU RCCL all-reduce): +runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + -- direct -- node_smoke --tier2-perf + +# Hard-fail on partial NIC enumeration (e.g. 7 of 8 RDMA NICs). +# The count is compared against the *training-NIC* set after the +# selector chain runs, so frontend / storage RoCE NICs do not count. +runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + -- direct -- node_smoke --tier2-perf --expected-rdma-nics 8 + +# Explicitly pin the training-NIC selector (otherwise NCCL_IB_HCA env +# is used; otherwise admin-disabled phys_state ports are auto-excluded): +runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + -- direct -- node_smoke --tier2-perf \ + --rdma-nic-allowlist 'rocep158s0:1,rocep190s0:1,rocep206s0:1,rocep222s0:1,rocep28s0:1,rocep62s0:1,rocep79s0:1,rocep96s0:1' +``` + +Equivalent with bare `srun` (works the same; useful when composing with custom `srun` flags): + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke + +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf +``` + +Single-node local check (no SLURM, both forms collapse to the same call): + +```bash +runner/primus-cli direct -- node_smoke +``` + +> **Note on the `direct` keyword**: with the `primus-cli slurm srun` wrapper, the entry-mode keyword `direct` between the two `--`s is mandatory to take the direct (no-container) path. Without it the wrapper routes through the **container** path. See [`preflight-direct.md` § Wrapper vs. bare-srun](./preflight-direct.md#wrapper-vs-bare-srun) for the full precedence and caveats. + +When `VENV_ACTIVATE` is set, `primus-cli direct` sources it before launching `node_smoke` (same convention as `primus-cli direct -- preflight`). When unset (e.g. inside the container path), it is a no-op. + +## Outputs + +After a run, `/` (default `output/preflight/`) contains: + +| File | Purpose | +|---|---| +| `smoke/.json` | Per-node verdict + every collected metric. One file per node. | +| `smoke_report.md` | Human-readable cluster report (status table, drift sections, perf summary, failing-node detail). | +| `passing_nodes.txt` | Newline-separated short hostnames. Pipe into `srun --nodelist=`. | +| `failing_nodes.txt` | Newline-separated short hostnames. Pipe into `srun --exclude=`. | +| `expected_nodes.txt` | (auto-populated from `scontrol show hostnames "$SLURM_JOB_NODELIST"`) Used by the aggregator to name nodes that never reported. | + +```bash +# Re-run training, excluding the bad nodes from the previous smoke: +srun --exclude=$(paste -sd, output/preflight/failing_nodes.txt) ... your-real-job +``` + +## Architecture + +- **Per-node Python entry** (`node_smoke.py run`) — runs independently on every node. No `MASTER_ADDR`, no global `torch.distributed` rendezvous; a stuck node cannot wedge its peers. +- **Per-GPU isolation** — each GPU's checks run in their own Python subprocess with a hard timeout. A stuck `torch.cuda.set_device()` (which can't be aborted by `signal.alarm` because it sits inside a non-interruptible driver syscall) is `SIGKILL`'d from the parent without affecting the rest of the node's checks. +- **Local-only RCCL** — Tier 2 all-reduce uses `torch.multiprocessing.spawn` over `tcp://127.0.0.1`. No cross-node communication. +- **Aggregator on `NODE_RANK==0`** — polls `/smoke/` for the expected number of JSONs (with a timeout), computes drift across the cluster, writes the markdown report and pass/fail txt files. Returns non-zero if any node FAILs or never reports. + +## Module layout — where each check lives + +The implementation is a Python sub-package under `primus/tools/preflight/node_smoke/`, split so each Tier 1 sub-section, the per-GPU subprocess body, the orchestrator, and the aggregator each live in their own file. The single public entry point is `main` (re-exported from `__init__`); `python -m primus.tools.preflight.node_smoke ...` resolves to `__main__.py` which calls it. + +``` +primus/tools/preflight/node_smoke/ +├── __init__.py # re-export `main` +├── __main__.py # `python -m primus.tools.preflight.node_smoke` +├── cli.py # `_build_parser`, `_cmd_run`, `_cmd_aggregate`, +│ # `_cmd_per_gpu`, `main` +├── types.py # `GPUResult`, `NodeResult` dataclasses +├── logging_utils.py # `_ts`, `_log`, `_warn`, hostname normalisation +├── shell_utils.py # `_which`, `_read_text`, `_resolve_gpu_bdf`, +│ # `_systemctl_is_active`, `_parse_size_with_unit`, +│ # `_findings_to_dicts` +├── per_gpu.py # `_per_gpu_body` (Tier 1 + optional Tier 2 perf, +│ # GEMM/HBM bandwidth measurement) +├── rccl_local.py # node-local RCCL all-reduce (Tier 2) +├── orchestrator.py # `_spawn_per_gpu`, `_node_status_from`, +│ # `_clean_dump_path` +├── collectors/ # one module per Tier 1 sub-section +│ ├── dmesg.py # recent dmesg error scan +│ ├── fingerprint.py # Tier 1 A — software-stack fingerprint +│ ├── nics.py # Tier 1 B — NIC / RDMA roll-call +│ ├── host_limits.py # Tier 1 C — ulimit / shm / NUMA / governor +│ ├── gpu_low_level.py # Tier 1 D-1 — amd-smi metric (ECC, throttle, +│ │ # clocks, power) +│ ├── xgmi.py # Tier 1 D-2 — XGMI link matrix +│ ├── clock.py # Tier 1 E — wall time + time-daemon health +│ ├── rocm_smi.py # Tier 1 F + cross-tool fallbacks for D-1/2/G +│ ├── gpu_processes.py # Tier 1 G — foreign / leaked PID detection +│ ├── tooling.py # tooling availability inventory +│ └── reused_info.py # reused gpu/host/network info collectors +└── aggregator/ + ├── summarizers.py # `_*_rows` / `_*_summary` data shapers + └── report.py # `write_smoke_report` + one `_write_
` + # helper per Markdown `##` section +``` + +Dependency graph (acyclic; arrows mean "imports"): + +```mermaid +flowchart TD + cli[cli.py] --> orchestrator[orchestrator.py] + cli --> per_gpu[per_gpu.py] + cli --> rccl_local[rccl_local.py] + cli --> aggReport["aggregator/report.py"] + cli --> collectorsAll["collectors/*"] + cli --> logging[logging_utils.py] + cli --> types[types.py] + + aggReport --> aggSum["aggregator/summarizers.py"] + aggReport --> logging + aggSum --> tooling["collectors/tooling.py"] + + orchestrator --> types + + per_gpu --> shell[shell_utils.py] + + collectorsAll --> shell + collectorsAll --> rocmsmi["collectors/rocm_smi.py"] + rocmsmi --> shell + logging -.->|stdlib only| std[(socket / sys / time)] + shell -.->|stdlib only| std + types -.->|stdlib only| std +``` + +* `collectors/` are leaf modules (depend on `shell_utils` + sometimes `collectors/rocm_smi`); they never import `cli` / `orchestrator` / `per_gpu`. +* `per_gpu` is the body of the `_per_gpu` subprocess and is the ONLY module loaded inside that subprocess via `python -m primus.tools.preflight.node_smoke _per_gpu N` — so its dependency surface is intentionally narrow (only `shell_utils`). +* `aggregator/` only depends on its own `summarizers` plus `logging_utils` (and `collectors/tooling` for the static `_TRACKED_TOOLS` constant). + +## What's checked + +### Tier 1 — mandatory (~5 s / GPU, always runs) + +**Per-GPU subprocess (with hard timeout):** +- `torch.cuda.set_device(i)` — proves the device is bindable (a stale GPU often fails here) +- 256 MB allocation +- Tiny GEMM (2048² bf16) with `isfinite()` check on the result + +**Reused from existing preflight collectors** (no rendezvous needed): +- `collect_gpu_info` — `level='fail'` Findings cause node FAIL +- `collect_host_info` — same +- `collect_network_info(expect_distributed=False)` — same + +**dmesg recent-error scan** — greps the last `--dmesg-minutes` (default 15) of `dmesg` for known patterns (`xid`, `gpu reset`, `hung_task`, `mce:`, `amdgpu.*error`, ...). Matches are surfaced in the report. + +**A. Software-stack fingerprint** (`tier1.fingerprint`): +- Kernel, OS, Python +- ROCm version (`/opt/rocm/.info/version`) +- amdgpu kernel-module version (`/sys/module/amdgpu/version`) +- PyTorch version, `torch.version.hip`, RCCL version (via `torch.cuda.nccl.version()`), librccl path +- Per-IB-device firmware (`/sys/class/infiniband//fw_ver`) and HCA model + +**B. NIC / RDMA roll-call** (`tier1.nics`): +- Per port (read entirely from `/sys/class/infiniband` — no `ibv_devinfo`/`ibstat` dependency, works inside containers): `state`, `phys_state`, `rate`, netdev + MTU, total non-zero GIDs, RoCE v2 GID count +- **Training-NIC selector** — many clusters expose more RDMA-capable ports than the training job uses (frontend / management / storage NICs). The hard-fail rules only run against the *included* subset. Precedence: + 1. `--rdma-nic-allowlist 'rocep158s0:1,rocep190s0:1,...'` (full `NCCL_IB_HCA` syntax: comma-separated `device[:port]`, `^...` for denylist, `=dev` for exact-match, no `:port` to match any port on the device). + 2. `NCCL_IB_HCA` env (same syntax) — mirrors what NCCL/RCCL itself will use, so the smoke test and the training launch agree by construction. + 3. Heuristic: auto-exclude any port whose `phys_state` is `Disabled` or `Sleep` (admin-disabled at firmware/driver level — no SFP, BIOS port-disable, netdev admin-down). Real failure modes on a port that *is* meant to be used produce a different `phys_state` (`Polling`, `LinkErrorRecovery`, or `LinkUp` with `state!=ACTIVE`), so this heuristic does not mask cable / driver problems. + 4. Fallback: every IB port must be ACTIVE / LinkUp. +- Excluded ports stay visible in `tier1.nics.ports` and are summarised in `tier1.nics.excluded_ports` + `info_issues` for diagnostics. They do NOT contribute to the node FAIL signal. +- **Hard fail rules** (only on the included set): port not `ACTIVE` / not `LinkUp`, active port with zero RoCE v2 GIDs (RoCE) or zero valid GIDs (IB), included-NIC count ≠ `--expected-rdma-nics N` (when set). +- **Empty-set guard**: if every discovered port gets excluded, the node still hard-fails — a node with zero training NICs cannot participate in inter-node training. + +**C. Host limits / system tunables** (`tier1.host_limits`): +- `RLIMIT_MEMLOCK`, `RLIMIT_NOFILE`, `RLIMIT_NPROC` +- `/dev/shm` size + free +- NUMA node count, CPU count, `cpu0` scaling governor +- **Hard fail rules**: `RLIMIT_MEMLOCK` finite and below `--ulimit-l-min-gb` (default 32 GiB) → "RDMA pin will fail under load"; `/dev/shm` size below `--shm-min-gb` (default 8 GiB) → "NCCL shared-mem may fail" + +### Tier 2 — optional perf sanity (`--tier2-perf`) + +Per-GPU steady-state metrics, with iteration counts aligned to the preflight `--quick` preset (`warmup=5, iters=20` for GEMM/RCCL; `warmup=10, iters=20` for HBM) so smoke and preflight numbers are directly comparable. + +- **GEMM TFLOPS** — 8192³ bf16 `torch.matmul`, threshold `--gemm-tflops-min` (default 600). +- **HBM GB/s** — 512 MB device-to-device `torch.Tensor.copy_` (counts read + write), threshold `--hbm-gbs-min` (default 2000). +- **Local 8-GPU RCCL all-reduce GB/s** — algorithmic bandwidth `2·S·(P-1)/P / t / 1e9` at 64 MB, threshold `--rccl-gbs-min` (default 100). + +## Aggregator report sections + +Every section short-circuits to a placeholder (`*All nodes match.*` / `*No NIC issues.*` / `*No host-limit issues.*`) on a healthy cluster, so the report stays short. Each section header is part of the operator-facing contract — order and wording are stable across releases (some Slack bots / CI scripts grep for them). + +In order: + +1. **Status table** — one row per node with `node_rank`, hostname, PASS/FAIL, duration, top fail reason. +2. **Stack drift across cluster** — for every scalar fingerprint key, outliers vs the cluster majority. +3. **NIC firmware drift across cluster** — per-IB-device firmware drift. +4. **NIC / RDMA roll-call issues** — every offending node + port (included set only). +5. **NIC port-count summary** — cluster-majority *training-NIC* count and any node that disagrees (catches partial-NIC degradation without `--expected-rdma-nics`). The count is taken from the included set, so nodes that legitimately have extra frontend / storage RoCE NICs don't show up as anomalies. +6. **NIC excluded ports (informational)** — ports the selector chain dropped from the training-NIC set, grouped by source (`--rdma-nic-allowlist` / `NCCL_IB_HCA` / heuristic). Informational only; does not contribute to FAIL. +7. **Host limits issues** — per-node hard-limit violations. +8. **GPU visibility issues** — nodes where torch couldn't see the GPUs or amd-smi sees more GPUs than torch (stale ROCm / wedged amdgpu driver). Independent of every other collector. +9. **GPU low-level outliers (PCIe link / HBM)** — per-GPU outliers vs the cluster majority on PCIe width/speed and HBM total. +10. **XGMI link issues** — any non-XGMI GPU pair (intra-node collectives silently fall back to PCIe). +11. **Cluster clock + time daemons** — wall-clock spread plus per-node time-daemon health. +12. **Tooling self-latency (`rocm-smi --version`)** — slow / timed-out tool calls (precursor to a wedged amdgpu driver). +13. **Tooling availability** — always-on inventory of `amd-smi` / `rocm-smi` / `lsof` per node, plus which Tier 1 checks have NO working tool on each node. +14. **Busy GPUs / leaked processes** — foreign PIDs holding GPUs at smoke start (most common cause of training failing to launch on an otherwise-healthy node). +15. **GPU pre-touch HBM usage outliers** — GPUs with non-trivial HBM in use BEFORE smoke touched the device. +16. **GPU compute-activity outliers** — GPUs with `gfx_activity_pct >= --gpu-activity-warn-pct` at smoke start (warn-only). +17. **Tier 2 perf summary** (conditional, only when at least one node ran Tier 2) — per-node GEMM TFLOPS / HBM GB/s as `min / median / max`, plus local RCCL GB/s. +18. **Failing nodes — full reasons** (conditional, only when there are failing nodes) — every fail reason, expanded per node. + +Each section that does pure data shaping is wrapped in its own `try / except`, so a future schema bug in one section can't truncate the rest of the report. The two intentional EXCEPTIONS are **Tier 2 perf summary** and **Failing nodes — full reasons** — both deliberately propagate exceptions so a regression in either bubbles up rather than silently rendering a half-empty section. + +## Configuration knobs + +The authoritative source of flags + defaults is `python -m primus.tools.preflight.node_smoke run --help` (and `... aggregate --help`). The tables below mirror the parser as of the package-split refactor. + +### `run` subcommand + +| Flag | Default | Purpose | +|---|---|---| +| `--dump-path` | `output/preflight` | Output directory. | +| `--expected-gpus N` | auto | Override GPU count (auto-detected from `LOCAL_WORLD_SIZE` / `GPUS_PER_NODE` / `torch.cuda.device_count()`). | +| `--per-gpu-timeout-sec` | 15 | Hard timeout per per-GPU subprocess. | +| `--tier2-perf` | off | Enable Tier 2 perf sanity (per-GPU GEMM TFLOPS + HBM GB/s + node-local RCCL all-reduce). Single switch — you cannot enable just one half. | +| `--gemm-tflops-min` | 600 | Tier 2 GEMM threshold. | +| `--hbm-gbs-min` | 2000 | Tier 2 HBM threshold. | +| `--rccl-size-mb` | 64 | Local RCCL message size. | +| `--rccl-gbs-min` | 100 | Local RCCL bandwidth threshold. | +| `--rccl-timeout-sec` | 120 | Hard timeout for the RCCL phase. | +| `--skip-dmesg` | off | Skip dmesg scan (e.g. inside containers). | +| `--dmesg-minutes` | 15 | dmesg `--since` window. | +| `--expected-rdma-nics N` | auto-report-only | When set, a mismatch between the **included (training-NIC) count** and N becomes a node FAIL. Compares against the post-selector count, not the raw number of devices under `/sys/class/infiniband`. | +| `--rdma-nic-allowlist LIST` | unset | Explicit training-NIC selector in `NCCL_IB_HCA` syntax (`device[:port],...`, `^...` denylist, `=dev` exact-match). Wins over `NCCL_IB_HCA` env. When neither this flag nor the env is set, the collector auto-excludes ports whose `phys_state` is `Disabled` or `Sleep`. | +| `--ulimit-l-min-gb GB` | 32 | RLIMIT_MEMLOCK threshold (0 disables). | +| `--shm-min-gb GB` | 8 | `/dev/shm` size threshold (0 disables). | +| `--rocm-smi-timeout-sec SEC` | 5.0 | Hard timeout for the `rocm-smi --version` self-latency canary; hitting it is a node FAIL (driver likely wedging). | +| `--hbm-busy-threshold-gib GiB` | 2.0 | FAIL the node if any GPU has at least this many GiB of HBM in use BEFORE smoke touches the device (i.e. someone else is holding it). Boundary is inclusive. | +| `--allow-foreign-procs` | off | Do NOT FAIL the node when foreign processes are found holding a GPU. They will still be reported. | +| `--allowed-procs LIST` | `gpuagent,rocm-smi-daemon,amd-smi,dcgm-exporter` | Comma-separated process names that are OK to find holding the GPU. Set to `""` to disable the whitelist. | +| `--gpu-activity-warn-pct PCT` | 20.0 | Warn (does NOT fail) if amd-smi reports any GPU's `gfx_activity_pct` above this when smoke starts. | +| `--require-tools LIST` | `""` (warn-only) | Comma-separated CLI tool names that MUST be in PATH (`amd-smi`, `rocm-smi`, `lsof`); anything missing becomes a hard node FAIL. | +| `--no-clean-dump-path` | off | Do NOT auto-wipe stale per-node JSONs / aggregator outputs from `--dump-path` on rank 0 at startup. Default behavior is to clean so re-runs on a different (smaller) nodelist don't inherit ghost PASS verdicts from removed nodes. | + +### `aggregate` subcommand + +| Flag | Default | Purpose | +|---|---|---| +| `--dump-path` | `output/preflight` | Same as `run`. | +| `--expected-nodes N` | none | If fewer JSONs land within `--wait-timeout-sec`, missing nodes are added as FAIL placeholders. | +| `--wait-timeout-sec` | 60 | Polling timeout. | +| `--rocm-smi-warn-sec SEC` | 1.0 | Flag (warn-only) any node where `rocm-smi --version` took longer than this. | +| `--clock-skew-warn-sec SEC` | 30.0 | Warn when wall-clock spread across nodes exceeds this many seconds. Includes srun launch jitter so the default is loose. | +| `--hbm-busy-threshold-gib GiB` | 2.0 | Mirrors the `run`-side default; used to label the **GPU pre-touch HBM usage outliers** section. | +| `--gpu-activity-warn-pct PCT` | 20.0 | Mirrors the `run`-side default; used to label the **GPU compute-activity outliers** section. | +| `--expected-nodelist-file FILE` | none | One short hostname per line. Missing nodes get their **real short hostname** in the report and `failing_nodes.txt` (instead of `` placeholders). The primus-cli wrapper auto-populates this from `scontrol show hostnames "$SLURM_JOB_NODELIST"` under SLURM. | + +### Launcher-level knobs (`primus-cli direct`) + +These are consumed by `primus-cli-direct.sh` **before** the `--` separator (not forwarded to the `node_smoke` Python tool): + +| Flag | Purpose | +|---|---| +| `--silent` | Back-pocket knob: redirect launcher + tool stdout to `/dev/null`. Launcher errors (`LOG_ERROR` / `LOG_WARN` on stderr) and the log file are preserved. Exit code propagated. | +| `--debug` | Verbose launcher logging. | +| `--dry-run`| Show the resolved command without executing. | +| `--env KEY=VALUE` | Inject an env var into the Python process. | + +### Rare advanced control (run-only / aggregate-only / no-aggregate) + +The primus-cli `node_smoke` subcommand always runs `_cmd_run` on every rank followed by rank-0 `_cmd_aggregate`. That is what users want ~100% of the time. For the rare cases where you need just one phase (e.g. re-aggregate yesterday's JSONs without re-running per-node, or smoke a single node without producing a cluster report), reach for the standalone CLI directly: + +```bash +# Per-node only, no aggregator (useful when scheduling phases separately): +python -m primus.tools.preflight.node_smoke run --tier2-perf + +# Aggregate only (read existing /smoke/*.json, produce cluster report): +python -m primus.tools.preflight.node_smoke aggregate \ + --dump-path output/preflight --expected-nodes 6 --wait-timeout-sec 5 +``` + +## Comparison with the full `preflight` + +| Aspect | `node_smoke` | full `preflight` | +|---|---|---| +| Rendezvous | None — every node independent | Global `torch.distributed` | +| Wall clock | ~50–60 s for 6 nodes (Tier 1+2) | Minutes; scales with N for inter-node tests | +| GEMM threshold | Hard threshold per GPU | Reports per-GPU numbers, no auto-fail | +| HBM bandwidth | Yes (D2D `copy_`) | Not measured | +| Inter-node all-reduce/all-to-all | Not tested (intentionally) | Yes | +| Drift detection | Yes (versions, NIC firmware, port count) | No | +| Host limits / RDMA roll-call | Yes (hard fail) | Reported via `collect_*_info` only | +| Output format | Per-node JSON + cluster md + SLURM-ready txt | Markdown + PDF | + +Use `node_smoke` to **screen** a cluster fast and exclude bad nodes. Use the full `preflight` when you want **deep cross-node measurements** (inter-node bandwidth matrix, ring-P2P, etc.). + +--- + +## Implementation history + +Captured here so future contributors understand *why* the design looks the way it does. + +### 1. Configurable preflight (predecessor work) + +Before `node_smoke` existed, the goal was simply to make the full `preflight` perf phase configurable: which tests to run, which message sizes, which subgroup sizes. Outcome (committed before `node_smoke`): + +- `--tests gemm,intra-allreduce,inter-allreduce,...` to select tests +- `--comm-sizes-mb 2,8,64,1024`, `--intra-comm-sizes-mb`, `--inter-comm-sizes-mb`, `--ring-p2p-sizes-mb` +- `--intra-group-sizes 2,4,8`, `--inter-group-sizes 2,4,all` +- `--quick` preset (small warmup/iters, single message size) +- New flag-precedence rules: `--tests` / `--quick` imply `--perf-test`; mixing perf and info selectors warns and drops info; tuning knobs without perf intent are inert with a quieter warn +- Report improvements: Node/Rank columns compressed into ranges (e.g. `0-7`), a Node→Hostname legend at the top, "Leader hostname" column showing only the first host of each group + +This work is in `primus/tools/preflight/preflight_perf_test.py` and the comm modules. It set up the global accessors (`set_warmup` / `set_iteration` / `get_*`) that `node_smoke` later mirrored to keep iteration counts comparable. + +### 2. Why a separate node-local smoke test + +A user pointed out that for large jobs, what really matters is "which node has a problem", not which GPU. They proposed a per-node distributed-environment with simple checks (`set_device`, quick bandwidth/speed tests) that returns a success/fail flag per node, *before* the real training job opens its global rendezvous. This was the motivation for `node_smoke.py` — much faster, no dependency on a healthy cluster, and a stuck node can't take down its peers. + +### 3. Tiering decision + +Two tiers, picked interactively: + +- **Tier 1**: mandatory, fast (~5 s/GPU) — `set_device`, alloc, tiny GEMM, plus reused info collectors. The bar is "the GPU enumerated and runs ops". +- **Tier 2**: optional perf sanity (`--tier2 / --tier2-rccl`) — GEMM TFLOPS, HBM bandwidth, local 8-GPU RCCL. The bar is "the GPU is at expected steady-state performance". + +HBM bandwidth was clarified to mean device-to-device `torch.Tensor.copy_` of a 512 MB buffer, counting read + write, which gives ~70–80 % of the MI300X HBM3 roofline (~5300 GB/s) on healthy hardware. + +### 4. First scaled run + measurement-quality bug + +The first 6-node run produced: + +- GEMM 8192³ bf16: smoke median **724 TFLOPS**, full preflight median **765 TFLOPS** (~6 % gap) +- Local AR 64 MB / 8 GPU: smoke median **199 GB/s**, full preflight median **230 GB/s** (~12 % gap) + +Formula audit confirmed `2·S·(P-1)/P / t` and `2·N³/t` are identical to `intra_node_comm.py` and `square_gemm.py`. The systematic offset traced to **iteration counts being too low**. The original RCCL loop was `1 warmup + 1 timed iter` — basically a kernel-launch latency test, not a bandwidth test. Fixed by aligning to the preflight `--quick` preset: + +- GEMM: `warmup 3→5, iters 10→20` +- HBM: `warmup 5→10, iters 10→20` +- RCCL: `warmup 1→5, iters 1→20` + +Per-node runtime cost rounded to <1 s additional. Aggregator gained a "Tier 2 perf summary" section so per-node GEMM/HBM/RCCL outliers are visible without grepping JSONs. + +### 5. A + B + C (drift, NIC roll-call, host limits) + +Discussion of what the smoke test was *missing* led to six categories. A, B, C landed: + +- **A. Stack drift detection** — per-node `fingerprint` (kernel, ROCm, amdgpu, RCCL, torch, NIC firmware, HCA model) + aggregator-side cluster-majority-vs-outlier comparison. Catches "1 of N nodes on a different RCCL build" — a frequent cause of "job dies at minute 3". +- **B. NIC / RDMA roll-call** — sysfs-only inventory of `/sys/class/infiniband` (no `ibv_devinfo` dependency), per-port hard-fail rules for state ≠ ACTIVE, missing RoCE v2 GIDs, count mismatch (when `--expected-rdma-nics` is set). +- **C. Host limits** — `RLIMIT_MEMLOCK` (32 GiB default threshold), `/dev/shm` (8 GiB default threshold), plus collected-only NUMA / governor / kernel for drift detection. + +Verified with a synthetic two-node drift test (one real + one edited copy with mismatched RCCL, mismatched amdgpu, mismatched `rdma3` firmware, `rdma2:1` DOWN, and `memlock=64 MiB`): every section lit up correctly, `fail_reasons` were prefixed with `nic:` / `host_limits:` for traceability, exit code propagated. + +### 6. Aggregator crash on heterogeneous fingerprints + +An 18-node run on a different cluster crashed the aggregator with `TypeError: unhashable type: 'dict'`. Root cause: `_stack_drift_rows()` added a key to its scalar-comparison set whenever **any** node reported it as `None` (or a scalar), then iterated **all** nodes' values for that key into `Counter(...)`. On the failing cluster `nic_fw` was `None` on one node and a dict on others — the dict wasn't hashable. Fix: + +1. Only collect a key when at least one node reports it as a real scalar (drop the "None counts as scalar" path). +2. Defense-in-depth `isinstance(v, (str, int, float))` check inside the per-host loop. +3. Each report section wrapped in its own `try / except` so one section's bug can't truncate the rest of the report. +4. New "NIC port-count summary" section that always renders and lists nodes whose port count differs from the cluster majority (so partial-NIC degradation like 7-of-8 is visible without `--expected-rdma-nics`). + +### 7. Package split (refactor of the 4.5k-line monolith) + +`node_smoke.py` had grown to ~4500 lines with all collectors, the orchestrator, the per-GPU subprocess body, and the ~700-line aggregator markdown writer in a single file. The refactor turned it into a Python sub-package (`primus/tools/preflight/node_smoke/`) with one module per Tier 1 sub-section (`collectors/`), the per-GPU subprocess body, the orchestrator, and the aggregator's data shapers (`aggregator/summarizers.py`) and Markdown writer (`aggregator/report.py`, with one `_write_
` helper per `##` heading). The single public entry point — `main` — is re-exported from `__init__.py`, so the existing `python -m primus.tools.preflight.node_smoke ...` invocation (used by the primus-cli wrapper and by `_spawn_per_gpu` for per-GPU subprocesses) keeps working unchanged. Behavior parity was checked by diffing the per-node JSON and `smoke_report.md` against a baseline (with a small allowlist for run-variant fields like PIDs, hardware cycle counters, and `available_gb`/`free_gb`/`cached_gb`); CLI help text, JSON schema, report section order, and exit-code semantics for `run` / `_per_gpu` / `aggregate` are byte-identical to pre-refactor. + +### 8. Short hostnames + naming nodes that never reported + +`failing_nodes.txt` held FQDNs (`socket.gethostname()` returned the FQDN on the failing cluster) — not pipeable into `srun --exclude=`. Nodes that never produced a JSON only showed up as `` placeholders, so operators couldn't act on them. + +Fix: + +1. Normalize `host = socket.gethostname().split(".", 1)[0]` in `_cmd_run` for both the JSON filename and the `host` field; logs use the short name too. +2. Aggregator defensively short-normalizes every loaded JSON, so legacy FQDN files produce SLURM-ready txt outputs without re-running the smoke step. +3. New `aggregate --expected-nodelist-file FILE` flag — missing nodes appended with their real short hostname (and a self-describing `expected hostname '' from --expected-nodelist-file` reason), written to `failing_nodes.txt` directly. +4. Wrapper resolves `SLURM_JOB_NODELIST` via `scontrol show hostnames` into `/expected_nodes.txt` and forwards it to the aggregator. Best-effort: silent fallback to count-only behaviour when `scontrol` is unavailable. + +This also makes "the node that SLURM marked as `task X: unknown`" visible in the report under its real hostname. + +--- + +## Future work + +These were proposed but not yet built. In rough priority order: + +### D. GPU low-level health (beyond "alloc + small GEMM works") + +Reveals hardware that *enumerates* but is degraded. Most map to one `rocm-smi` query or one sysfs read in the existing per-GPU subprocess. + +- GPU count == expected and `lspci -d 1002:` agrees +- PCIe link width/speed per GPU (`/sys/bus/pci/devices//current_link_{speed,width}`) — catches "GPU at Gen3 x8 because the slot needs reseating" +- XGMI link matrix between every GPU pair (reuse `primus/tools/preflight/gpu/gpu_topology.py`) +- HBM size per GPU matches expected +- ECC counters: uncorrectable as hard fail, correctable as info-only with a cluster-median baseline +- GPU clock state: flag any GPU stuck at idle GFX clock (stale-state symptom) +- Throttle reasons from `rocm-smi --showperflevel` (`power_throttle` / `thermal_throttle`) +- Power cap drift across the cluster + +Aggregator gets a "GPU-level drift" section that pinpoints `host:gpu` outliers, not just node-level. + +### E. Time / cluster sync + +- Wall-clock skew vs `node_rank=0`: each node writes its `time.time()` into its JSON; aggregator computes `max - min` and warns at > 1 s, fails at > 5 s +- Time-daemon health (`systemctl is-active chronyd / ntpd / systemd-timesyncd`) + +### F. Storage / runtime liveness (site-specific) + +- Shared-FS latency probe: 1 KB write + `stat` to a unique path, aggregator flags nodes far above the cluster median (Lustre/NFS hiccups) +- Shared-FS quota / free space +- DNS resolution sanity for peer hostnames +- `rocm-smi --version` self-latency (5 s timeout) — catches drivers that have started to wedge but haven't crashed yet (we've seen 30–60 s `rocm-smi` calls precede a full GPU hang by minutes) +- Container / image hash drift — if the launcher exports `CONTAINER_IMAGE_TAG`, fold it into the existing fingerprint + +### Bigger architectural item (lower priority) + +- If `NODE_RANK==0` itself fails to start, no aggregator runs anywhere. Possible mitigations: separate aggregator step submitted after the smoke step, or polling watchdog on the submit host. Out of scope for now — handled in practice by always passing `--time=` to `srun` so SLURM force-terminates a stuck job and you can re-run the aggregator alone with `--aggregate-only` + `--expected-nodelist-file`. diff --git a/docs/posttraining.md b/docs_deprecated/posttraining.md similarity index 100% rename from docs/posttraining.md rename to docs_deprecated/posttraining.md diff --git a/docs_deprecated/preflight-direct.md b/docs_deprecated/preflight-direct.md new file mode 100644 index 000000000..416ce4457 --- /dev/null +++ b/docs_deprecated/preflight-direct.md @@ -0,0 +1,794 @@ +# Run Preflight Without a Container + +> ⚠ **Run the [node-smoke test](./node-smoke-test-instruction.md) first.** `preflight` opens a global `torch.distributed` rendezvous, so a single sick node (wedged driver, leaked rank holding HBM, partial NIC enumeration, time-sync drift, etc.) can stall the whole job for up to `--dist-timeout-sec` seconds — long before any cross-node bandwidth number is produced. The node-smoke test catches those exact failure modes *without* a rendezvous in ~30–60 s and emits a SLURM-ready `failing_nodes.txt` you can pipe straight into `srun --exclude=`. Treat node-smoke as a hard prerequisite; only run `preflight` on the nodes node-smoke marked PASS. See [§0 "Which test should I run?"](#0-which-test-should-i-run) for the side-by-side comparison and the recommended 3-step workflow. + +This guide explains how to run Primus's `[preflight](./preflight.md)` cluster-diagnostic tool **directly on the host** (no Docker / Podman), via the standard Primus launcher. + +**Git clone the Primus repository to a shared filesystem that all nodes can read.** + +```bash +git clone --recurse-submodules https://github.com/AMD-AIG-AIMA/Primus.git +cd Primus +git checkout dev/preflight-direct-test +``` + +**Recommended (through the primus-cli SLURM wrapper):** + +``` +runner/primus-cli slurm srun -N --ntasks-per-node=1 -- direct -- preflight [PREFLIGHT_ARGS...] +``` + +**Equivalent (bare srun, useful when composing with custom srun flags):** + +``` +srun -N --ntasks-per-node=1 runner/primus-cli direct -- preflight [PREFLIGHT_ARGS...] +``` + +Both forms produce the **same workload** on the same ranks. The wrapper form is recommended because it auto-resolves `MASTER_ADDR` / `MASTER_PORT` / `NNODES` / `NODE_RANK` / `GPUS_PER_NODE` once on the launching node and passes them to every rank via `--env`, applies any `slurm.`* config defaults (partition / time / etc.) from your YAML, and is the same pattern used for `train` / `benchmark` / `node_smoke`. See [§ Wrapper vs. bare-srun](#wrapper-vs-bare-srun) below for the exact precedence / caveats. + +`primus-cli direct` activates an optional Python virtualenv (`VENV_ACTIVATE`), auto-derives the distributed environment variables (`NNODES`, `NODE_RANK`, `MASTER_ADDR`, `MASTER_PORT`, `GPUS_PER_NODE`) from `SLURM_*` when running inside a SLURM allocation, and then launches the `preflight` Python subcommand via `torchrun` (one worker per GPU). It is the recommended entry point when: + +- You're running on a SLURM cluster but cannot (or don't want to) use the container-based path. +- Your nodes share a Python virtual environment on a network-mounted filesystem. +- You want a single-node sanity check with no extra configuration. + +--- + +## 0. Which test should I run? + +Primus ships **two** complementary cluster screens. Pick the right one — and ideally run them in this order. + + +| Aspect | `node-smoke` (start here) | `preflight` (this doc) | +| ----------------- | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------- | +| Purpose | "Which nodes are healthy enough to run anything?" | "What is the actual cross-node performance on the surviving nodes?" | +| Rendezvous | None — every node independent | Global `torch.distributed` rendezvous | +| Wall clock | ~30–60 s for 6 nodes (Tier 1+2) | A few minutes; scales with N for inter-node tests | +| Granularity | Per-node PASS/FAIL | Per-rank perf measurements | +| Safety | A stuck node cannot wedge its peers | A single hung NIC can stall the whole rendezvous | +| Output | Per-node JSON + cluster md + SLURM-ready `passing_nodes.txt` / `failing_nodes.txt` | Markdown + PDF perf report | +| Entry point | `primus-cli direct -- node_smoke` | `primus-cli direct -- preflight` (this doc) | +| Quick-start guide | `[node-smoke-test-instruction.md](./node-smoke-test-instruction.md)` | This doc, §3+ | + + +### Recommended workflow + +> **Before running any of the commands below, complete the one-time setup:** +> +> 1. **Python virtualenv** on a shared filesystem — see [§2 Set up the Python virtual environment](#2-set-up-the-python-virtual-environment), then point the launcher at it via `export VENV_ACTIVATE=...` (details in [§2 → Tell the launcher where the venv is](#tell-the-launcher-where-the-venv-is)). +> 2. **NCCL / fabric environment variables** — usually the defaults in `base_env.sh` are fine, but multi-NIC nodes may need `NCCL_IB_HCA` / `NCCL_IB_GID_INDEX` / `NCCL_SOCKET_IFNAME` overrides. See [§4 Cluster-specific NCCL configuration](#4-cluster-specific-nccl-configuration) for known-good values per fabric (Broadcom, Pensando Pollara/AINIC). + +Through the `primus-cli slurm srun -- direct --` wrapper (recommended): + +```bash +# 1) Prune broken nodes with node-smoke (fast, no rendezvous). +runner/primus-cli slurm srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + -- direct -- node_smoke --tier2-perf + +# 2) Re-allocate excluding the bad nodes, and run preflight --quick +# for a fast cross-node sanity check. +runner/primus-cli slurm srun -N -c 128 --gpus-per-node=8 \ + --ntasks-per-node=1 \ + --exclude=$(paste -sd, output/preflight/failing_nodes.txt) \ + -- direct -- preflight --quick + +# 3) Optional: full preflight on the same set if --quick numbers +# look off, or if you want the full bandwidth matrix. +runner/primus-cli slurm srun -N -c 128 --gpus-per-node=8 \ + --ntasks-per-node=1 \ + --exclude=$(paste -sd, output/preflight/failing_nodes.txt) \ + -- direct -- preflight +``` + +Equivalent with bare `srun` (works identically; useful when scripting around custom srun flags that don't compose with the wrapper): + +```bash +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct -- node_smoke --tier2-perf + +srun -N -c 128 --gpus-per-node=8 --ntasks-per-node=1 \ + --exclude=$(paste -sd, output/preflight/failing_nodes.txt) \ + runner/primus-cli direct -- preflight --quick + +srun -N -c 128 --gpus-per-node=8 --ntasks-per-node=1 \ + --exclude=$(paste -sd, output/preflight/failing_nodes.txt) \ + runner/primus-cli direct -- preflight +``` + +Why this ordering matters: + +- A single broken node can stall a `torch.distributed.init_process_group()` for `--dist-timeout-sec` seconds (default 120), so feeding a known-good list to preflight is much faster. +- `node-smoke` catches things preflight cannot — leaked / foreign processes, wedged drivers, partial NIC enumeration, time-sync drift, RDMA roll-call issues — that produce *misleading* preflight failures. +- `preflight --quick` adds the cross-node bandwidth signal that `node-smoke` deliberately does not measure. + +--- + +## 1. Prerequisites + +- A working AMD ROCm installation on every node. +- Network reachability between nodes (Ethernet for bootstrap, RDMA / InfiniBand recommended for perf tests). +- A Python ≥ 3.10 virtual environment **on a shared filesystem** that all nodes can read (the same path is sourced on every node). +- The Primus repository checked out somewhere readable from every node. + +--- + +## 2. Set up the Python virtual environment + +The environment must live on a path visible from every node (e.g. NFS-mounted home, Lustre, or any shared filesystem). All nodes will `source` the same activation script. + +You can use any tool you like; `uv` is the fastest. Either of the following works. + +### What you actually need to install + +The `preflight` and `node-smoke` tools deliberately use **only a small subset** of Primus's full dependency tree. You do **not** need to install the entire `requirements.txt` — that pulls in trainer / dataset / experiment-tracking packages that neither tool ever imports. + + +| Package | Required for | Skip when | +| -------------------- | ------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | +| `torch` (ROCm build) | Both tools — perf measurements (`torch.matmul`, `torch.distributed`, `torch.cuda.`*). | Never (mandatory). | +| `markdown2` | `preflight` PDF report only (Markdown → HTML). | You always pass `--disable-pdf`, or you only run `node-smoke` (which never produces PDFs). | +| `weasyprint` | `preflight` PDF report only (HTML → PDF). | Same as above. | +| `matplotlib` | `preflight --plot` only (per-test bandwidth bar charts). | You don't pass `--plot`. | + + +Everything else in the preflight / node-smoke code path is Python stdlib (`os`, `subprocess`, `socket`, `argparse`, `dataclasses`, `json`, `time`, ...) — no extra installs needed. + +### Option A — `uv` (recommended), minimal install + +```bash +mkdir -p ~/envs/preflight +cd ~/envs/preflight + +uv venv --python 3.12 +source .venv/bin/activate + +# 1) ROCm-built PyTorch (pin to your ROCm version; rocm7.1 shown here) +uv pip install torch torchvision --index-url https://download.pytorch.org/whl/rocm7.1 --no-cache-dir + +# 2) Optional: only if you want preflight PDF reports (omit to use --disable-pdf) +uv pip install markdown2 weasyprint + +# 3) Optional: only if you want preflight --plot bar charts +uv pip install matplotlib +``` + +### Option B — `python -m venv`, minimal install + +```bash +mkdir -p ~/envs/preflight +python3.12 -m venv ~/envs/preflight/.venv +source ~/envs/preflight/.venv/bin/activate + +pip install torch torchvision --index-url https://download.pytorch.org/whl/rocm7.1 --no-cache-dir +pip install markdown2 weasyprint # optional, for preflight PDFs +pip install matplotlib # optional, for preflight --plot +``` + +### Option C — full Primus runtime (only if you also want the rest of Primus) + +```bash +cd /path/to/Primus +uv pip install -r requirements.txt # or: pip install -r requirements.txt +``` + +This installs every Primus runtime dependency (trainer, dataset loaders, experiment trackers, ...). Use only if you're going to run more than just preflight / node-smoke from this environment. + +### Per-tool minimum install matrix + +If you want the absolute smallest footprint, install only what your intended invocations need: + + +| Invocation | `torch` | `markdown2` | `weasyprint` | `matplotlib` | +| ------------------------------------------------ | -------- | --------------------------------- | --------------------------------- | ------------ | +| `node-smoke` (any flags) | required | — | — | — | +| `preflight --host --gpu --network --disable-pdf` | required | — | — | — | +| `preflight --host --gpu --network` (with PDF) | required | required | required | — | +| `preflight --quick --disable-pdf` | required | — | — | — | +| `preflight --quick` (with PDF) | required | required | required | — | +| `preflight ... --plot` | required | required (unless `--disable-pdf`) | required (unless `--disable-pdf`) | required | + + +### Tell the launcher where the venv is + +`primus-cli direct` reads the `**VENV_ACTIVATE**` environment variable. When set, it sources the path before launching the Python process; when unset, it is a no-op (the container path, which uses the container's bundled Python, leaves this unset): + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate +``` + +`VENV_ACTIVATE` is the only optional environment variable specific to the direct flow. Everything else has a sensible default; distributed-env variables (`NNODES`, `NODE_RANK`, `MASTER_ADDR`, ...) are auto-derived from SLURM when not pre-exported. + +--- + +## 3. Run preflight + +### Single node (no SLURM) + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate + +# Info report only (fast) +runner/primus-cli direct -- preflight --host --gpu --network + +# Info + perf report +runner/primus-cli direct -- preflight + +# Perf report only +runner/primus-cli direct -- preflight --perf-test +``` + +When SLURM is not detected the script defaults to `NNODES=1`, `NODE_RANK=0`, `MASTER_ADDR=localhost`. Any of those can be overridden by exporting them before calling the script. + +### Multi-node without SLURM (parallel SSH) + +When no scheduler is available (bare-metal, cloud VMs, lab nodes), launch +`primus-cli direct` on each node yourself via SSH. The script works +identically — you just pre-export the distributed variables that SLURM +would normally provide. + +#### Requirements + +- All nodes share the same filesystem (or at least the same Primus checkout + venv path). +- Nodes can reach each other on a **data-plane** network interface (not the management NIC). +- SSH key-based access to each node from the launching host. + +#### Required environment variables + + +| Variable | Description | +| -------------------- | ---------------------------------------------------------------------- | +| `NNODES` | Total number of nodes | +| `NODE_RANK` | This node's rank (`0` through `NNODES-1`) | +| `MASTER_ADDR` | IP of rank-0 node **on the data-plane interface** | +| `MASTER_PORT` | Rendezvous port (default `1234`; increment between concurrent runs) | +| `GPUS_PER_NODE` | GPUs per node (default `8`) | +| `NCCL_SOCKET_IFNAME` | Data-plane NIC name (e.g. `enp159s0np0`) — **critical for multi-node** | +| `GLOO_SOCKET_IFNAME` | Same as `NCCL_SOCKET_IFNAME` | +| `VENV_ACTIVATE` | Path to virtualenv `activate` script | + + +> **Warning**: `NCCL_SOCKET_IFNAME` auto-detection often picks a management interface +> (e.g. `enp28s0np0`, `eno8303`) instead of the high-bandwidth data NIC. For multi-node +> runs this causes `init_process_group` to hang or NCCL to fail silently. Always set it +> explicitly. + +#### Identifying the correct data-plane interface + +```bash +# On any node, find the interface whose IP matches the MASTER_ADDR subnet: +ip -4 addr show | grep "10.245.134" +# → enp159s0np0 inet 10.245.134.129/24 + +# Or check which interface routes to the master: +ip route get 10.245.134.129 | awk '{print $5; exit}' +### Multi-node via SLURM + +`primus-cli direct` auto-detects a SLURM allocation (via `SLURM_JOB_ID`) and derives all distributed variables from `SLURM_*` automatically. **Pre-exported values always win**, so the same launcher script also works inside the `primus-cli slurm srun ... -- direct -- ...` chain (where `slurm-entry` has already set these via `--env`): + +| Variable | Resolved as | +| --------------- | -------------------------------------------------------------------- | +| `NNODES` | `NNODES` → `SLURM_NNODES` → `SLURM_JOB_NUM_NODES` → `1` | +| `NODE_RANK` | `NODE_RANK` → `SLURM_NODEID` → `SLURM_PROCID` → `0` | +| `MASTER_ADDR` | `MASTER_ADDR` (if not empty / not `localhost`) → first hostname from `scontrol show hostnames "$SLURM_NODELIST"` | +| `MASTER_PORT` | `MASTER_PORT` → `1234` | +| `GPUS_PER_NODE` | `GPUS_PER_NODE` → `8` | + +Run it as a single task per node (the script invokes `torchrun` internally, which spawns one worker per GPU): + +> **Verify NCCL / network env first.** The script sets sensible `NCCL_`* defaults via `base_env.sh`, but auto-detection can pick the wrong device on multi-NIC nodes. Always confirm `NCCL_IB_HCA`, `NCCL_IB_GID_INDEX`, `NCCL_SOCKET_IFNAME`, and `GLOO_SOCKET_IFNAME` (set to the same value as `NCCL_SOCKET_IFNAME`) are correct for your fabric, and `export` overrides before running. See [§4](#4-cluster-specific-nccl-configuration) for cluster-specific values. + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate + +# export NCCL_IB_HCA=rdma1,rdma2,rdma3,rdma4,rdma5,rdma6,rdma7 +# export NCCL_IB_GID_INDEX=3 +# export NCCL_SOCKET_IFNAME=eno0 +# export GLOO_SOCKET_IFNAME=eno0 + +# Recommended: through the primus-cli SLURM wrapper. +runner/primus-cli slurm srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 \ + --nodelist --ntasks-per-node=1 \ + -- direct -- preflight --perf-test + +# Or, equivalently, with bare srun: +srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --nodelist \ + --ntasks-per-node=1 \ + runner/primus-cli direct -- preflight --perf-test +``` + + + +#### Wrapper vs. bare-srun + +Both forms target the **same** `primus-cli-direct.sh` launcher and produce identical workloads. The difference is only in how the SLURM context is constructed: + + +| Aspect | `primus-cli slurm srun -- direct --` (recommended) | Bare `srun ... primus-cli direct --` | +| ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| `MASTER_ADDR` resolution | Resolved **once** on the launching node via `scontrol show hostnames "$SLURM_NODELIST" | head -n1`, then propagated to every rank via `--env MASTER_ADDR=...`. | Each rank re-derives it inside `primus-cli-direct.sh` STEP 4.7 from `SLURM_`* (same result, more `scontrol` calls). | +| `NNODES` / `NODE_RANK` / `GPUS_PER_NODE` | Set explicitly by `slurm-entry.sh` via `--env`. | Derived from `SLURM_NNODES` / `SLURM_NODEID` / `SLURM_PROCID` inside `direct.sh`. | +| `slurm.*` config defaults | Honored (partition, time, ntasks-per-node, etc. from the active YAML). | Not consulted — you pass every flag explicitly to `srun`. | +| Default wall-time | `-t 4:00:00` is auto-added if you don't pass `--time`. | None — `srun` uses the cluster default (may reject the job). | +| `direct` keyword | **Required**: `primus-cli slurm srun ... -- direct -- `. Without `direct`, the wrapper routes through the **container** path. | N/A — there's only one path. | +| `--ntasks-per-node=1` | **Not auto-added**. Pass it on the CLI (before the first `--`) or set it in the `slurm.`* config. | **Not auto-added**. Pass it as an `srun` flag. | +| Best for | Production / repeatable runs. Same pattern as `train` / `benchmark` / `node_smoke`. | Ad-hoc runs where you want to compose with arbitrary `srun` flags (`--nodelist=$(...)`, `--exclude=...` from a runtime file, etc.). | + + +For the rest of this doc the examples use bare `srun` for brevity, but every example also works with the wrapper form by substituting `srun runner/primus-cli direct --` → `runner/primus-cli slurm srun -- direct --`. + +### Key `srun` flags + + +| Flag | Why it's necessary | +| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `-c 128` | Allocate all CPU cores per task. Without this, SLURM may default to 1 core, which starves the RCCL network proxy threads and can cause >30× slowdown on perf tests. Set this to your node's core count. | +| `--gpus-per-node=8` | Grants GPU device access (`/dev/kfd`, `/dev/dri`). Required for non-container execution. | +| `--ntasks-per-node=1` | One launcher invocation per node; `primus-cli direct` then spawns 8 workers per node via `torchrun`. | +| `-t 00:45:00` | Wall-clock limit. Full perf tests on 8N usually finish well under 10 min. | + + +> Tip — check core count: `srun -N 1 --gpus-per-node=8 bash -c 'nproc'` + +--- + +## 4. Cluster-specific NCCL configuration + +`primus-cli direct` sources `runner/helpers/envs/base_env.sh`, which sets sensible defaults for `NCCL_`* and auto-detects `NCCL_IB_HCA` / `NCCL_SOCKET_IFNAME`. Pre-exported values from your shell take precedence, so the standard pattern is: + +```bash +export VAR=value +runner/primus-cli direct -- preflight ... +``` + +### Broadcom NICs (no AINIC) + +Most clusters fall here. The defaults from `base_env.sh` are usually fine, but the two values most commonly worth overriding are: + +```bash +export NCCL_CROSS_NIC=1 # default in base_env.sh is 0 +export NCCL_PXN_DISABLE=0 # default in base_env.sh is 1 + +srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --nodelist \ + --ntasks-per-node=1 \ + runner/primus-cli direct -- preflight --perf-test +``` + +### Pensando Pollara (AINIC) RDMA + +```bash +export USING_AINIC=1 +export NCCL_IB_GID_INDEX=1 # AINIC uses index 1 (default in base_env.sh is 3) +export NCCL_PXN_DISABLE=0 + +srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --nodelist \ + --ntasks-per-node=1 \ + runner/primus-cli direct -- preflight +``` + +> `primus-cli direct` *does* accept `--env KEY=VALUE` on its own command line (placed before `--`), in addition to the conventional `export`/`srun --export=` approaches. + +--- + +## 5. Launcher flags vs. preflight flags + +Anything you place **after** the `--` separator is forwarded verbatim to the `preflight` Python tool. The launcher (`primus-cli-direct.sh`) consumes a small set of flags **before** `--`. The one most users care about is `--silent`. + +### Launcher-only flags (before `--`) + + +| Flag | Effect | +| ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `--silent` | Back-pocket knob: redirect the launcher's and the Python tool's `stdout` to `/dev/null`. Launcher errors (`LOG_ERROR` / `LOG_WARN`, written to `stderr`) are preserved so real failures still surface; the log file under `logs/` captures everything. Exit code is propagated unchanged. **Not recommended** for normal use — you lose live progress; prefer the log file. | +| `--debug` | Verbose launcher logging (`PRIMUS_LOG_LEVEL=DEBUG`). Forwarded to the Python tool as `--debug` too. | +| `--dry-run` | Print the resolved configuration and final `torchrun` / `python3` command without executing. | +| `--single` | Force `python3` instead of `torchrun`. `node_smoke` auto-selects this; for `preflight` you usually want the default (`torchrun`). | +| `--env KEY=VALUE` | Inject an env var into the Python process (in addition to anything `export`-ed in the shell). | +| `--log_file PATH` | Redirect the captured tee log to a specific path (default: `logs/log_.txt`). | + + +See `runner/primus-cli direct --help` for the full set. + +### Forwarded `preflight` flags (after `--`, most common) + +See [Preflight](./preflight.md) for the full list. The most common are: + +- Mode selection: `--host`, `--gpu`, `--network`, `--perf-test`, `--tests`, `--quick` +- Test tuning: `--comm-sizes-mb`, `--intra-comm-sizes-mb`, `--inter-comm-sizes-mb`, `--intra-group-sizes`, `--inter-group-sizes`, `--ring-p2p-sizes-mb` +- Reporting: `--dump-path`, `--report-file-name`, `--disable-pdf`, `--plot` +- Reliability: `--comm-cleanup-delay-sec`, `--dist-timeout-sec` + +If you do not pass `--report-file-name`, `preflight` auto-generates a unique one of the form: + +``` +preflight-${NNODES}N-YYYYMMDD-HHMMSS +``` + +This guarantees that each run lands in its own files and prevents stale leftovers from earlier runs from being mistaken for fresh output. The auto-name logic now lives in the Python tool itself, so every call site (host `srun ... primus-cli direct`, `primus-cli slurm ... -- direct`, `primus-cli slurm ... -- container`) gets the same fresh name. + +### Examples + +The examples below all assume one of the two equivalent shell-prefix conventions. Pick whichever matches your habits — every example block in this section works with either definition: + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate + +# Recommended: through the primus-cli SLURM wrapper. Auto-resolves +# MASTER_ADDR/NNODES/NODE_RANK once on the launching node and propagates +# them via --env; honors slurm.* config defaults. +SRUN="runner/primus-cli slurm srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --ntasks-per-node=1 --nodelist --" +# Then in every example below, replace `$SRUN runner/primus-cli direct --` +# with just `$SRUN direct --`. (The wrapper expects the entry-mode keyword +# `direct` as the first token after the inner `--`.) + +# Equivalent: bare srun. NNODES/NODE_RANK/MASTER_ADDR get derived inside +# primus-cli-direct.sh's STEP 4.7 directly from SLURM_*; same net effect. +SRUN="srun -t 00:45:00 -N 4 -c 128 --gpus-per-node=8 --ntasks-per-node=1 --nodelist " +``` + +The examples in this section use the **bare-srun** form below for brevity (since `$SRUN runner/primus-cli direct -- preflight` reads naturally as one command line). To use the wrapper form instead, substitute `$SRUN runner/primus-cli direct --` → `$SRUN direct --` after exporting `SRUN` to the wrapper variant. + +#### A. Mode selection + +```bash +# Default: info report + every perf test +$SRUN runner/primus-cli direct -- preflight + +# Info-only (fast, no torch.distributed rendezvous) +$SRUN runner/primus-cli direct -- preflight --host --gpu --network --disable-pdf + +# Perf-only, every test +$SRUN runner/primus-cli direct -- preflight --perf-test + +# Fast pre-launch sanity preset (gemm + intra-AR + inter-AR @ 64,1024 MB, +# full intra-node group, full N-node inter group, low warmup/iter) +$SRUN runner/primus-cli direct -- preflight --quick +``` + +> **Note**: Mixing perf-mode flags (`--perf-test` / `--tests` / `--quick`) with info selectors (`--host` / `--gpu` / `--network`) makes preflight drop the info selectors with a `WARN`. Run two invocations if you want both reports. + +#### B. Test selection (`--tests`) + +```bash +# Only GEMM +$SRUN runner/primus-cli direct -- preflight --tests gemm + +# Only the inter-node bandwidth tests +$SRUN runner/primus-cli direct -- preflight --tests inter-allreduce,inter-alltoall + +# Only the inter-node ring P2P +$SRUN runner/primus-cli direct -- preflight --tests inter-ring-p2p + +# Combine: GEMM + inter-AR with overridden sizes/groups +$SRUN runner/primus-cli direct -- preflight \ + --tests gemm,inter-allreduce \ + --comm-sizes-mb 64,1024 \ + --inter-group-sizes all +``` + +Valid `--tests` tokens: `gemm`, `intra-allreduce`, `intra-alltoall`, `inter-allreduce`, `inter-alltoall`, `inter-p2p`, `inter-ring-p2p`, `all`. Unknown tokens fail fast (before NCCL init). + +#### C. Message sizes + +```bash +# One CSV applied to both intra and inter +$SRUN runner/primus-cli direct -- preflight --tests intra-allreduce,inter-allreduce \ + --comm-sizes-mb 8,128 + +# Different sizes for intra vs inter (override wins over --comm-sizes-mb) +$SRUN runner/primus-cli direct -- preflight --tests intra-allreduce,inter-allreduce \ + --comm-sizes-mb 8,128 --intra-comm-sizes-mb 4,32 + +# Inter-only override (also covers inter-p2p when enabled) +$SRUN runner/primus-cli direct -- preflight --tests inter-allreduce,inter-p2p \ + --comm-sizes-mb 8,128 --inter-comm-sizes-mb 16,512 +``` + +#### D. Group sizes + +```bash +# Custom intra-node group sizes (each must divide LOCAL_WORLD_SIZE) +$SRUN runner/primus-cli direct -- preflight \ + --tests intra-allreduce \ + --intra-group-sizes 4,8 + +# Custom inter-node groups: 2-node pairs and the full N-node group +$SRUN runner/primus-cli direct -- preflight \ + --tests inter-allreduce \ + --inter-group-sizes 2,all +``` + +> Note: for `inter-alltoall` only, every requested per-group node count is internally clamped to **16** (real-world MoE training rarely dispatches across more nodes; see `[preflight.md` §5.2](./preflight.md#52-group-sizes) for the rationale). The other inter-node tests use the requested sizes unchanged. So on a 128-node cluster, `--tests inter-alltoall --inter-group-sizes all` actually runs at 16-node sub-groups, while `--tests inter-allreduce --inter-group-sizes all` runs at 128 nodes as written. + +#### E. Ring P2P sizes + +```bash +$SRUN runner/primus-cli direct -- preflight \ + --tests inter-ring-p2p \ + --ring-p2p-sizes-mb 5,20,80 +``` + +#### F. Plotting + +```bash +# Generate per-test bandwidth bar charts under //*.png +$SRUN runner/primus-cli direct -- preflight \ + --tests intra-allreduce,inter-allreduce --plot +``` + +#### G. Reliability knobs + +```bash +# Bump the per-phase cleanup delay. Default 2.0 is sufficient at every +# cluster size for the comm shapes preflight exercises (inter-alltoall +# is internally capped at 16 nodes; see preflight.md §5.2). Only bump +# this on very flaky networks or unusual kernel TIME_WAIT settings. +$SRUN runner/primus-cli direct -- preflight --quick --comm-cleanup-delay-sec 5 + +# Fail fast if torch.distributed rendezvous can't complete in 30s +$SRUN runner/primus-cli direct -- preflight --perf-test --dist-timeout-sec 30 +``` + +> Operating clusters at ≥ 128 nodes? See `[preflight.md` §7](./preflight.md#7-running-on-very-large-clusters--64-nodes) for the recommended OS-level tunings (`tcp_tw_reuse`, wider `ip_local_port_range`) and per-test invocation patterns. With the §5.2 inter-alltoall cap in place, a default invocation is safe at every cluster size; the §7.2 sysctls remain best-practice for any RDMA workload. + +#### H. Reporting & output layout + +```bash +# Quick info-only check on 4 nodes, no PDF +$SRUN runner/primus-cli direct -- preflight --host --gpu --network --disable-pdf \ + --report-file-name info-4N + +# Perf test only, silenced (CI-friendly), explicit name. Note that --silent +# is consumed by primus-cli-direct.sh and must appear BEFORE the `--` +# separator; everything after `--` is forwarded to the preflight Python tool. +$SRUN runner/primus-cli direct --silent -- preflight --perf-test \ + --report-file-name nightly-4N-perf + +# Archive each run under its own directory +$SRUN runner/primus-cli direct -- preflight --quick \ + --dump-path /shared/preflight-archive/$(date +%Y%m%d-%H%M%S) +``` + +#### I. Backward-compat aliases + +These still work and are equivalent to flags above. Use them only when retrofitting older scripts. + +```bash +# Same as --host --gpu --network +$SRUN runner/primus-cli direct -- preflight --check-host --check-gpu --check-network + +# Same as --inter-group-sizes all AND drops inter-p2p +$SRUN runner/primus-cli direct -- preflight --perf-test --no-split-nodes-subgroup +``` + +#### J. Combined "production-ready" pre-launch screen + +```bash +# 1) Smoke first to prune broken nodes (note: --silent goes BEFORE `--`) +srun -N "$SLURM_NNODES" --ntasks-per-node=1 \ + runner/primus-cli direct --silent -- node_smoke --tier2-perf + +# 2) Quick perf sanity on the survivors +srun -N -c 128 --gpus-per-node=8 --ntasks-per-node=1 \ + --exclude=$(paste -sd, output/preflight/failing_nodes.txt) \ + runner/primus-cli direct --silent -- preflight --quick \ + --comm-cleanup-delay-sec 5 --dist-timeout-sec 60 \ + --report-file-name screen-$(date +%Y%m%d-%H%M%S) +``` + +--- + +## 6. Outputs + +Reports are written to `--dump-path` (default: `output/preflight/`), with the basename from `--report-file-name` and a `_perf` suffix for performance reports: + + +| File | Produced by | Notes | +| ----------------- | ----------------------------------------------- | ------------------------- | +| `.md` | `--host --gpu --network` (or default selection) | Info report | +| `.pdf` | same, unless `--disable-pdf` | Info report PDF | +| `_perf.md` | `--perf-test` | Perf report (GEMM + comm) | +| `_perf.pdf` | same, unless `--disable-pdf` | Perf report PDF | + + +Only **rank 0** writes the report. After preflight completes, the Python tool prints the absolute path of every report file it produced to stdout. Under `--silent` these prints go to `/dev/null` along with everything else (one of the trade-offs of using `--silent`); without `--silent` the announcement is visible live. Sample output: + +``` +[Primus:Preflight] Report: /home/.../Primus/output/preflight/preflight-4N-20260428-201925.md +[Primus:Preflight] Report: /home/.../Primus/output/preflight/preflight-4N-20260428-201925_perf.md +``` + +--- + +## 7. Environment variable reference + +Variables read by `primus-cli direct` itself: + + +| Variable | Required | Default | Purpose | +| --------------- | -------- | ---------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | +| `VENV_ACTIVATE` | no | — | Path to the venv `bin/activate` script. Unset = no-op (use system / container Python). Set + missing file = fail-fast. | +| `NNODES` | no | `1` (or auto-derived from `SLURM_NNODES` / `SLURM_JOB_NUM_NODES`) | Number of nodes. Pre-exported always wins. | +| `NODE_RANK` | no | `0` (or auto-derived from `SLURM_NODEID` / `SLURM_PROCID`) | This node's rank. Pre-exported always wins. | +| `GPUS_PER_NODE` | no | `8` | GPUs per node | +| `MASTER_ADDR` | no | `localhost` (or first host from `scontrol show hostnames "$SLURM_NODELIST"`) | Rendezvous host. Pre-exported always wins. | +| `MASTER_PORT` | no | `1234` | Rendezvous port | + + +Variables consumed downstream by `primus-cli direct` / `base_env.sh` (set them via `export`): + + +| Variable | Default in `base_env.sh` | When to override | +| -------------------- | ------------------------ | ------------------------------------------------- | +| `NCCL_SOCKET_IFNAME` | auto-detected | Force a specific Ethernet interface for bootstrap | +| `NCCL_IB_HCA` | auto-detected | Force specific RDMA HCAs | +| `NCCL_IB_GID_INDEX` | `3` | `1` on AINIC clusters | +| `NCCL_CROSS_NIC` | `0` | `1` for multi-rail IB fabrics | +| `NCCL_PXN_DISABLE` | `1` | `0` to enable PXN multi-hop NIC sharing | +| `USING_AINIC` | unset | `1` on Pensando Pollara clusters | +| `NCCL_DEBUG` | unset | `INFO` for verbose NCCL logging | + + +--- + +## 8. Troubleshooting + +### `[ERROR] [direct] VENV_ACTIVATE is set but file does not exist: ...` + +`VENV_ACTIVATE` was set in the environment but the path it points at doesn't exist on this node. This is a fail-fast guard to prevent a silent fallback to system Python (which usually has the wrong `torch` / no ROCm). Either fix the path: + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate +``` + +… or unset it to fall back to the container / system Python: + +```bash +unset VENV_ACTIVATE +``` + +If the path looks right but the file still appears missing, confirm the venv lives on a filesystem visible from the node SLURM scheduled you onto. + +### `[Primus:Preflight] FAIL: No GPUs detected` + +The Python process inside the venv can't find ROCm. Diagnose with: + +```bash +srun --nodes=1 --nodelist= bash -c ' +echo "=== PATH ==="; echo $PATH +echo "=== LD_LIBRARY_PATH ==="; echo $LD_LIBRARY_PATH +echo "=== rocm-smi ==="; rocm-smi --showid 2>&1 +echo "=== Python torch check ===" +source ~/envs/preflight/.venv/bin/activate +python3 -c "import torch; print(\"hip:\", torch.version.hip); print(\"available:\", torch.cuda.is_available()); print(\"count:\", torch.cuda.device_count())" +' +``` + +If `LD_LIBRARY_PATH` is empty, set it explicitly: + +```bash +export LD_LIBRARY_PATH=/opt/rocm/lib:${LD_LIBRARY_PATH:-} +``` + +### Report announcement points at stale files + +This shouldn't happen with the current Python tool — the auto-generated unique report name (`preflight-${NNODES}N-`) ensures every run gets a fresh path. If you explicitly pass `--report-file-name X`, you're responsible for choosing a name that doesn't collide with prior runs. + +### Slow perf tests (~30× expected) + +Almost always a symptom of insufficient CPU cores. Pass `-c ` to `srun` so RCCL's network proxy threads have CPU to spawn on. Verify with `srun -N 1 --gpus-per-node=8 bash -c 'nproc'`. + +### Using `conda` instead of venv + +`primus-cli direct` does `source "$VENV_ACTIVATE"`, which works for venv/uv but not directly for conda. Two options: + +1. Create a venv inside the conda env and point `VENV_ACTIVATE` at that venv's activate script. +2. Write a small shim activate script (e.g. `~/envs/conda-shim.sh`) that activates conda and the desired env, then point `VENV_ACTIVATE` at it: + ```bash + # ~/envs/conda-shim.sh + source "$HOME/miniconda3/etc/profile.d/conda.sh" + conda activate + ``` + +### "Address already in use" during perf tests + +This error occurs when peak simultaneous ESTAB sockets per node during an `ncclCommInit` exhausts the kernel ephemeral-port pool, so the next outgoing `bind()` walks the entire range without finding an allocatable port. (Despite the name and the `TIME_WAIT` framing in the kernel docs, accumulated `TIME_WAIT` count does *not* gate this for NCCL inter-node OOB — see `[preflight.md` §7.1](./preflight.md#71-why-address-already-in-use-used-to-surface-at-scale) for the mechanism and the empirical evidence.) + +Preflight has two complementary defenses: + +1. The **inter-node alltoall sub-group is internally capped at 16 nodes** (see `[preflight.md` §5.2](./preflight.md#52-group-sizes)) — the only test that, at large scale, can push peak ESTAB anywhere near the per-node ephemeral pool. The cap eliminates this failure mode by construction. +2. A **global barrier + `--comm-cleanup-delay-sec` sleep** (default 2 s) is inserted after every comm destroy, primarily for cross-rank synchronization across the destroy → setup transition. + +If you still see `Address already in use` (e.g. on a network with an unusually narrow ephemeral-port range), the directly relevant **OS-level tuning** is widening that range — best-practice for any RDMA host: + +```bash +# Widen the ephemeral port range from ~28k to ~64k. This is the only +# OS knob that directly addresses the binding constraint. +sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535" + +# General hygiene for hosts running mixed RDMA + repeated outgoing +# TCP workloads (NCCL inter-node OOB by itself doesn't benefit from +# this -- see preflight.md §7.2 for why). +sudo sysctl -w net.ipv4.tcp_tw_reuse=1 +``` + +As a fallback, raise the per-phase delay: + +```bash +# Bump the per-phase delay (default 2 s) on a particularly stressed +# network. Rarely needed in practice with the §5.2 alltoall cap. +runner/primus-cli direct -- preflight --comm-cleanup-delay-sec 5 +``` + +See `[preflight.md` §7](./preflight.md#7-running-on-very-large-clusters--64-nodes) for the full explanation, persistence, and recommended large-cluster invocation patterns (split tests into separate runs, etc.). + +If the error occurs at `init_process_group` (before tests even start), it typically means a previous job left port 29500 in `TIME_WAIT`. Either wait ~60 s or use a different port: + +```bash +export MASTER_PORT=29501 +``` + +### Capturing full output + +The launcher already writes a complete log to `logs/log_.txt` (configurable via `--log_file PATH`), even under `--silent`. If you also want a copy at the call site, redirect there: + +```bash +srun ... runner/primus-cli direct -- preflight --perf-test \ + 2>&1 | tee preflight-$(date +%Y%m%d-%H%M%S).log +``` + +--- + +## 9. Automated node bisection (finding the bad node in an NCCL hang) + +When a cluster-wide preflight run hangs or fails, use +`[tools/preflight_bisect/bisect.py](../tools/preflight_bisect/bisect.py)` to +run `preflight --perf-test` on smaller Slurm node subsets until suspect nodes +are isolated. + +### Prerequisites + +1. Working non-container preflight setup from the sections above, with + `VENV_ACTIVATE` exported from a shared filesystem path. +2. Run from the SLURM login/head node, where both `scontrol` and `srun` are + available. +3. Run from inside a Slurm allocation, or provide a Slurm nodelist explicitly. + +### Example from inside an allocation + +```bash +export VENV_ACTIVATE=~/envs/preflight/.venv/bin/activate +mkdir -p output + +python tools/preflight_bisect/bisect.py \ + --nodelist "$SLURM_NODELIST" \ + --output-dir "output/bisect-$(date +%Y%m%d-%H%M%S)" \ + --trial-timeout-sec 600 \ + --slurm-time 00:15:00 \ + --preflight-env USING_AINIC=1 \ + --preflight-env NCCL_IB_GID_INDEX=1 \ + --preflight-env NCCL_CROSS_NIC=1 \ + --preflight-env NCCL_PXN_DISABLE=0 \ + 2>&1 | tee output/bisect-latest.log +``` + +Adjust the `--preflight-env` lines to match your cluster. Per-trial logs and a +final `summary.txt` are written under `--output-dir`. + +> Note: Set `--trial-timeout-sec` high enough for a healthy subset to finish. +> Too small a timeout can turn slow-but-good trials into false failures, causing +> the bisection to explore extra paths. +> +> Note: `--preflight-env KEY=VALUE` values are concatenated into a single +> `srun --export=ALL,...` argument, so values must not contain commas or +> whitespace. Keep comma-containing values as normal exported environment +> variables. + +--- + +## 10. See also + +- [Preflight](./preflight.md) — full reference for the `preflight` subcommand and its flags +- [CLI User Guide](./cli/PRIMUS-CLI-GUIDE.md) — container-based and `primus-cli slurm` workflows +- `[runner/primus-cli-direct.sh](../runner/primus-cli-direct.sh)` — the direct launcher itself (`primus-cli direct` dispatches here) +- `[primus/tools/preflight/](../primus/tools/preflight/)` — preflight implementation +- `[tools/preflight_bisect/bisect.py](../tools/preflight_bisect/bisect.py)` — bisect wrapper for narrowing down failing nodes in multi-node preflight runs diff --git a/docs_deprecated/preflight.md b/docs_deprecated/preflight.md new file mode 100644 index 000000000..7f3e90c3f --- /dev/null +++ b/docs_deprecated/preflight.md @@ -0,0 +1,522 @@ +# Preflight + +`preflight` is Primus' cluster diagnostic tool. It produces: + +- A **fast info report** (host / GPU / network configuration), and +- A configurable suite of **performance tests** (GEMM TFLOPS, intra-node and inter-node communication bandwidth, P2P, ring P2P). + +Use it to spot misconfiguration, hardware degradation, or perf outliers **before** committing a large distributed training run to a global rendezvous. + +- **User-facing entry**: `primus-cli ... -- preflight [args]` +- **No-container launcher**: `runner/primus-cli direct -- preflight ...` — see [`preflight-direct.md`](./preflight-direct.md). +- **Implementation entrypoint**: `primus/cli/subcommands/preflight.py` → `primus/tools/preflight/preflight_perf_test.py`. + +> Looking for a faster, distributed-rendezvous-free per-node screen? See [`node-smoke.md`](./node-smoke.md) (and the [quick-start guide](./node-smoke-test-instruction.md)). The recommended workflow is **smoke first, preflight second** — see [§10 Comparison with node-smoke](#10-comparison-with-node-smoke). + +--- + +## 1. Two run modes (and how preflight picks one) + +Preflight has two report types, controlled by a single precedence rule: + +| Mode | Triggered by | What it does | +|---|---|---| +| **Info-only** | `--host`, `--gpu`, `--network` (in any combination) | Lightweight host / GPU / network introspection. **No `torch.distributed` rendezvous.** Cannot hang on network misconfig. | +| **Perf-only** | `--perf-test`, `--tests ...`, or `--quick` | Runs the configured perf tests under a global rendezvous. **Implied** by `--tests` and `--quick`. | +| **Default (info + perf)** | No flags at all | Runs the info report first, then every perf test. | + +### Mode precedence + +1. **Any of `--perf-test` / `--tests` / `--quick` is set → perf-only mode.** + If info selectors (`--host`/`--gpu`/`--network`) are also present, they are dropped and a `WARN` is emitted (also written as a `> Note:` at the top of the perf report). To get both reports, run two invocations. +2. **Otherwise, any of `--host`/`--gpu`/`--network` is set → info-only mode.** + Perf-only tuning knobs (e.g. `--comm-sizes-mb`) are inert in this mode and trigger a single `WARN` listing them. +3. **Otherwise (no flags) → default**: info report **first** (no rendezvous), then perf tests. + +The default order ensures you always get a report even if `torch.distributed` initialization later hangs. + +--- + +## 2. Quick start + +### Info report only (fast, no rendezvous) + +```bash +primus-cli direct -- preflight --host --gpu --network +``` + +### Full preflight (info + every perf test) + +```bash +primus-cli direct -- preflight +``` + +### Perf tests only + +```bash +primus-cli direct -- preflight --perf-test +``` + +### Fast pre-launch sanity check + +```bash +primus-cli direct -- preflight --quick +``` + +Equivalent on SLURM via `primus-cli slurm`: + +```bash +primus-cli slurm srun -N 4 -- preflight --quick +``` + +Without a container, see [`preflight-direct.md`](./preflight-direct.md) for the equivalent `runner/primus-cli direct -- preflight ...` invocations. + +--- + +## 3. Test selection (`--tests`) + +`--tests` takes a comma-separated list of canonical tokens (or `all`). Implies `--perf-test`. + +| Token | What it runs | +|---|---| +| `gemm` | Single-GPU square GEMM TFLOPS sweep. | +| `intra-allreduce` | Intra-node `all_reduce` bandwidth at every selected `--intra-group-sizes` x `--intra-comm-sizes-mb`. | +| `intra-alltoall` | Intra-node `all_to_all` bandwidth, same configuration matrix. | +| `inter-allreduce` | Inter-node `all_reduce` bandwidth at every selected `--inter-group-sizes` x `--inter-comm-sizes-mb`. | +| `inter-alltoall` | Inter-node `all_to_all` bandwidth, same configuration matrix. | +| `inter-p2p` | Inter-node 2-rank P2P send/recv. Requires `--inter-group-sizes` to actually contain pair-able sizes. | +| `inter-ring-p2p` | Inter-node ring-pattern P2P, sized by `--ring-p2p-sizes-mb`. | +| `all` | Every token above. Default when `--tests` is omitted. | + +Examples: + +```bash +# GEMM only +primus-cli direct -- preflight --tests gemm + +# Just the inter-node bandwidth tests +primus-cli direct -- preflight --tests inter-allreduce,inter-alltoall + +# Combine with size overrides +primus-cli direct -- preflight \ + --tests gemm,inter-allreduce \ + --comm-sizes-mb 64,1024 \ + --inter-group-sizes all +``` + +Unknown tokens fail fast (before any rendezvous): + +```text +[Primus:Preflight] ERROR: invalid perf config: --tests: unknown token 'gem'. +Valid tokens: gemm, intra-allreduce, intra-alltoall, inter-allreduce, +inter-alltoall, inter-p2p, inter-ring-p2p, all +``` + +--- + +## 4. Quick preset (`--quick`) + +`--quick` is the recommended **pre-launch sanity** preset. Implies `--perf-test`. It substitutes: + +| Knob | `--quick` value | +|---|---| +| `--tests` | `gemm,intra-allreduce,inter-allreduce` | +| `--comm-sizes-mb` | `64,1024` | +| `--intra-group-sizes` | `LOCAL_WORLD_SIZE` (full intra-node group only) | +| `--inter-group-sizes` | `all` (full N-node group only) | +| `warmup` | `5` | +| `iteration` | `20` | + +**User-supplied flags override the preset.** For example: + +```bash +# Quick preset, but with a custom size set +primus-cli direct -- preflight --quick --comm-sizes-mb 32,256 +``` + +A full perf run with default knobs takes minutes; `--quick` typically finishes in <60s on healthy hardware. + +--- + +## 5. Tuning the perf tests + +All perf tuning knobs default to `None` so preflight can tell whether you set them. When unset, the documented defaults below apply. + +### 5.1 Message sizes (collective + P2P) + +| Flag | Default | Applies to | +|---|---|---| +| `--comm-sizes-mb CSV` | `2,4,8,16,32,64,128,256,512,1024` | Default for both intra- and inter-node `allreduce` / `alltoall` and `inter-p2p` when no specific override is given. | +| `--intra-comm-sizes-mb CSV` | falls back to `--comm-sizes-mb` | Override for **intra-node** `allreduce` / `alltoall`. | +| `--inter-comm-sizes-mb CSV` | falls back to `--comm-sizes-mb` | Override for **inter-node** `allreduce` / `alltoall` / `inter-p2p`. | + +```bash +# Smaller, focused sweep +primus-cli direct -- preflight --comm-sizes-mb 8,128 + +# Different sizes for intra vs inter +primus-cli direct -- preflight \ + --tests intra-allreduce,inter-allreduce \ + --comm-sizes-mb 8,128 \ + --intra-comm-sizes-mb 4,32 +``` + +### 5.2 Group sizes + +| Flag | Default | Notes | +|---|---|---| +| `--intra-group-sizes CSV` | `2,4,8` | Each value must divide `LOCAL_WORLD_SIZE`. | +| `--inter-group-sizes CSV` | `2,4,all` | `all` means the full N-node group. Other values are subgroup sizes. **For `inter-alltoall` only**, every requested per-group node count is internally clamped to **16** before deduping (see "Inter-node alltoall is capped at 16 nodes" below). The other inter-node tests (`inter-allreduce`, `inter-p2p`, `inter-ring-p2p`) use the requested sizes unchanged. | + +```bash +# All-GPU intra + full N-node inter only +primus-cli direct -- preflight \ + --tests intra-allreduce,inter-allreduce \ + --intra-group-sizes 8 \ + --inter-group-sizes all +``` + +Validation is gated by which tests are actually selected. For example, `--tests gemm --intra-group-sizes 3` does **not** abort on a host with `LOCAL_WORLD_SIZE=8`; the intra-group constraint is only checked when an intra test is enabled. + +#### Inter-node alltoall is capped at 16 nodes + +Regardless of the cluster size or what `--inter-group-sizes` requests, the `inter-alltoall` test always runs on per-group node counts of at most **16**. Concretely, every requested value `G` is replaced with `min(G, 16)`, and the resulting list is deduped. Examples: + +| Cluster | `--inter-group-sizes` | Requested (resolved) | `inter-alltoall` actually runs | +|---|---|---|---| +| 8 N | `all` | `[8]` | `[8]` (no change) | +| 64 N | `2,4,all` | `[2, 4, 64]` | `[2, 4, 16]` | +| 128 N | `2,4,16,32,all` | `[2, 4, 16, 32, 128]` | `[2, 4, 16]` | +| 128 N | `64` | `[64]` | `[16]` | + +When the cap actually changes the list, preflight emits a single one-line WARN to stdout so the row labels in the report (e.g. `alltoall-16nodes` instead of `alltoall-128nodes`) are not surprising. + +Why a hard cap and why 16: + +- **Real-world MoE training rarely dispatches across more than ~8 nodes.** DeepSeek-V3's largest published configuration uses `EP=64` over 8 nodes with per-token dispatch capped at 4 nodes; NVIDIA Megatron-Core's published EP recipes follow the same shape. 16 covers every published configuration with comfortable headroom while staying well clear of the per-node ephemeral-port pressure described in §7. +- **The cap eliminates the dominant source of `Address already in use` at large scale.** During each `ncclCommInit`, an inter-node alltoall sub-group of `K` nodes opens a near-full mesh of IB OOB sockets per local rank — empirically, peak simultaneous ESTAB sockets per node grow ~linearly with `K` at **~145 sockets per added node** (linear fit `peak_ESTAB ≈ 145·K + 683` over measurements at 24/32/48/56 N; see §7.1.2). Once peak ESTAB approaches the size of the kernel's ephemeral-port pool (default `28 232` ports), the next outgoing `bind()` walks the entire range without finding an allocatable port and fails. Capping the sub-group at 16 holds peak ESTAB at **~3.7 k** — measured directly on a 56 N cluster with the cap active — comfortably under any sensible pool, so the failure cannot occur regardless of cluster size or OS tuning. +- **Other inter-node tests are unaffected.** `inter-allreduce` (ring/tree, ~`log K` peers per rank) and `inter-ring-p2p` (ring, 2 peers per rank) and `inter-p2p` (pairwise) all open far fewer simultaneous OOB sockets than alltoall and continue to honor `--inter-group-sizes` exactly as written. As a concrete reference point: at 56 nodes, a default-configured `inter-allreduce` peaks at ~1.8 k ESTAB; an `inter-alltoall` over the same 56 nodes peaks at ~8.8 k. +- **Intentionally not configurable.** This is a known-safe ceiling for the comm shapes preflight is supposed to characterize, not a tuning knob; raising it would re-introduce the very failure mode preflight is meant to *detect* in the cluster, not *cause*. + +### 5.3 Ring P2P sizes + +| Flag | Default | Applies to | +|---|---|---| +| `--ring-p2p-sizes-mb CSV` | `10,20,40,80,160` | `inter-ring-p2p` only. | + +```bash +primus-cli direct -- preflight \ + --tests inter-ring-p2p \ + --ring-p2p-sizes-mb 5,20,80 +``` + +### 5.4 Plotting + +| Flag | Effect | +|---|---| +| `--plot` | After each perf test, write per-size bandwidth bar charts under `//` and reference them in the markdown report. | + +--- + +## 6. Reliability knobs + +Two knobs that are inert under happy-path conditions but matter at scale or on flaky networks. + +### 6.1 `--comm-cleanup-delay-sec FLOAT` (default `2.0`) + +Delay (seconds) inserted between destroying NCCL/RCCL process groups and creating new ones. Provides cross-rank synchronization across the destroy → setup transition (so a rank doesn't try to connect to a peer whose listener hasn't finished closing) and gives the kernel a moment to unlink closed-socket bookkeeping. See §7.1 for why this knob is *not* primarily defending against `TIME_WAIT` pressure (which doesn't apply to NCCL's connection pattern) — the actual `Address already in use` defense is the §5.2 inter-alltoall cap. + +- Default `2.0` is essentially free and worth keeping as cheap insurance at every cluster size. +- Set to `0` to disable the sleep entirely (barrier only). +- Bump to e.g. `5` only on very flaky networks or pathological kernel scheduling; widening `ip_local_port_range` (§7.2) is a more direct fix when the binding constraint is genuinely hit. + +```bash +# Small/medium clusters: defaults are fine. Override only if you see +# port-reuse races on very flaky networks or unusual kernel TIME_WAIT +# settings. +primus-cli slurm srun -N 8 -- preflight --quick --comm-cleanup-delay-sec 5 +``` + +See §7 ("Running on very large clusters") for the rationale behind why the default works at scale and for the OS-level best-practices that apply to any RDMA workload. + +### 6.2 `--dist-timeout-sec INT` (default `120`) + +Timeout (seconds) for `torch.distributed.init_process_group`. If init does not complete within this many seconds, preflight writes the info report (when applicable) plus a `Distributed Init` failure section to the markdown report, prints a clear error, and exits `2` — instead of hanging indefinitely. + +> Note: §6 used to also document `--comm-cleanup-large-threshold-nodes`, which forced a 60 s drain whenever a destroyed subgroup met or exceeded a size threshold (default 64 nodes). That flag was removed because the underlying failure mode it tried to paper over — peak simultaneous ESTAB exhausting the per-node ephemeral-port pool during a large inter-node alltoall `ncclCommInit` — is now prevented at the source by the §5.2 alltoall cap. The `--comm-cleanup-delay-sec` knob remains, but its primary role is now cross-rank synchronization across the destroy → setup transition rather than draining `TIME_WAIT`; the default 2 s is essentially free and worth keeping as cheap insurance. + +```bash +# Fail fast if rendezvous does not work +primus-cli direct -- preflight --perf-test --dist-timeout-sec 30 +``` + +--- + +## 7. Running on very large clusters (≥ 64 nodes) + +Beyond ~64 nodes, two practical concerns dominate that smaller runs never see. Read this section once if you operate clusters in this range; it explains the failure mode, the OS knobs that fix it at the system level, and the recommended preflight invocation patterns. + +### 7.1 Why "Address already in use" used to surface at scale + +The failure is a **per-node ephemeral port exhaustion** during a single inter-node alltoall `ncclCommInit`, not a chronic accumulation of `TIME_WAIT` sockets across phases. Understanding this distinction is what motivates both the §5.2 cap and the §7.2 OS tuning recommendations. + +#### 7.1.1 What actually consumes the per-node ephemeral pool + +Linux's outgoing-connection allocator (`__inet_hash_connect()`) does **not** reject ports just because some other socket is in any state on them. It rejects a port only when the new connection's full 4-tuple `(saddr, sport, daddr, dport)` collides with an existing socket's 4-tuple: + +- **Live ESTAB sockets** sit on a specific 4-tuple and prevent the kernel from reusing that exact 4-tuple. Each new outgoing connection that lands on a port already holding an ESTAB socket has to walk to the next port. As ESTAB density approaches one-socket-per-port across the entire ephemeral range, the walk takes longer and longer until eventually no port is allocatable — that's the EADDRINUSE. +- **TIME_WAIT sockets** also live on specific 4-tuples but are governed by `tcp_tw_reuse`. Critically, they only block a new connection when the new connection's *desired* 4-tuple collides with the historical one — i.e. when the new connection is to the *same* `(daddr, dport)` from the *same* `(saddr, sport)`. + +For NCCL inter-node OOB traffic, the second case essentially never happens: each `ncclCommInit` connects to **fresh peer OOB listening ports** (the peer chooses an ephemeral listener per setup), so successive comm setups always have different `dport`. TIME_WAIT entries from a previous destroy sit on `(local, P_old, peer, dport_OLD)`; the next setup wants `(local, ?, peer, dport_NEW)`. Even when the new connection happens to land on `sport == P_old`, the dports differ → no 4-tuple collision → the TIME_WAIT entry is invisible to the allocator regardless of `tcp_tw_reuse`. + +The practical consequence: under NCCL's connection pattern, **the binding constraint reduces to peak simultaneous ESTAB ≤ size of the ephemeral pool**. + +#### 7.1.2 Why inter-node alltoall is the test that exhausts it + +Per-node peak ESTAB scales very differently across the inter-node test families: + +| Test (56 N, default knobs) | Peer topology per rank | Peak ESTAB per node | +|---|---|---| +| `inter-allreduce` | ring / tree, ~`log K` peers | ~1.8 k | +| `inter-alltoall` | full mesh, `K-1` peers | ~8.8 k | + +The empirical scaling of `inter-alltoall` peak ESTAB in the default perf-test sweep. Four measurements at 24/32/48/56 N fit a near-perfect line: + +``` +peak_ESTAB(per node) ≈ 145.09 · K + 683 +``` + +| K (nodes) | Measured peak ESTAB | Fit (145·K + 683) | Source | +|---|---|---|---| +| 16 (capped run) | **3 687** | 3 003 | direct measurement — fit slightly under-predicts at the low-N extrapolation | +| 24 | 4 165 | 4 165 | calibration point | +| 32 | 5 326 | 5 326 | calibration point | +| 48 | 7 647 | 7 647 | calibration point | +| 56 | 8 808 | 8 808 | calibration point | +| 128 (uncapped, extrapolated) | — | **~19 240** | linear extrapolation | + +Two practical reads from this: + +- **At 56 N the workload already sits at ~31 % of the default 28 232-port pool**, with ~19 k ports of headroom. That's why every default-pool 56 N run in our experiment succeeded. +- **An uncapped 128-N inter-alltoall would peak around ~19 k ESTAB** — still inside the default pool but with ~9 k ports of headroom, and *over the cliff* on any cluster that has narrowed `ip_local_port_range`, that runs additional outgoing TCP work concurrently, or that has the source-port allocator's random-walk hit a bad starting offset. + +#### 7.1.3 Empirical confirmation: the binding constraint really is `peak_ESTAB ≤ pool_size` + +Holding the workload constant (56 N, only inter-alltoall) and varying *only* the per-node ephemeral pool size: + +| Pool | Peak ESTAB | Headroom | Result | +|---|---|---|---| +| 28 231 (default) | 8 805 | +19 426 | OK | +| 9 000 | 8 806 | +194 | OK (barely) | +| 8 000 | (~8 800 expected) | −800 | **FAIL** | + +The transition is sharp and right at the predicted boundary. Note that peak `TIME_WAIT` count in the same runs ranged from ~16 k to ~24 k — **far above** the pool size in the 9 k and 8 k rows. If TIME_WAIT count drove the failure, both narrow-pool rows should fail. They don't; only the row where `peak_ESTAB > pool_size` does. + +The mechanism is further corroborated by a direct cap experiment on the same 56 N cluster, default pool: re-running the inter-node alltoall test with the per-group node count capped at 16 (matching the §5.2 cap) yields peak ESTAB **3 687** — well under both the default 28 k pool and the 9 k / 8 k stressed pools — and the run completes cleanly. The cap acts directly on the binding constraint by holding peak ESTAB low enough that the pool cannot be exhausted, regardless of cluster size. + +#### 7.1.4 The cap closes the failure mode at the source + +The §5.2 inter-node alltoall cap (16 nodes max) holds peak ESTAB at **~3.7 k** regardless of cluster size — measured directly in §7.1.3, comfortably under the default 28 k pool (~13 % utilization) and still safe at half-default pool widths. The OS-level tunings in §7.2 remain useful general hygiene for any RDMA / multi-NIC workload, but a default preflight invocation no longer needs them to avoid `Address already in use`. + +This is **not** a real training failure mode in any case — production training jobs create their TP / DP / PP / EP communicators *once* at startup and reuse them, and real-world MoE training rarely dispatches across more than ~8 nodes. The preflight tool is the only thing that builds many large communicators in a short window, which is why the issue was preflight-specific to begin with. + +### 7.2 OS-level tuning (best-practice for any large-cluster node) + +Given §7.1's mechanism (binding constraint = peak ESTAB ≤ pool size), the OS knobs split cleanly into "directly relevant" and "general hygiene": + +```bash +# 1) DIRECTLY RELEVANT: widen the per-node ephemeral pool. +# The default range is 32768-60999 (~28 k ports). Widening it +# to 1024-65535 (~64 k ports) more than doubles the headroom +# for peak simultaneous ESTAB — and that is the only thing that +# can produce EADDRINUSE under the NCCL connection pattern. +sudo sysctl -w net.ipv4.ip_local_port_range="1024 65535" + +# 2) GENERAL HYGIENE: allow TIME_WAIT reuse for outgoing connections. +# For the NCCL inter-node OOB pattern this is largely a no-op +# (each ncclCommInit picks fresh peer destination ports, so +# historical TIME_WAIT 4-tuples never collide with what the +# next setup wants). It is still recommended for any host that +# runs additional outgoing TCP workloads where the SAME +# (daddr, dport) is hit repeatedly from the same source IP -- +# the textbook scenario the kernel doc is written around. +sudo sysctl -w net.ipv4.tcp_tw_reuse=1 + +# Persist across reboots: +cat <<'EOF' | sudo tee /etc/sysctl.d/99-large-cluster.conf +net.ipv4.ip_local_port_range = 1024 65535 +net.ipv4.tcp_tw_reuse = 1 +EOF +sudo sysctl --system +``` + +If you only have time to set one of these, pick `ip_local_port_range`. With the §5.2 inter-alltoall cap holding peak ESTAB at ~3.7 k even at 1024 N, neither knob is *required* for preflight — but the wider port range is the right insurance for any host that might also run other outgoing TCP traffic concurrently. + +Note on `tcp_tw_reuse=2`: as of Linux 4.12, value `2` enables TIME_WAIT reuse **only for loopback** (`127.0.0.0/8`, `::1`). For inter-node IB OOB connections this is equivalent to `tcp_tw_reuse=0`. The fact that several of our 56 N runs succeeded under `tcp_tw_reuse=2` with `peak_WAIT > 16 k` is direct evidence that `TIME_WAIT` *count* doesn't gate inter-node bind() — only `peak_ESTAB > pool_size` does (see §7.1.3). + +### 7.3 In-tool defenses + +Preflight has two complementary defenses: + +1. **The §5.2 inter-node alltoall cap (16 nodes max).** *This is the actual fix.* Regardless of cluster size or `--inter-group-sizes`, the alltoall test never builds a sub-group large enough to push peak ESTAB anywhere near the per-node ephemeral-port pool. This eliminates the historical EADDRINUSE failure mode by construction (see §7.1). +2. **The §6.1 per-phase cleanup delay (`--comm-cleanup-delay-sec`, default `2.0`).** A global barrier + sleep inserted after every comm destroy. Its primary job is **cross-rank synchronization** across the destroy → setup transition (so a rank doesn't try to connect to a peer whose listener hasn't finished closing) and giving the kernel a moment to unlink closed-socket bookkeeping. It is *not* protecting against `TIME_WAIT` 4-tuple collisions — those don't occur in NCCL's connection pattern (see §7.1.1). The default 2 s is essentially free and worth keeping as cheap insurance. + +Together, a default preflight invocation is safe at every cluster size we test up to 1024 nodes. The only situation where you would consider raising `--comm-cleanup-delay-sec` is on a network with unusually narrow ephemeral-port ranges or pathological kernel scheduling — and even there, widening `ip_local_port_range` (§7.2) is the cleaner fix because it directly addresses the binding constraint. + +### 7.4 Recommended invocation patterns at very large scale + +For clusters at or beyond ~128 nodes, the most reliable and most informative way to use preflight is still to **split the run into one test family per invocation** rather than one big run — not for `Address already in use` reasons (the §7.3 defenses handle that), but because it keeps wall-clock per invocation small and makes it trivial to identify which specific comm shape is degraded if a metric looks off. + +```bash +# 1) GPU + intra-node fabric first (cheap, no inter-node OOB churn). +primus-cli slurm srun -N 128 -- preflight \ + --tests gemm,intra-allreduce,intra-alltoall + +# 2) Inter-node DP-style collectives, all-nodes group only. +primus-cli slurm srun -N 128 -- preflight \ + --tests inter-allreduce \ + --inter-group-sizes all +# Note: --inter-group-sizes all is honored here for inter-allreduce. +# For inter-alltoall it would be clamped to 16 (see §5.2). +primus-cli slurm srun -N 128 -- preflight \ + --tests inter-alltoall \ + --inter-group-sizes all + +# 3) Inter-node PP-style ring P2P (the test that benefits most from +# isolation — it's the closest match to what real pipeline-parallel +# training actually exercises). +primus-cli slurm srun -N 128 -- preflight \ + --tests inter-ring-p2p + +# 4) Optional: pairwise inter-node P2P scan (useful for finding a +# single bad link, slower because it walks many pairs). +primus-cli slurm srun -N 128 -- preflight \ + --tests inter-p2p +``` + +Each invocation: + +- Tears down its own `WORLD` at exit, so the next invocation starts with a fresh per-node port pool. +- Touches only one `--tests` value, so you get a per-test wall clock and can re-run a single phase if its numbers look off without paying for the others. +- Carries its own report under `--report-file-name` (or the wrapper-generated default), which makes archiving and comparison across runs straightforward. + +### 7.5 Decision flow + +| Cluster size | Recommended approach | +|---|---| +| ≤ 32 nodes | Single command, default knobs. Nothing special. | +| 33-127 nodes | Single command, default knobs. | +| ≥ 128 nodes | Single command works with default knobs (the §5.2 alltoall cap holds peak ESTAB at ~3.7 k, ≈ 7.6× under the default 28 k ephemeral pool). Splitting per `--tests` token as in §7.4 is recommended for diagnostic clarity rather than for safety. Widening `ip_local_port_range` (§7.2) is best-practice for any RDMA workload but no longer required for preflight specifically. | +| ≥ 256 nodes | Always split as in §7.4 — keeps every invocation snappy and makes regressions much easier to localize. | + +--- + +## 8. Reporting + +| Flag | Default | Effect | +|---|---|---| +| `--dump-path DIR` | `output/preflight` | Output directory for reports + plots. | +| `--report-file-name NAME` | auto-generated `preflight-${NNODES}N-YYYYMMDD-HHMMSS` | Base name for report files. Omit to let preflight auto-generate a unique timestamped name (prevents stale leftovers from prior runs being mistaken for fresh output). Pass an explicit value when you want a stable / well-known filename. | +| `--disable-pdf` | enabled | Skip PDF generation (Markdown only). Useful when `weasyprint`/`markdown2` aren't installed. | + +Output files: + +| File | Produced when | Notes | +|---|---|---| +| `.md` / `.pdf` | Info-only mode, or default mode | Info report. | +| `_perf.md` / `_perf.pdf` | Perf-only mode, or default mode | Perf report (GEMM + comm). | + +Only **rank 0** writes the report. + +### Perf report layout + +A `_perf.md` produced by a default run contains, in order: + +1. (Optional) `> Note:` line listing dropped info selectors. +2. `# Nodes` legend — `Node N → Hostname` table, used by every subsequent table to keep host columns compact. +3. `=======IB Bandwidth roofline (GB/s)=======` — bandwidth of the first IB device on Node 0. +4. Per enabled test, in this order: `gemm`, `intra-comm`, `inter-comm`, `inter-p2p`, `inter-ring-p2p`. Each section has a configuration line, a results table (Node / Rank / hostname / per-size GB/s), optional plots, and a per-rank wall-clock summary. +5. `[Primus:Preflight] done in s` lines on stdout for at-a-glance progress on the launching shell. + +--- + +## 9. Backward-compat aliases + +| Flag | Equivalent | Notes | +|---|---|---| +| `--check-host`, `--check-gpu`, `--check-network` | `--host`, `--gpu`, `--network` | Same behavior. Keep working for older scripts. | +| `--no-split-nodes-subgroup` | `--inter-group-sizes all` **and** drops `inter-p2p` | Pre-`--tests`/`--inter-group-sizes` alias. Use the new flags in new scripts. | + +--- + +## 10. Comparison with node-smoke + +| Aspect | `node-smoke` | `preflight` | +|---|---|---| +| Rendezvous | None — every node independent | Global `torch.distributed` | +| Wall clock | ~30–60 s for 6 nodes (Tier 1+2) | Minutes; scales with N for inter-node tests | +| Granularity | Per-node PASS/FAIL | Per-rank measurements (no auto-fail by default) | +| Inter-node bandwidth matrix | Not tested (intentionally) | Yes (allreduce/alltoall/p2p/ring-p2p) | +| Drift detection | Yes (versions, NIC firmware, port count) | No | +| Host limits / RDMA roll-call | Yes (hard fail) | Reported via `collect_*_info` only | +| Output format | Per-node JSON + cluster md + SLURM-ready txt | Markdown + PDF | + +**Recommended workflow**: run `node-smoke` first to exclude broken nodes, then run `preflight` on the surviving set to get cross-node bandwidth measurements. See [`node-smoke-test-instruction.md`](./node-smoke-test-instruction.md) §3 ("Quick start") for the integration commands. + +--- + +## 11. Validation & error handling + +Preflight resolves the perf config **before** any distributed rendezvous. This means typos and bad sizes/group-sizes fail in seconds, not after a 120s NCCL init: + +```text +[Primus:Preflight] ERROR: invalid perf config: --tests: unknown token 'gem'. +[Primus:Preflight] ERROR: invalid perf config: + --intra-group-sizes: [3] do not divide LOCAL_WORLD_SIZE=8 +[Primus:Preflight] ERROR: invalid perf config: --comm-sizes-mb: '0' must be positive +``` + +In info-only mode, perf-only tuning knobs trigger a single warning so you notice them but they don't abort: + +```text +[Primus:Preflight] WARN: --comm-sizes-mb,--intra-group-sizes have no effect +in info-only mode (no --perf-test/--tests/--quick). +``` + +In default mode where info selectors are dropped because perf intent was set, the preserved warning is also written into the perf report header: + +```text +> Note: info selectors --host were dropped because perf mode +> (--perf-test/--tests/--quick) takes precedence. Run them in a separate +> invocation if you want both reports. +``` + +--- + +## 12. Operational tips + +- **For multi-node runs, always use `primus-cli slurm` or `primus-cli direct` under `srun`** so distributed environment variables (`NNODES` / `NODE_RANK` / `MASTER_ADDR`) are set correctly. +- **Insufficient CPU cores cause >30x perf slowdowns** — pass `srun -c ` so RCCL's network proxy threads have CPU to spawn on. Verify with `srun -N 1 --gpus-per-node=8 bash -c 'nproc'`. +- **For a quick environment snapshot**, prefer `--host --gpu --network` (no rendezvous, finishes in seconds even on broken networks). +- **Between each communication test phase**, preflight performs a global barrier + `--comm-cleanup-delay-sec` sleep (default 2 s) for cross-rank sync across the destroy → setup transition. The default works at every cluster size we test up to 1024 nodes because the inter-node alltoall sub-group is internally capped at 16 (see §5.2), which holds peak simultaneous ESTAB sockets per node well under the kernel's ephemeral-port pool — the only constraint that actually produces `Address already in use` under NCCL's connection pattern (see §7.1). Widening `ip_local_port_range` (§7.2) is best-practice for any RDMA workload. +- **For pre-launch screening of a large cluster**, the recommended sequence is: + 1. `node-smoke` to prune broken nodes (`failing_nodes.txt`). + 2. `preflight --quick` on the surviving nodes for the perf sanity numbers. + 3. `preflight` (full) on the same set if the `--quick` numbers raise a flag. + +--- + +## 13. Running preflight without a container + +If you cannot (or prefer not to) use a container, see [`preflight-direct.md`](./preflight-direct.md) for the step-by-step `runner/primus-cli direct -- preflight ...` walkthrough — Python virtual-environment setup, SLURM invocation patterns, NCCL configuration for Broadcom and Pensando (AINIC) clusters, and many configurable-knob examples. + +--- + +## 14. See also + +- [`preflight-direct.md`](./preflight-direct.md) — quick-start guide for `primus-cli direct -- preflight` (no container). +- [`node-smoke.md`](./node-smoke.md) — full reference for the per-node smoke test. +- [`node-smoke-test-instruction.md`](./node-smoke-test-instruction.md) — short quick-start for the smoke test. +- [`runner/primus-cli-direct.sh`](../runner/primus-cli-direct.sh) — non-container launcher (`primus-cli direct` dispatches here). +- [`primus/tools/preflight/`](../primus/tools/preflight/) — implementation. +- [`primus/tools/preflight/preflight_args.py`](../primus/tools/preflight/preflight_args.py) — canonical CLI definition (single source of truth for flags + defaults). diff --git a/docs/projection.md b/docs_deprecated/projection.md similarity index 99% rename from docs/projection.md rename to docs_deprecated/projection.md index 02596c6b3..3588c5efb 100644 --- a/docs/projection.md +++ b/docs_deprecated/projection.md @@ -626,7 +626,7 @@ Splitting the residual into these two terms is more robust than the older single With `--memory-mode both`, a side-by-side table shows the simulate vs. benchmark per-component deltas. A small delta (within ~10–20%) means the analytical model and the residual term are well-calibrated; a large positive delta (simulate ≫ benchmark) usually means simulate is over-counting unsharded components, while a large negative delta means the bench captured overhead the analytical model under-estimates. -> The benchmark-based memory projection is what the [Tuning Agent](tuning_agent.md) uses for OOM-accurate feasibility filtering when a GPU is available, so its `tokens/s/GPU` rankings never include configs that would OOM on the real cluster. +> The benchmark-based memory projection is what the [Tuning Agent](../docs/02-user-guide/tuning-agent.md) uses for OOM-accurate feasibility filtering when a GPU is available, so its `tokens/s/GPU` rankings never include configs that would OOM on the real cluster. --- diff --git a/docs/quickstart.md b/docs_deprecated/quickstart.md similarity index 96% rename from docs/quickstart.md rename to docs_deprecated/quickstart.md index 4d6dd1e6c..6aa149624 100644 --- a/docs/quickstart.md +++ b/docs_deprecated/quickstart.md @@ -87,7 +87,7 @@ primus-cli [options] [mode-args] -- [command] **Learn More:** - [CLI User Guide](./cli/PRIMUS-CLI-GUIDE.md) - Complete reference -- [CLI Architecture](./cli/CLI-ARCHITECTURE.md) - Design deep dive +- [CLI Architecture](../docs/06-developer-guide/cli-architecture.md) - Design deep dive - [Configuration Guide](./configuration.md) - YAML configuration - [Examples](../examples/README.md) - Real-world templates diff --git a/docs/tech_blogs/primus_cli_unified_entry_rocm.md b/docs_deprecated/tech_blogs/primus_cli_unified_entry_rocm.md similarity index 100% rename from docs/tech_blogs/primus_cli_unified_entry_rocm.md rename to docs_deprecated/tech_blogs/primus_cli_unified_entry_rocm.md diff --git a/docs/tech_blogs/primus_pipeline/imgs/actual-perf.png b/docs_deprecated/tech_blogs/primus_pipeline/imgs/actual-perf.png similarity index 100% rename from docs/tech_blogs/primus_pipeline/imgs/actual-perf.png rename to docs_deprecated/tech_blogs/primus_pipeline/imgs/actual-perf.png diff --git a/docs/tech_blogs/primus_pipeline/imgs/llama2-7B-perf.png b/docs_deprecated/tech_blogs/primus_pipeline/imgs/llama2-7B-perf.png similarity index 100% rename from docs/tech_blogs/primus_pipeline/imgs/llama2-7B-perf.png rename to docs_deprecated/tech_blogs/primus_pipeline/imgs/llama2-7B-perf.png diff --git a/docs/tech_blogs/primus_pipeline/imgs/qwen-235B-perf.png b/docs_deprecated/tech_blogs/primus_pipeline/imgs/qwen-235B-perf.png similarity index 100% rename from docs/tech_blogs/primus_pipeline/imgs/qwen-235B-perf.png rename to docs_deprecated/tech_blogs/primus_pipeline/imgs/qwen-235B-perf.png diff --git a/docs/tech_blogs/primus_pipeline/imgs/simulation.png b/docs_deprecated/tech_blogs/primus_pipeline/imgs/simulation.png similarity index 100% rename from docs/tech_blogs/primus_pipeline/imgs/simulation.png rename to docs_deprecated/tech_blogs/primus_pipeline/imgs/simulation.png diff --git a/docs/tech_blogs/primus_pipeline/imgs/simulator_shell.png b/docs_deprecated/tech_blogs/primus_pipeline/imgs/simulator_shell.png similarity index 100% rename from docs/tech_blogs/primus_pipeline/imgs/simulator_shell.png rename to docs_deprecated/tech_blogs/primus_pipeline/imgs/simulator_shell.png diff --git a/docs/tech_blogs/primus_pipeline/primus_pipeline.md b/docs_deprecated/tech_blogs/primus_pipeline/primus_pipeline.md similarity index 100% rename from docs/tech_blogs/primus_pipeline/primus_pipeline.md rename to docs_deprecated/tech_blogs/primus_pipeline/primus_pipeline.md diff --git a/docs/tech_blogs/projection/projection.md b/docs_deprecated/tech_blogs/projection/projection.md similarity index 100% rename from docs/tech_blogs/projection/projection.md rename to docs_deprecated/tech_blogs/projection/projection.md diff --git a/examples/README.md b/examples/README.md index ce0e36460..5723b1168 100644 --- a/examples/README.md +++ b/examples/README.md @@ -36,10 +36,11 @@ It supports both **single-node** and **multi-node** training, and includes optio Primus supports multiple backends. -| Backend | Description | -| ---------- | ------------------------------------------------------------ | -| Megatron | Open-source framework for large-scale transformer training | -| TorchTitan | PyTorch-compatible framework developed for training at scale | +| Backend | Description | +| -------------- | ------------------------------------------------------------ | +| Megatron | Open-source framework for large-scale transformer training | +| TorchTitan | PyTorch-compatible framework developed for training at scale | +| NeMo AutoModel | NVIDIA-NeMo AutoModel (diffusion: Wan 2.2 T2V); `third_party/Automodel` submodule, installed editable on first run | ## 🖥️ Single Node Training @@ -49,7 +50,7 @@ We recommend using the official [rocm/megatron-lm Docker image](https://hub.dock ```bash # Pull the latest Docker image -docker pull docker.io/rocm/primus:v26.3 +docker pull docker.io/rocm/primus:v26.4 ``` @@ -126,7 +127,7 @@ Multi-node training is launched via **SLURM**. Specify the number of nodes and the model config: ```bash -export DOCKER_IMAGE="docker.io/rocm/primus:v26.3" +export DOCKER_IMAGE="docker.io/rocm/primus:v26.4" export NNODES=8 # Example for megatron llama3.1_8B @@ -208,6 +209,13 @@ The following models are supported out of the box via provided configuration fil | Mixtral-8x7B-v0.1 | [mistralai/Mixtral-8x7B-v0.1 ](https://huggingface.co/mistralai/Mixtral-8x7B-v0.1) | [mixtral_8x7B_v0.1-BF16-pretrain.yaml](https://github.com/AMD-AGI/Primus/blob/main/examples/megatron/configs/MI300X/mixtral_8x7B_v0.1-BF16-pretrain.yaml) | | | Mixtral-8x22B-v0.1 | [mistralai/Mixtral-8x22B-v0.1 ](https://huggingface.co/mistralai/Mixtral-8x22B-v0.1) | [mixtral_8x22B_v0.1-BF16-pretrain.yaml](https://github.com/AMD-AGI/Primus/blob/main/examples/megatron/configs/MI300X/mixtral_8x22B_v0.1-BF16-pretrain.yaml) | | +### Diffusion Models + +- **Flux** - Flow-based diffusion model for text-to-image generation + - Training guide: [examples/megatron/diffusion/README.md](megatron/diffusion/README.md) (Flux 535M and 12B) + - Architecture & developer docs: [docs/04-technical-guides/diffusion-models/README.md](../docs/04-technical-guides/diffusion-models/README.md) + - FP8 training: [docs/04-technical-guides/diffusion-models/fp8_training.md](../docs/04-technical-guides/diffusion-models/fp8_training.md) + --- ### 🏃‍♂️ How to Run a Supported Model @@ -285,7 +293,7 @@ When using the `create` command to start a new training workload, the following | `--gpu` | Number of GPUs | 8 | | `--exp` | Path to experiment (training config) file (required) | — | | `--data_path` | Path to training data | — | -| `--image` | Docker image to use | `docker.io/rocm/primus:v26.3` | +| `--image` | Docker image to use | `docker.io/rocm/primus:v26.4` | | `--hf_token` | HuggingFace token | Read from env var `HF_TOKEN` | | `--workspace` | Workspace name | `primus-safe-pretrain` | | `--nodelist` | Comma-separated list of node hostnames to run on | — | diff --git a/examples/deepseek-v4/benchmark/bench_v4_attention.py b/examples/deepseek-v4/benchmark/bench_v4_attention.py new file mode 100644 index 000000000..45cd76cde --- /dev/null +++ b/examples/deepseek-v4/benchmark/bench_v4_attention.py @@ -0,0 +1,592 @@ +############################################################################### +# Copyright (c) 2025, Advanced Micro Devices, Inc. All rights reserved. +# +# See LICENSE for license information. +############################################################################### + +"""DeepSeek-V4 attention kernel benchmark — all backends in one table. + +Unified benchmark of the **forward** and **backward** V4 attention kernels for +every backend, so there is a single place to compare them (one row per backend, +``ms | TFLOP/s`` cells; TFLOP/s over the useful work so all rows are comparable): + +* ``triton`` — PRODUCTION Triton (separate K/V; pool/SWA/HCA launchers) +* ``gluon`` — fused single-latent (K==V) sparse-MLA, hand-tuned gfx950 +* ``triton_v2`` — fused single-latent sparse-MLA in plain Triton (tl.dot / MFMA) +* ``flydsl_v1`` — fused single-latent sparse-MLA in native FlyDSL MFMA (fwd + bwd) +* ``turbo_flydsl`` — extracted Primus-Turbo sparse_mla_v2 native FlyDSL MFMA +* ``_turbo_flydsl`` — the INTEGRATED in-tree Primus-Turbo backend via the turbo API + (``primus_turbo.flydsl.attention``); the ``turbo`` model backend + (``use_v4_attention_backend`` / ``use_v4_csa_attention_backend = "turbo"``) + +The legacy ``_flydsl_v0_deprecated`` gathered-CSA backend (scalarized GEMV) is +NOT benchmarked: it has known correctness issues and depends on the +/workspace/FlyDSL-amd source tree. + +The fused ``gluon`` / ``triton_v2`` / ``flydsl_v1`` backends share ONE kernel-pair +API and are timed on IDENTICAL V4-form inputs (zero rope pad + [local ++ pool] +kv + [SWA window ++ pool] topk). Each backend is guarded; unavailable ones are +simply skipped. + +Benchmarks for the two production model sizes across all three layer kinds: + +* ``cr=0`` — dense / sliding-window (SWA-only) attention +* ``cr=4`` — CSA (local SWA + sparse top-k from the compressed pool) +* ``cr=128`` — HCA (local SWA + full compressed pool, joint softmax) + +at the real attention shapes (``seq_len=4096``, ``mbs=1``, bf16, sink on, +``swa_window=128``): + +* V4-Flash: H=64, head_dim=512, index_topk=512 +* V4-Pro: H=128, head_dim=512, index_topk=1024 + +Backend detail: + +* ``triton`` (ALL crs): PRODUCTION launchers used by ``DeepseekV4Attention`` — + cr=0/128 ``_launch_v4_attention_fwd``/``_bwd`` (dense/HCA); cr=4 + ``_launch_v4_csa_attention_pool_fwd``/``_pool_bwd`` (split FWD + segreduce + BWD in-kernel gather; NOT the legacy gathered API, ~30-260x slower). +* ``gluon`` / ``triton_v2`` / ``flydsl_v1`` (ALL crs): fused single-latent + sparse-MLA (``sparse_mla_{fwd,bwd}_v4_*``); the layer kind is just a + different TOPK (cr=0: swa 128; cr=4: 128+sparse; cr=128: 128+pool). + +Effective TFLOP/s uses ``2*T*H*TOPK*(D_V+D_V)`` (useful work over head_dim=512), +BWD = 2.5x FWD, the SAME formula for every backend so rows are directly +comparable (the fused backends' zero-rope-pad overhead shows up in ms). + +Run inside the dev container (gfx950 / MI355X): + + python examples/deepseek-v4/benchmark/bench_v4_attention.py + python examples/deepseek-v4/benchmark/bench_v4_attention.py --variant pro --cr 4 +""" + +from __future__ import annotations + +import argparse +import math +import os +import sys +from typing import Tuple + +import torch + +# NOTE: the legacy `_flydsl_v0_deprecated` CSA backend (scalarized GEMV) is +# intentionally NOT imported/benchmarked here — it has known correctness issues +# and depends on the /workspace/FlyDSL-amd source tree. The native FlyDSL MFMA +# backend is `_flydsl_v1` (benchmarked below as `flydsl_v1`). +from primus.backends.megatron.core.transformer.v4_attention_kernels._triton_v1.v4_attention_bwd import ( + _launch_v4_attention_bwd, +) +from primus.backends.megatron.core.transformer.v4_attention_kernels._triton_v1.v4_attention_fwd import ( + _launch_v4_attention_fwd, +) +from primus.backends.megatron.core.transformer.v4_attention_kernels._triton_v1.v4_csa_attention_bwd import ( + _launch_v4_csa_attention_pool_bwd, +) +from primus.backends.megatron.core.transformer.v4_attention_kernels._triton_v1.v4_csa_attention_fwd import ( + _launch_v4_csa_attention_pool_fwd, +) + +# Fused single-latent (K==V) sparse-MLA backends. All share ONE kernel-pair +# API (fwd(q, kv, topk, attn_sink, kv_lora_rank, scale) -> (o, lse); bwd -> +# (dq, dkv, d_sink)) so they are timed on identical V4-form inputs. Each is +# guarded so the benchmark still runs where a backend is unavailable. +_SPARSE_MLA_BACKENDS = {} # name -> (fwd_fn, bwd_fn) +try: + from primus.backends.megatron.core.transformer.v4_attention_kernels._gluon_dsa import ( + sparse_mla_bwd_v4_gluon, + sparse_mla_fwd_v4_gluon, + ) + + _SPARSE_MLA_BACKENDS["gluon"] = (sparse_mla_fwd_v4_gluon, sparse_mla_bwd_v4_gluon) +except Exception: # noqa: BLE001 + pass +try: + from primus.backends.megatron.core.transformer.v4_attention_kernels._triton_v2 import ( + sparse_mla_bwd_v4_triton, + sparse_mla_fwd_v4_triton, + ) + + _SPARSE_MLA_BACKENDS["triton_v2"] = (sparse_mla_fwd_v4_triton, sparse_mla_bwd_v4_triton) +except Exception: # noqa: BLE001 + pass +try: + from primus.backends.megatron.core.transformer.v4_attention_kernels._flydsl_v1 import ( + sparse_mla_bwd_v4_flydsl, + sparse_mla_fwd_v4_flydsl, + ) + + _SPARSE_MLA_BACKENDS["flydsl_v1"] = (sparse_mla_fwd_v4_flydsl, sparse_mla_bwd_v4_flydsl) +except Exception: # noqa: BLE001 + pass +# _turbo_flydsl: the INTEGRATED Primus-Turbo sparse-MLA backend, via the turbo API +# (primus_turbo.flydsl.attention). Not an agent/workspace extraction — this is the +# in-tree `_turbo_flydsl` backend (enabled in the model via +# use_v4_attention_backend / use_v4_csa_attention_backend = "turbo"). Requires an +# installed primus_turbo carrying primus_turbo.flydsl.attention. +try: + from primus.backends.megatron.core.transformer.v4_attention_kernels._turbo_flydsl import ( + sparse_mla_bwd_v4_turbo_flydsl, + sparse_mla_fwd_v4_turbo_flydsl, + ) + + _SPARSE_MLA_BACKENDS["_turbo_flydsl"] = ( + sparse_mla_fwd_v4_turbo_flydsl, + sparse_mla_bwd_v4_turbo_flydsl, + ) +except Exception: # noqa: BLE001 + pass +# gluon_v2: our aiter-gluon-inspired backend (guarded; skipped until present). +try: + from primus.backends.megatron.core.transformer.v4_attention_kernels._gluon_v2 import ( + sparse_mla_bwd_v4_gluon_v2, + sparse_mla_fwd_v4_gluon_v2, + ) + + _SPARSE_MLA_BACKENDS["gluon_v2"] = (sparse_mla_fwd_v4_gluon_v2, sparse_mla_bwd_v4_gluon_v2) +except Exception: # noqa: BLE001 + pass +# gluon_v3: active gfx950 optimization campaign backend. +try: + from primus.backends.megatron.core.transformer.v4_attention_kernels._gluon_v3 import ( + sparse_mla_bwd_v4_gluon_v3, + sparse_mla_fwd_v4_gluon_v3, + ) + + _SPARSE_MLA_BACKENDS["gluon_v3"] = (sparse_mla_fwd_v4_gluon_v3, sparse_mla_bwd_v4_gluon_v3) +except Exception: # noqa: BLE001 + pass +# aiter PR#3833 gluon sparse-MLA prefill (fwd-only) — OPTIONAL reference for +# comparison. Lives under agent/workspace; set PRIMUS_V4_AITER_DIR to override. +# Guarded so the benchmark is unchanged when the extracted adapter is absent. +try: + _aiter_dir = os.environ.get( + "PRIMUS_V4_AITER_DIR", + os.path.join( + os.path.dirname(__file__), + "..", + "..", + "agent", + "workspace", + "aiter_dsv4_prefill_20260706", + ), + ) + _aiter_dir = os.path.abspath(_aiter_dir) + if _aiter_dir not in sys.path: + sys.path.insert(0, _aiter_dir) + from aiter_v4_adapter import sparse_mla_fwd_v4_aiter + + _SPARSE_MLA_BACKENDS["aiter_gluon"] = (sparse_mla_fwd_v4_aiter, None) +except Exception: # noqa: BLE001 + pass + +_GLUON_AVAIL = "gluon" in _SPARSE_MLA_BACKENDS + +_VARIANTS = { + "flash": dict(H=64, index_topk=512), + "pro": dict(H=128, index_topk=1024), +} +_HEAD_DIM = 512 +_SWA_WINDOW = 128 +_ROPE_DIM = 64 # sparse-MLA d_qk = kv_lora_rank (=_HEAD_DIM) + rope +_CR_NAME = {0: "SWA/dense", 4: "CSA", 128: "HCA"} + + +# --------------------------------------------------------------------------- +# Input builders +# --------------------------------------------------------------------------- + + +def _common_inputs(B: int, H: int, S: int, D: int, seed: int = 0): + """q + MQA single-latent K/V (full + [.,1,.,.]) + sink + dout, all bf16.""" + g = torch.Generator(device="cuda").manual_seed(seed) + dev, dt = "cuda", torch.bfloat16 + q = torch.randn(B, H, S, D, generator=g, device=dev, dtype=dt) + k_mqa = torch.randn(B, 1, S, D, generator=g, device=dev, dtype=dt) + v_mqa = torch.randn(B, 1, S, D, generator=g, device=dev, dtype=dt) + k_full = k_mqa.expand(B, H, S, D).contiguous() + v_full = v_mqa.expand(B, H, S, D).contiguous() + sink = torch.randn(H, generator=g, device=dev, dtype=torch.float32) * 0.1 + dout = torch.randn(B, H, S, D, generator=g, device=dev, dtype=dt) + return q, k_mqa, v_mqa, k_full, v_full, sink, dout + + +def _csa_sparse(B: int, H: int, S: int, D: int, K: int, P: int, g: torch.Generator): + """CSA sparse branch: compressed pool + per-query top-K indices, plus the + pre-gathered equivalent (FlyDSL) sharing the same valid/invalid pattern.""" + dev, dt = "cuda", torch.bfloat16 + pool = torch.randn(B, P, D, generator=g, device=dev, dtype=dt) + valid = torch.rand(B, S, K, generator=g, device=dev) > 0.25 + topk_idxs = torch.randint(0, P, (B, S, K), generator=g, device=dev, dtype=torch.int32) + topk_idxs = torch.where(valid, topk_idxs, torch.full_like(topk_idxs, -1)) + safe = topk_idxs.clamp(min=0).long() + gathered = torch.gather( + pool.unsqueeze(1).expand(B, S, P, D), dim=2, index=safe.unsqueeze(-1).expand(B, S, K, D) + ) + gathered = gathered * valid.unsqueeze(-1).to(dt) + sparse_mask = torch.where( + valid, + torch.zeros((), dtype=dt, device=dev), + torch.tensor(float("-inf"), dtype=dt, device=dev), + ) + return pool, topk_idxs, gathered, sparse_mask + + +def _hca_mask(S: int, P: int, ratio: int, device, dtype): + """HCA pool-only additive causal mask [S, P]: pool slot s visible to query t + iff (s+1)*ratio - 1 <= t (matches DeepseekV4Attention._hca_extra_mask).""" + t = torch.arange(S, device=device).unsqueeze(1) + s_end = (torch.arange(P, device=device).unsqueeze(0) + 1) * ratio - 1 + return torch.where(s_end <= t, 0.0, float("-inf")).to(dtype) + + +def _build_gluon_v4form(*, cr: int, H: int, S: int, D: int, K: int, P: int, W: int, seed: int = 0): + """Gluon inputs in the **V4 form** (matches the training adapter): + + The 512 V4 latent (RoPE baked in-place) is the gluon "lora" with a zero rope + pad (kernel needs D_ROPE>0); kv = [local latent ++ pool] and topk = [SWA + window ++ (cr=4: sparse pool top-k | cr=128: full causal pool | cr=0: none)]. + ``topk`` is padded to a multiple of 64 so the gluon bwd dKV tiling (TILE_K=64) + is valid (notably HCA: 128+32=160 -> 192). scale = 1/sqrt(D) (V4, over 512). + """ + g = torch.Generator(device="cuda").manual_seed(seed) + dev, dt = "cuda", torch.bfloat16 + latent = torch.randn(S, D, generator=g, device=dev, dtype=dt) + q512 = torch.randn(S, H, D, generator=g, device=dev, dtype=dt) + z_q = torch.zeros(S, H, _ROPE_DIM, device=dev, dtype=dt) + q_g = torch.cat([q512, z_q], dim=-1).contiguous() # [S, H, D+rope_pad] + sink = torch.randn(H, generator=g, device=dev, dtype=torch.float32) * 0.1 + do = torch.randn(S, H, D, generator=g, device=dev, dtype=dt) + + ti = torch.arange(S, device=dev).view(S, 1) + win = ti - W + 1 + torch.arange(W, device=dev).view(1, W) # [S, W] local token idx + win = torch.where(win >= 0, win, torch.full_like(win, -1)) + + if cr == 0: + kv512 = latent.unsqueeze(1) # [S, 1, D] + topk = win + else: + pool = torch.randn(P, D, generator=g, device=dev, dtype=dt) + kv512 = torch.cat([latent, pool], dim=0).unsqueeze(1) # [S+P, 1, D] + if cr == 4: + sp = torch.randint(0, P, (S, K), generator=g, device=dev) + pool_topk = S + sp + else: # cr == 128: HCA full causal pool + ps = torch.arange(P, device=dev).view(1, P) + vis = ((ps + 1) * cr - 1) <= ti # [S, P] + pool_topk = torch.where(vis, S + ps, torch.full_like(ps.expand(S, P), -1)) + topk = torch.cat([win, pool_topk], dim=1) + + tk = topk.shape[1] + pad = ((tk + 63) // 64) * 64 - tk + if pad > 0: + topk = torch.cat([topk, torch.full((S, pad), -1, device=dev, dtype=topk.dtype)], dim=1) + topk_g = topk.to(torch.int32).contiguous() + + z_kv = torch.zeros(kv512.shape[0], 1, _ROPE_DIM, device=dev, dtype=dt) + kv_g = torch.cat([kv512, z_kv], dim=-1).contiguous() + return q_g, kv_g, topk_g, sink, do + + +# --------------------------------------------------------------------------- +# Timing helpers +# --------------------------------------------------------------------------- + + +def _time_ms(fn, *, warmup: int, iters: int) -> Tuple[float, float]: + """Return (median_ms, mean_ms) over ``iters`` timed launches.""" + for _ in range(warmup): + fn() + torch.cuda.synchronize() + times = [] + start = torch.cuda.Event(enable_timing=True) + end = torch.cuda.Event(enable_timing=True) + for _ in range(iters): + start.record() + fn() + end.record() + torch.cuda.synchronize() + times.append(start.elapsed_time(end)) + times.sort() + return times[len(times) // 2], sum(times) / len(times) + + +def _safe_time(fn, *, warmup: int, iters: int): + """Time ``fn``; on failure return (None, short_error_string).""" + if fn is None: + return None, None + try: + med, _ = _time_ms(fn, warmup=warmup, iters=iters) + return med, None + except Exception as exc: # noqa: BLE001 - benchmark must survive one cell failing + msg = str(exc).strip().splitlines()[-1] if str(exc).strip() else type(exc).__name__ + return None, f"{type(exc).__name__}: {msg}" + + +def _call_fwd(fn): + """Call a fwd launcher once for the bwd's (out, lse); (None, None) on failure.""" + if fn is None: + return None, None + try: + return fn() + except Exception: # noqa: BLE001 + return None, None + + +def _tflops(flops: float, med_ms) -> float: + if med_ms is None or med_ms <= 0: + return float("nan") + return flops / (med_ms * 1e-3) / 1e12 + + +def _cell(med, flops) -> str: + if med is None: + return f"{'FAIL':>18s}" + return f"{med:9.2f} | {_tflops(flops, med):6.1f}" + + +# --------------------------------------------------------------------------- +# Per-(variant, cr) benchmark +# --------------------------------------------------------------------------- + + +def _bench_cr(variant: str, cr: int, *, B: int, S: int, warmup: int, iters: int): + cfg = _VARIANTS[variant] + H = cfg["H"] + D = _HEAD_DIM + scale = 1.0 / math.sqrt(D) + g = torch.Generator(device="cuda").manual_seed(11) + q, k_mqa, v_mqa, k_full, v_full, sink, dout = _common_inputs(B, H, S, D) + + fwd_triton = bwd_triton = None + + if cr == 0: + topk_eff = _SWA_WINDOW + glu_K, glu_P = 0, 0 + extra = "" + + def fwd_triton(): + return _launch_v4_attention_fwd( + q, + k_full, + v_full, + sink=sink, + swa_window=_SWA_WINDOW, + additive_mask=None, + scale=scale, + hca_local_seqlen=0, + ) + + out_t, lse_t = _call_fwd(fwd_triton) + + def bwd_triton(): + return _launch_v4_attention_bwd( + q, + k_full, + v_full, + out_t, + dout, + lse_t, + sink=sink, + swa_window=_SWA_WINDOW, + additive_mask=None, + scale=scale, + hca_local_seqlen=0, + ) + + elif cr == 4: + P = max(S // 4, 1) + K = min(cfg["index_topk"], P) + topk_eff = _SWA_WINDOW + K + glu_K, glu_P = K, P + extra = f" K_topk={K} P={P}" + pool, topk_idxs, gathered, sparse_mask = _csa_sparse(B, H, S, D, K, P, g) + + def fwd_triton(): + return _launch_v4_csa_attention_pool_fwd( + q, + k_full, + v_full, + pool, + topk_idxs, + sink=sink, + swa_window=_SWA_WINDOW, + scale=scale, + ) + + out_t, lse_t = _call_fwd(fwd_triton) + + def bwd_triton(): + return _launch_v4_csa_attention_pool_bwd( + q, + k_full, + v_full, + pool, + topk_idxs, + out_t, + dout, + lse_t, + sink=sink, + swa_window=_SWA_WINDOW, + scale=scale, + ) + + elif cr == 128: + P = max(S // cr, 1) + topk_eff = _SWA_WINDOW + P + glu_K, glu_P = 0, P + extra = f" P={P}" + pool_bh = torch.randn(B, H, P, D, generator=g, device="cuda", dtype=torch.bfloat16) + k_hca = torch.cat([k_full, pool_bh], dim=2).contiguous() + v_hca = torch.cat([v_full, pool_bh], dim=2).contiguous() + hca_mask = _hca_mask(S, P, cr, "cuda", torch.bfloat16) + + def fwd_triton(): + return _launch_v4_attention_fwd( + q, + k_hca, + v_hca, + sink=sink, + swa_window=_SWA_WINDOW, + additive_mask=hca_mask, + scale=scale, + hca_local_seqlen=S, + ) + + out_t, lse_t = _call_fwd(fwd_triton) + + def bwd_triton(): + return _launch_v4_attention_bwd( + q, + k_hca, + v_hca, + out_t, + dout, + lse_t, + sink=sink, + swa_window=_SWA_WINDOW, + additive_mask=hca_mask, + scale=scale, + hca_local_seqlen=S, + ) + + else: + raise ValueError(f"unsupported cr={cr}") + + # Fused single-latent (K==V) sparse-MLA backends (gluon / triton_v2 / + # turbo_flydsl): all on the SAME V4-form inputs (zero rope pad + [local ++ pool] + # kv + [SWA window ++ pool] topk). Time each by swapping the kernel pair. + sparse_fb = {} # name -> (fwd_closure, bwd_closure) + if _SPARSE_MLA_BACKENDS: + gq, gkv, gtopk_idx, gsink, gdo = _build_gluon_v4form( + cr=cr, H=H, S=S, D=D, K=glu_K, P=glu_P, W=_SWA_WINDOW + ) + gscale = 1.0 / math.sqrt(D) # V4 scale (score over 512) + for _name, (_fwd_k, _bwd_k) in _SPARSE_MLA_BACKENDS.items(): + fwd_c = ( + lambda fk: (lambda: fk(gq, gkv, gtopk_idx, attn_sink=gsink, kv_lora_rank=D, scale=gscale)) + )(_fwd_k) + _out, _lse = _call_fwd(fwd_c) + if _out is None: # fwd unavailable (e.g. flydsl_v2 kernel WIP) -> skip bwd + bwd_c = None + else: + bwd_c = ( + lambda bk, o, l: ( + lambda: bk( + gq, gkv, o, gdo, gtopk_idx, l, attn_sink=gsink, kv_lora_rank=D, scale=gscale + ) + ) + )(_bwd_k, _out, _lse) + sparse_fb[_name] = (fwd_c, bwd_c) + + # Effective FLOPs over the USEFUL work (score+value both over head_dim=512, + # TOPK = real key count); BWD = 2.5x FWD. Same formula for ALL backends so + # TFLOP/s is comparable (the sparse-MLA zero-rope-pad overhead shows up in + # ms, not in counted FLOPs). + T = B * S + fwd_flop = 2.0 * T * H * topk_eff * (D + D) + flops = {"fwd": fwd_flop, "bwd": 2.5 * fwd_flop} + + # Backend table: native production Triton (separate K/V), then the fused + # sparse-MLA backends (legacy `_flydsl_v0` gathered CSA is excluded). + backends = [("triton", fwd_triton, bwd_triton)] + for _name in ( + "gluon", + "triton_v2", + "gluon_v2", + "gluon_v3", + "flydsl_v1", + "_turbo_flydsl", + "aiter_gluon", + ): + if _name in sparse_fb: + backends.append((_name, sparse_fb[_name][0], sparse_fb[_name][1])) + + print( + f"\n=== V4-{variant.upper()} cr={cr} ({_CR_NAME[cr]}) | B={B} H={H} S={S} D={D}" + f"{extra} TOPK_eff={topk_eff} swa={_SWA_WINDOW} sink=on bf16 ===\n" + f" FWD GFLOP={fwd_flop / 1e9:.1f} (useful, over head_dim={D}) " + f"(cells: ms | TFLOP/s; *_v2/gluon = V4-form fused single-latent)", + flush=True, + ) + print( + f" {'backend':12s} {'fwd (ms|TF)':>18s} {'bwd (ms|TF)':>18s}", + flush=True, + ) + rows = [] + for name, fwd_fn, bwd_fn in backends: + fwd_med, fwd_err = _safe_time(fwd_fn, warmup=warmup, iters=iters) + bwd_med, bwd_err = _safe_time(bwd_fn, warmup=warmup, iters=iters) + print(f" {name:12s} {_cell(fwd_med, flops['fwd'])} {_cell(bwd_med, flops['bwd'])}", flush=True) + for op, err in (("fwd", fwd_err), ("bwd", bwd_err)): + if err: + print(f" {name} {op} error: {err}", flush=True) + rows.append((name, fwd_med, bwd_med)) + return rows + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--variant", + choices=["flash", "pro", "both"], + default="both", + help="model size to benchmark (default: both)", + ) + parser.add_argument( + "--cr", + choices=["0", "4", "128", "all"], + default="all", + help="compress ratio / layer kind to benchmark (default: all)", + ) + parser.add_argument("--seq", type=int, default=4096, help="sequence length (default 4096)") + parser.add_argument("--mbs", type=int, default=1, help="micro batch size / B (default 1)") + parser.add_argument("--warmup", type=int, default=10, help="warmup launches (default 10)") + parser.add_argument("--iters", type=int, default=30, help="timed launches (default 30)") + args = parser.parse_args() + + if not torch.cuda.is_available(): + raise SystemExit("CUDA / HIP device required for this benchmark") + + # Production-optimal Triton config (matches attention_perf.md P57 defaults): + # split CSA FWD (monolithic OFF), split + segreduce BWD. + os.environ.setdefault("PRIMUS_V4_CSA_FWD_FORCE_MONOLITHIC", "0") + os.environ.setdefault("PRIMUS_V4_ATTN_BWD_USE_SPLIT", "1") + os.environ.setdefault("PRIMUS_V4_CSA_BWD_SEGREDUCE", "1") + + torch.backends.cuda.matmul.allow_tf32 = True + variants = ["flash", "pro"] if args.variant == "both" else [args.variant] + crs = [0, 4, 128] if args.cr == "all" else [int(args.cr)] + + print( + f"device={torch.cuda.get_device_name(0)} torch={torch.__version__} " + f"seq={args.seq} mbs={args.mbs} warmup={args.warmup} iters={args.iters}", + flush=True, + ) + for v in variants: + for cr in crs: + _bench_cr(v, cr, B=args.mbs, S=args.seq, warmup=args.warmup, iters=args.iters) + + +if __name__ == "__main__": + main() diff --git a/examples/deepseek-v4/benchmark/bench_v4_attention_results.md b/examples/deepseek-v4/benchmark/bench_v4_attention_results.md new file mode 100644 index 000000000..6fff4e42f --- /dev/null +++ b/examples/deepseek-v4/benchmark/bench_v4_attention_results.md @@ -0,0 +1,97 @@ +# DeepSeek-V4 Attention — Backend Performance + +Full forward + backward benchmark of every V4 attention backend, produced by +[`bench_v4_attention.py`](./bench_v4_attention.py). + +## Setup + +- **GPU**: AMD Instinct MI355X (gfx950), single GPU +- **Container**: `dev_primus_wenx` +- **Torch**: `2.10.0+git94c6e04`, Triton `3.7.0`, FlyDSL `0.2.2` +- **Primus-Turbo**: `dev/kyle/flydsl_attn_deepseekv4` @ `350ec3f` (native-FlyDSL sparse-MLA v2) +- **Config**: `seq_len=4096`, `mbs=1`, bf16, sink **on**, `swa_window=128` +- **Models**: V4-Flash (`H=64`, index_topk=512), V4-Pro (`H=128`, index_topk=1024) +- **Layer kinds**: `cr=0` dense/SWA, `cr=4` CSA, `cr=128` HCA +- **Timing**: `--warmup 10 --iters 30`, median latency +- **Cell format**: `latency ms | TFLOP/s` + +Raw log: `agent/workspace/_bench_all_final.log` + +## Backends + +| Backend | Description | +|---------|-------------| +| `triton` | Production separate-K/V Triton dense/SWA/HCA + split CSA pool kernels | +| `gluon` | 1st-gen fused single-latent Gluon sparse-MLA (`_gluon_dsa`) | +| `triton_v2` | Fused single-latent sparse-MLA in plain Triton | +| `gluon_v2` | 2nd-gen Gluon sparse-MLA baseline | +| `gluon_v3` | Optimized Gluon sparse-MLA: Round-9 CSA formula-pack + aiter Gluon LSE fwd route (benchmark-only; not wired for training) | +| `flydsl_v1` | In-tree native FlyDSL MFMA sparse-MLA backend | +| `turbo_flydsl` | Extracted Primus-Turbo `sparse_mla_v2` (June `optimize/...` branch) | +| `_turbo_flydsl` | **Integrated** Primus-Turbo native-FlyDSL sparse-MLA via the turbo API (`primus_turbo.flydsl.attention`); the `turbo` model backend | +| `aiter_gluon` | aiter Gluon sparse-MLA prefill reference, fwd-only | + +`aiter_gluon` has no backward implementation, so bwd is shown as `—`. + +## Forward + +`latency ms | TFLOP/s`; **bold** is fastest latency in the row. + +| variant | cr | triton | gluon | triton_v2 | gluon_v2 | gluon_v3 | flydsl_v1 | turbo_flydsl | _turbo_flydsl | aiter_gluon | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| flash | 0 | 0.46 \| 151.0 | 0.31 \| 222.1 | 0.30 \| 230.0 | 0.28 \| 247.8 | 0.28 \| 248.3 | 0.45 \| 153.3 | 0.30 \| 228.7 | **0.20 \| 335.5** | 0.47 \| 145.2 | +| flash | 4 | 1.47 \| 234.3 | 0.89 \| 386.1 | 0.87 \| 397.1 | 0.73 \| 469.0 | 0.66 \| 523.6 | 1.37 \| 250.9 | 0.72 \| 476.6 | **0.53 \| 651.7** | 0.83 \| 413.4 | +| flash | 128 | 0.75 \| 114.7 | 0.38 \| 226.3 | 0.38 \| 223.9 | 0.33 \| 263.9 | 0.33 \| 263.2 | 0.58 \| 147.3 | 0.35 \| 245.5 | **0.22 \| 384.2** | 0.52 \| 164.7 | +| pro | 0 | 0.86 \| 159.5 | 0.57 \| 239.8 | 0.58 \| 236.2 | 0.51 \| 269.1 | 0.51 \| 269.0 | 1.05 \| 131.0 | 0.55 \| 251.9 | **0.38 \| 357.9** | 0.79 \| 173.1 | +| pro | 4 | 4.44 \| 278.3 | 2.91 \| 425.5 | 2.78 \| 444.3 | 2.36 \| 525.0 | 1.92 \| 645.1 | 4.82 \| 256.6 | 2.09 \| 591.0 | **1.41 \| 878.1** | 2.16 \| 573.4 | +| pro | 128 | 1.46 \| 117.7 | 0.72 \| 239.4 | 0.72 \| 238.6 | 0.61 \| 281.2 | 0.61 \| 280.9 | 1.20 \| 143.5 | 0.63 \| 270.7 | **0.43 \| 395.5** | 0.88 \| 195.2 | + +## Backward + +`latency ms | TFLOP/s`; **bold** is fastest latency in the row. + +| variant | cr | triton | gluon | triton_v2 | gluon_v2 | gluon_v3 | flydsl_v1 | turbo_flydsl | _turbo_flydsl | aiter_gluon | +|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|---:| +| flash | 0 | 2.09 \| 82.2 | 1.20 \| 143.4 | 1.16 \| 148.4 | 1.13 \| 152.6 | 1.13 \| 152.0 | 2.20 \| 78.3 | 1.38 \| 124.6 | **0.67 \| 257.8** | — | +| flash | 4 | 5.18 \| 165.8 | 5.18 \| 166.0 | 5.93 \| 144.9 | 4.81 \| 178.5 | 3.99 \| 215.1 | 6.16 \| 139.5 | 3.94 \| 218.1 | **2.55 \| 336.9** | — | +| flash | 128 | 2.86 \| 75.0 | 1.63 \| 131.4 | 1.67 \| 128.8 | 1.54 \| 139.3 | 1.54 \| 139.2 | 2.77 \| 77.5 | 1.84 \| 117.0 | **0.78 \| 274.9** | — | +| pro | 0 | 4.02 \| 85.4 | 1.82 \| 189.0 | 1.81 \| 190.1 | 1.71 \| 201.4 | 1.70 \| 202.2 | 5.69 \| 60.3 | 2.09 \| 164.5 | **1.29 \| 267.2** | — | +| pro | 4 | 15.10 \| 204.8 | 13.48 \| 229.3 | 10.74 \| 287.8 | 8.52 \| 362.9 | 8.52 \| 362.9 | 30.45 \| 101.5 | 9.41 \| 328.7 | **6.32 \| 489.3** | — | +| pro | 128 | 5.55 \| 77.3 | 2.42 \| 177.4 | 2.47 \| 174.2 | 2.27 \| 189.4 | 2.27 \| 189.4 | 6.94 \| 61.9 | 2.91 \| 147.6 | **1.49 \| 288.2** | — | + +## `_turbo_flydsl` (integrated turbo) vs `gluon_v3` (best in-tree) + +`_turbo_flydsl` is the fastest backend on **every** fwd and bwd cell. Speedup over +`gluon_v3` (TFLOP/s ratio): + +| variant | cr | FWD speedup | BWD speedup | +|---|---:|---:|---:| +| flash | 0 | 1.35× | 1.70× | +| flash | 4 | 1.24× | 1.57× | +| flash | 128 | 1.46× | 1.98× | +| pro | 0 | 1.33× | 1.32× | +| pro | 4 | 1.36× | 1.35× | +| pro | 128 | 1.41× | 1.52× | +| **mean** | | **~1.36×** | **~1.57×** | + +## Summary + +- **`_turbo_flydsl`** (the integrated Primus-Turbo native-FlyDSL backend, selectable + in the model via `use_v4_attention_backend = turbo`) is the fastest backend across + all six shapes in both directions — **~1.36× fwd** and **~1.57× bwd** over the best + in-tree backend (`gluon_v3`), and larger margins over `triton_v2`/`gluon_v2`. +- The biggest wins are the CSA `cr=4` forward (pro cr=4: `1.41 ms` / 878 TF vs + gluon_v3 `1.92 ms` / 645 TF) and the backward across the board (flash cr=128 bwd + `0.78 ms` / 275 TF ≈ **2×** gluon_v3's `1.54 ms` / 139 TF). The fully-native FlyDSL + backward is the headline improvement. +- `_turbo_flydsl` also clearly beats the older extracted `turbo_flydsl` (June branch), + chiefly on the backward (e.g. pro cr=4 bwd `6.32 ms` vs `9.41 ms`). +- `gluon_v3` remains the strongest **in-tree** backend but is benchmark-only (not wired + for training); `gluon_v2` is the wired gluon training backend. + +## Reproduce + +```bash +PYTHONPATH= python examples/deepseek-v4/benchmark/bench_v4_attention.py \ + --variant both --cr all --warmup 10 --iters 30 +``` diff --git a/examples/deepseek-v4/projection/README.md b/examples/deepseek-v4/projection/README.md new file mode 100644 index 000000000..6c5b1319e --- /dev/null +++ b/examples/deepseek-v4/projection/README.md @@ -0,0 +1,93 @@ +# DeepSeek-V4 Performance Projection + +A trace-driven performance projection toolkit + static website for DeepSeek-V4 +(Flash / Pro) training on AMD Instinct GPUs (MI355X measured, MI455X projected). + +## Idea in one paragraph + +We profile a **single transformer layer** of a given compression-ratio (`cr`) +type on **MI355X**, extract a clean forward / backward **time + TFLOPs breakdown** +per module (attention sub-modules, MoE sub-modules), plus the model's non-layer +parts (embedding, output/logits, loss) and the optimizer step. We emit one JSON +per model variant. A static website then loads that JSON and, given a target GPU +and a distributed strategy (PP / VPP / EP / DP / CP), reconstructs the full model +(real layer count + `cr` schedule), models the PP bubble, EP dispatch/combine, +recompute, and the optimizer, and derives **iteration time, TFLOP/s/GPU and +tokens/s/GPU** step by step. Page 1 is the MI355X projection; page 2 scales the +breakdown to MI455X by theoretical ratios. + +## Pipeline + +``` +run profiling script (per cr) -> chrome trace JSON (rank 0) + | | + | v + | tools/parse_trace.py (+ kernel/module map) + v | + one trace per cr type {0,4,128} v + breakdown JSON (site/data/.json) + | + v + static site (site/index.html) + - model config view + - per-cr fwd/bwd breakdown tables + - GPU + parallelism controls + - step-by-step iter-time / TFLOPs / tok/s derivation + - iteration timeline (3 levels: layer / PP ranks / schedule) + - MI355X page + MI455X scaled page +``` + +## Directory layout + +``` +examples/deepseek-v4/projection/ + README.md # this file + design/ # methodology, assumptions, schema, math (the spec) + 01-overview.md + 02-assumptions.md + 03-json-schema.md + 04-projection-math.md + 07-iteration-timeline.md # 3-level iteration-time composition view + script/ # profiling launchers (one trace per cr) + deepseek_v4_layer_trace-projection.sh + tools/ # trace -> breakdown JSON + parse_trace.py + kernel_module_map.py + site/ # static website (no build step) + index.html + assets/app.js + assets/style.css + data/.json # breakdown JSON consumed by the site +``` + +## Quick start + +1. Profile each `cr` on MI355X (run inside the training container): + + ```bash + # one trace per cr type; 1 layer, seq 4096, adam + dist-opt, GA=2, no recompute + CR=0 ./examples/deepseek-v4/projection/script/deepseek_v4_layer_trace-projection.sh + CR=4 ./examples/deepseek-v4/projection/script/deepseek_v4_layer_trace-projection.sh + CR=128 ./examples/deepseek-v4/projection/script/deepseek_v4_layer_trace-projection.sh + ``` + +2. Build the breakdown JSON from the three traces: + + ```bash + python3 examples/deepseek-v4/projection/tools/parse_trace.py \ + --model pro \ + --trace cr0= \ + --trace cr4= \ + --trace cr128= \ + --out examples/deepseek-v4/projection/site/data/pro.json + ``` + +3. Open the site locally: + + ```bash + python3 -m http.server -d examples/deepseek-v4/projection/site 8000 + # http://localhost:8000/?model=pro + ``` + +See `design/` for the full methodology and the exact assumptions baked into the +projection math. diff --git a/examples/deepseek-v4/projection/design/01-overview.md b/examples/deepseek-v4/projection/design/01-overview.md new file mode 100644 index 000000000..eed63aac8 --- /dev/null +++ b/examples/deepseek-v4/projection/design/01-overview.md @@ -0,0 +1,91 @@ +# 01 — Overview & methodology + +## Goal + +Project DeepSeek-V4 (Flash / Pro) training throughput (iteration time, +TFLOP/s/GPU, tokens/s/GPU) for a real multi-hundred-GPU job, from a small set of +**single-layer** MI355X traces, then scale the result to MI455X by theoretical +hardware ratios. + +## Why single-layer, per-cr traces + +DeepSeek-V4 layers come in three attention flavours selected by the per-layer +`compress_ratio` (`cr`): + +| cr | attention branch | +|------|-----------------------------------------------| +| 0 | dense + sliding-window attention (SWA) | +| 4 | CSA (compressed sparse attention, top-k via Indexer) | +| 128 | HCA (compressed attention, full pool visibility) | + +The MoE block is **identical across all `cr`** (cr only changes attention). So +three single-layer traces — one per cr — fully characterise the per-layer cost, +and the full model is `Σ over layers (attention[cr_of_layer] + moe)`. + +Profiling a *single* layer per cr (instead of a full model) keeps: +- **memory** within the MI355X budget at the production `seq=4096`, and +- **attribution clean**: with one cr in the trace, the dense attention kernels + (which are shared across cr types in a multi-cr run) belong unambiguously to + that cr. + +## What we measure vs what we model + +**Measured on MI355X (from traces):** +- per-module forward / backward time for one layer of each cr, +- TFLOPs for the three compute-bound kernel classes (`gemm`, `grouped_gemm`, + `attn`); everything else is treated as memory-bound, +- embedding, output/logits, loss (the non-layer parts), taken once, +- the optimizer-step cost per unit parameter (for scaling). + +**Modeled on top (in the website):** +- full layer count and exact `cr` schedule (Flash 43L, Pro 61L), +- PP / VPP partitioning -> per-stage critical path + pipeline bubble, +- EP dispatch/combine cost (no overlap in current stack), +- gradient-accumulation (GA) and DP behaviour (DP/PP comm assumed hidden), +- activation recompute (add a forward pass to the recomputed layers' backward), +- optimizer step scaled to per-rank parameter count for the target sharding, +- MI455X = MI355X breakdown scaled by compute / memory-bandwidth ratios. + +## Capture configuration (the projection trace) + +The profiling script (`script/deepseek_v4_layer_trace-projection.sh`) deliberately +differs from `run_deepseek_v4_pro_muon.sh`: + +| knob | projection value | why | +|-------------------|------------------|-----| +| `seq_length` | 4096 | production per-microbatch token count | +| `num_layers` | 1 | clean per-layer attribution; fits memory at seq 4096 | +| `compress_ratios` | `[CR]` | one cr per trace | +| optimizer | `adam` + distributed optimizer | compute is optimizer-independent; dist-opt = zero1 | +| overlap grad/param | off | num_layers=1 breaks Megatron's chained param-sync; compute already clean | +| `global_batch_size` | `2 * DP` | GA=2 (see below) | +| recompute | off (`recompute_num_layers 0`) | capture pure fwd / pure bwd | +| profiler | on, `with_stack=True`, window iter 6->7 | map kernels -> nn.module | + +### Why overlap-off + GA=2 + take min + +We capture with the distributed optimizer's comm overlap **off**. Two reasons: + +- with `num_layers=1`, Megatron's chained param-gather sync trips an assertion + (`param_and_grad_buffer.start_param_sync`) when overlap is on; and +- with overlap off, **every microbatch's compute is already overlap-free** — + exactly the clean per-kernel time we want. In a real run GA is large so the + vast majority of microbatches are clean anyway, and the projection assumes DP + comm is hidden (A2), so there is no need to measure the contaminated overlap. + +GA=2 (two microbatches) plus a late steady profiler window lets the parser +compute per-microbatch time as `sum(kernel_durations) / num_microbatches`. +This keeps scalar control-flow stalls (for example Indexer top-k syncs) that the +full-model calibration shows are real per-layer costs, while avoiding warm-up +and autotune iterations. + +## Non-overlap assumptions in the current stack + +- **No MoE deepep-comm + grouped-gemm overlap.** dispatch + grouped_gemm + + combine are summed directly. +- **EP dispatch/combine has no overlap** with compute; counted in full. +- **DP / PP comm assumed hidden** (only the PP bubble remains). Optimistic; first + thing to revisit at calibration time. + +See `02-assumptions.md` for the complete list, and `04-projection-math.md` for +the formulas. diff --git a/examples/deepseek-v4/projection/design/02-assumptions.md b/examples/deepseek-v4/projection/design/02-assumptions.md new file mode 100644 index 000000000..c86e751d8 --- /dev/null +++ b/examples/deepseek-v4/projection/design/02-assumptions.md @@ -0,0 +1,108 @@ +# 02 — Assumptions (single source of truth) + +Every assumption baked into the projection. When a projection number looks off, +start here. + +## Optimizer / DP + +- **A1.** Production optimizer modeled = **AdamW + distributed optimizer (zero1)**. + Muon is out of scope. NOTE on **capture**: the trace itself is taken with + `use_distributed_optimizer=False` (+ fp32 states) because with dist-opt ON the + ROCm Kineto profiler drops the compute GPU kernels for pure dense(cr=0)/HCA + (cr=128) layers (CSA cr=4 is unaffected). dist-opt does not change the fwd/bwd + compute, so this only affects which kernels Kineto records; the optimizer step + is modeled analytically regardless (A3). +- **A2.** DP communication (param all-gather / grad reduce-scatter) is **fully + hidden** behind compute at large GA. We do not add a DP comm term. *(Optimistic; + primary calibration target.)* +- **A3.** The optimizer step is a per-iteration term, **not** multiplied by GA or + replicated per PP microbatch. It scales with **per-rank optimizer parameter + count**: full-model params are first averaged over PP/TP ownership, then + sharded over DP under ZeRO-1. CP does not shard parameters. The modeled Adam + traffic uses the full mixed-precision read/write cost per parameter and is + treated as memory-bound. + +## Pipeline / parallelism + +- **A4.** PP point-to-point comm is **hidden**; only the pipeline **bubble** + remains. CP/TP comm not modeled in v1 (TP=1, CP=1 in the V4 release configs). +- **A5.** Pipeline bubble fraction uses 1F1B: `(PP-1)/GA`; interleaved VPP divides + it by the VPP degree: `(PP-1)/(GA*VPP)`. +- **A6.** Per-stage time is the **sum of the specific cr-type layers** mapped to + that stage; the iteration critical path is driven by the **max** (slowest) + stage, plus embedding on stage 0 and output/loss on the last stage. + +## EP / MoE + +- **A7.** EP dispatch/combine has **no overlap** with compute (current stack) and + is counted in full, every microbatch. +- **A8.** No MoE deepep-comm + grouped-gemm overlap; MoE = dispatch + grouped_gemm + + combine summed. +- **A9.** EP is intra-node only (e.g. EP=8 within an 8-GPU node). Cross-node EP is + out of scope for v1; if EP spans nodes the dispatch/combine cost model must + change (RDMA, different bandwidth). +- **A10.** MoE per-layer cost is `cr`-independent; the three single-cr traces + must agree on it (cross-check). The site uses one MoE breakdown for all layers. + +## Trace capture / attribution + +- **A11.** One trace per cr (`0`, `4`, `128`), **1 layer**, `seq=4096`, + `recompute off`, profiler window iter 6->7 (post warmup/autotune). +- **A12.** Capture with comm-overlap **off** (`num_layers=1` breaks Megatron's + chained param sync, and compute is already clean without overlap). GA=2; clean + per-kernel time = `min` over launches grouped by `(module, phase, shape)`, + removing residual jitter. +- **A13.** Kernel -> nn.module attribution uses `with_stack=True`: GPU kernels are + linked to their launching CPU op via trace flow events, and the module is read + from the CPU op's python call stack. **fwd/bwd phase** is determined by, in + priority order: (1) a `_fwd_`/`_bwd_` tag in the kernel name; (2) for linked + kernels, whether the launching CPU op's timestamp lies inside an + `autograd::engine::evaluate_function` interval (= backward); (3) for unlinked + kernels (no `External id`), whether the kernel's GPU timestamp lies in the + backward GPU-time window reconstructed from the linked backward kernels. The + old rule (default-to-forward when unlinked) leaked backward compute -- incl. + the MoE dgrad/wgrad grouped GEMMs -- into forward; see `design/06`. One-off + device stalls billed to a compute kernel (> `_MAX_PLAUSIBLE_LAUNCH_US`) are + dropped as artifacts and reported in `provenance.dropped_stall_us_per_mb`. +- **A14.** Only `gemm`, `grouped_gemm`, `attn` kernels get a TFLOPs number; all + other kernels are memory-bound (TFLOPs = null) and contribute time only. +- **A15.** Embedding / output-logits / loss are taken **once** (from any single + trace), not triple-counted across the three cr traces. + +## Recompute + +- **A16.** Traces are captured with recompute **off** (pure fwd, pure bwd). At + projection time, a recomputed layer's backward gets `+1 forward` of that layer + added back. Recompute selection (#layers / which) is a site control. + +## MTP + +- **A17.** MTP is modeled analytically when `mtp_num_layers > 0`. Each MTP + depth uses `mtp_compress_ratios` (default cr=4, matching the current Flash + Megatron FLOPs anchor), plus the per-depth `eh_proj`, extra logits/loss, and + HyperHead FLOPs. Timing is an approximation: the MTP inner layer reuses the + measured layer time for that cr, while `eh_proj` is scaled from output-GEMM + throughput. A dedicated MTP trace remains the next calibration step. + +## Scope exclusions (v1) + +- **A18.** No cross-node EP; no TP; no CP comm modeling. +- **A19.** FP8 / MXFP8 not modeled; BF16 only. + +## MI355X -> MI455X scaling + +- **A20.** Compute-bound kernels (`gemm`, `grouped_gemm`, `attn`) scale by the + **peak-TFLOPs ratio** (BF16) `t_mi355 / t_mi455`. +- **A21.** Memory-bound kernels (everything else, incl. optimizer) scale by the + **HBM-bandwidth ratio** `bw_mi355 / bw_mi455`. +- **A22.** A single tunable **efficiency factor** (default 1.0) multiplies the + scaled compute time to account for MFU differences on new HW (flat peak ratio + is optimistic). +- **A23.** Comm (EP/DP/PP) is not rescaled in v1 (intra-node EP only; DP/PP + hidden). + +## Validation + +- **A24.** Self-consistency: configured to the trace scenario (PP=1, EP=8, + measured GA, single node) the model must reproduce the measured single-node + iteration time. Multi-node calibration is deferred. diff --git a/examples/deepseek-v4/projection/design/03-json-schema.md b/examples/deepseek-v4/projection/design/03-json-schema.md new file mode 100644 index 000000000..3f1b7b8cb --- /dev/null +++ b/examples/deepseek-v4/projection/design/03-json-schema.md @@ -0,0 +1,117 @@ +# 03 — Breakdown JSON schema + +One JSON per model variant (`flash` / `pro`), written to +`site/data/.json`, consumed by the website. All times in **microseconds +(us)**; all FLOP counts are per single layer / per single invocation at the +captured `seq` and `micro_batch_size`. + +## Top level + +```jsonc +{ + "schema_version": 1, + "model": "pro", // "flash" | "pro" + "generated_at": "2026-06-18T11:00:00Z", + "provenance": { + "commit": "dac0a60c", + "host": "node001", + "container": "dev_primus_wenx", + "traces": { "cr0": "", "cr4": "", "cr128": "" } + }, + + "capture": { // how the trace was taken (the unit) + "gpu": "MI355X", + "seq_length": 4096, + "micro_batch_size": 1, + "tokens_per_microbatch": 4096, // seq_length * micro_batch_size + "ep": 8, + "ga_for_capture": 2, + "optimizer": "adam", + "distributed_optimizer": true, + "recompute": "off" + }, + + "model_config": { // shown on the site's config panel + "num_layers": 61, + "hidden_size": 7168, + "num_attention_heads": 128, + "kv_channels": 512, + "num_experts": 384, + "moe_router_topk": 6, + "moe_ffn_hidden_size": 3072, + "index_topk": 1024, + "vocab_size": 129280, + "compress_ratios": [128,128,4, /* ... */ ,0], + "cr_layer_counts": { "0": 1, "4": 29, "128": 31 } // derived from compress_ratios + }, + + "hardware": { // for MI355->MI455 scaling (A20-A23) + "MI355X": { "peak_tflops_bf16": 2300, "hbm_bandwidth_gbps": 8000 }, + "MI455X": { "peak_tflops_bf16": 4600, "hbm_bandwidth_gbps": 16000 } + }, + + "layers": { // per-cr breakdown + "0": { "attention": , "moe": }, + "4": { "attention": , "moe": }, + "128": { "attention": , "moe": } + }, + + "non_layer": { // taken once (A15) + "embedding": , + "output": , // final norm + lm_head/logits + "loss": + }, + + "optimizer": { // per-iteration term (A3) + "type": "adam", + "measured_params": 123456789, // params updated in the 1-layer trace, this rank + "time_us": 850.0, // measured optimizer-step time for those params + "bytes_per_param": 18, // bf16 master-param-remainder adam state bytes + "class": "memory_bound" + }, + + "comm": { // per-microbatch EP cost (A7-A9), per layer + "ep_dispatch_us": 0.0, + "ep_combine_us": 0.0 + } +} +``` + +## `` object + +A phase-split list of modules. Each module is one row group; the site renders +forward left-to-right and backward right-to-left. + +```jsonc +{ + "forward": [ , ... ], + "backward": [ , ... ] +} +``` + +## `` object + +```jsonc +{ + "module": "attn.core", // logical module name (from kernel/module map) + "time_us": 740.0, // clean min-grouped time for this module/phase + "class": "compute_bound", // "compute_bound" | "memory_bound" + "flop_class": "attn", // "gemm" | "grouped_gemm" | "attn" | null + "flops": 1.84e12, // total FLOPs for this module/phase, or null + "tflops": 740.0, // achieved TFLOP/s = flops / time_s / 1e12 (null if memory_bound) + "kernels": [ // optional: contributing kernels (debug / drill-down) + { "name": "_v4_attention_fwd_kernel", "time_us": 500.0, "launches": 1 } + ] +} +``` + +## Notes + +- `time_us` is always the **clean** (min-grouped, overlap-free) time per A12. +- `tflops` is present only when `flop_class != null` (A14). +- The site computes everything else (full model, PP/EP/DP, MI455) from this JSON; + the JSON itself is hardware-MI355X, single-layer, single-microbatch ground + truth + static config. +- `cr_layer_counts` is derived from `compress_ratios` by the parser so the site + doesn't re-parse the schedule. +- For Flash, `model` = `"flash"`, `cr_layer_counts` e.g. `{"0":3,"4":20,"128":20}`. diff --git a/examples/deepseek-v4/projection/design/04-projection-math.md b/examples/deepseek-v4/projection/design/04-projection-math.md new file mode 100644 index 000000000..fbe192bcf --- /dev/null +++ b/examples/deepseek-v4/projection/design/04-projection-math.md @@ -0,0 +1,193 @@ +# 04 — Projection math + +This is the exact derivation the website implements (`site/assets/app.js`). All +inputs come from the breakdown JSON (`03-json-schema.md`); all assumptions are in +`02-assumptions.md`. Times in seconds unless noted. + +Notation: +- `cr ∈ {0, 4, 128}`, `n[cr]` = number of layers of that cr (`cr_layer_counts`). +- A `` has `forward` / `backward` module lists. Define + `T(bd, phase) = Σ_module bd[phase][m].time` (sum of clean module times). + +## Step 0 — per-layer and non-layer base times (MI355X) + +For each cr: +``` +layer_fwd[cr] = T(attention[cr], forward) + T(moe, forward) +layer_bwd[cr] = T(attention[cr], backward) + T(moe, backward) +``` +EP dispatch/combine are included as memory-bound module rows inside `moe` +(A7/A8), so they are already in these sums — do not add `comm` again (the `comm` +field is informational for the UI). + +Non-layer (taken once, A15): +``` +emb_fwd = T(embedding, forward); emb_bwd = T(embedding, backward) +out_fwd = T(output, forward) + T(loss, forward) +out_bwd = T(output, backward) + T(loss, backward) +``` + +MTP (if `mtp_num_layers > 0`, A17): +``` +mtp_inner_fwd = layer_fwd[mtp_cr] ; mtp_inner_bwd = layer_bwd[mtp_cr] +mtp_out_fwd = out_fwd ; mtp_out_bwd = out_bwd +mtp_eh_time ≈ output_time * (mtp_eh_proj_flops / output_flops) + +mtp_fwd = mtp_num_layers * (mtp_inner_fwd + mtp_out_fwd) + mtp_eh_time / 3 +mtp_bwd = mtp_num_layers * (mtp_inner_bwd + mtp_out_bwd) + 2 * mtp_eh_time / 3 +``` +The current Flash Megatron FLOPs anchor uses `mtp_cr=4`; a future dedicated MTP +trace can replace the `eh_proj` and inner-layer approximation. + +## Step 0b — manual layer-time mode (optional, UI-only) + +The site exposes a **layer-timing mode** toggle in the controls panel: + +- `trace` (default): `layer_fwd/bwd[cr]` come from the breakdown JSON exactly as + in Step 0. +- `manual`: the user types `layer_fwd[cr]` / `layer_bwd[cr]` directly (one + fwd/bwd pair per `cr ∈ {0,4,128}`, in µs) and those values replace the + trace-derived per-layer times. This is a what-if calculator: you supply the + per-layer cost and the site reuses Steps 1-5 unchanged to derive the full-model + iteration time, bubble, optimizer, tokens/s and TFLOP/s. + +Rules baked into the implementation: + +- **Granularity is per-`cr`** (same as the data model — `cr` only changes + attention, the MoE block is shared), not per physical layer. The `cr` schedule, + PP/VPP layout and recompute still expand these per-cr times to the full model. +- **Per-GPU storage.** A hand-entered time already targets one GPU, so manual + values are stored separately for MI355X and MI455X and the + MI355→MI455 scaling (Step 6) is **bypassed** in manual mode. Switching the GPU + tab edits that GPU's own set. +- **Prefill from trace.** Entering manual mode (or switching GPU within it) + seeds any unset field with the current trace-derived value, so toggling never + changes the result until you actually edit a number. Unset/blank fields keep + falling back to trace. +- **Time only, FLOPs stay analytic.** Manual input overrides time but not FLOPs; + `TFLOP/s/GPU` continues to use the V4 analytic model FLOPs (Step 5), so it + stays meaningful. +- **Scope.** Manual covers the three per-cr decoder layers **and** the non-layer + parts — embedding (PP stage 0), output / loss / MTP (last PP stage) — each as a + fwd/bwd pair. Any field left unset falls back to its trace-derived value, so you + can override just the parts you care about. FLOPs stay analytic regardless. + +## Step 1 — recompute (A16) + +If a layer is recomputed, its backward replays one forward: +``` +layer_bwd_eff[cr] = layer_bwd[cr] + recompute_factor[cr] * layer_fwd[cr] +``` +`recompute_factor` ∈ {0,1} per layer. The site exposes `none`, `full`, and +`first-n`; `first-n` adds one forward replay to the first N decoder layers owned +by each physical PP stage, matching the common Megatron +`recompute_num_layers=N` block pattern. + +## Step 2 — map layers to PP stages / VPP chunks (A6) + +Inputs: `PP`, `VPP`, optional `pipeline_layout`. Total model chunks +`C = PP * VPP`. If `pipeline_layout` is provided, parse Megatron-style `t` / +`t*N` stage specs (for example `Et*10|t*11|t*11|t*11mL`) and assign virtual +chunk `k` to device `k mod PP`. Otherwise build the ordered layer list from +`compress_ratios`, slice it into `C` contiguous chunks, and use the same +`k mod PP` mapping. The UI validates that an explicit layout has exactly +`PP*VPP` stages and exactly `num_layers` decoder layers; invalid layouts block +projection instead of silently falling back. For device `d`: +``` +Df[d] = Σ_{chunks on d} Σ_{layer in chunk} layer_fwd[cr(layer)] +Db[d] = Σ_{chunks on d} Σ_{layer in chunk} layer_bwd_eff[cr(layer)] +``` +Add non-layer parts to their devices: +``` +Df[0] += emb_fwd ; Db[0] += emb_bwd +Df[PP-1] += out_fwd ; Db[PP-1] += out_bwd +Df[PP-1] += mtp_fwd ; Db[PP-1] += mtp_bwd +``` +Critical device: +``` +Df_crit = max_d Df[d] ; Db_crit = max_d Db[d] +``` +(Using per-device max is an upper-bound for imbalanced stages; for a balanced +schedule it is exact.) + +## Step 3 — pipeline iteration time (A4/A5) + +With gradient accumulation `GA` microbatches and interleaved VPP, the steady +1F1B time on the critical device is: +``` +pipe_compute = (GA + (PP - 1) / VPP) * (Df_crit + Db_crit) +``` +- `GA * (Df_crit + Db_crit)` is the steady throughput term; +- `(PP-1)/VPP * (Df_crit + Db_crit)` is the bubble (fraction `(PP-1)/(GA*VPP)`). +- PP=1 ⇒ `pipe_compute = GA * (Df_crit + Db_crit)` (no bubble). + +`GA = GBS / (DP * MBS)`. The site takes `GBS`, `MBS`, `DP` as inputs (or derives +`DP = world_size / (PP * TP * CP)` with EP ⊆ DP). + +## Step 4 — optimizer step (A1/A3) + +Per-iteration, once, zero1-sharded, memory-bound: +``` +local_model_params = total_params / (PP * TP) +per_rank_opt_params = local_model_params / DP +opt_bytes = per_rank_opt_params * bytes_per_param +opt_time = opt_bytes / hbm_bandwidth / opt_efficiency +``` +`total_params` is computed from `model_config` for the full model (dense + +expert params + untied embedding/output). PP/TP determine the average local +model-parameter ownership; CP does not shard parameters. EP is represented in +the full expert count and cancels with the data-replica count for ZeRO-1 +optimizer sharding, so the average optimizer shard is `total/(PP*TP*DP)`. +`bytes_per_param` is the full Adam mixed-precision step traffic (default 30B: +reads + writes), and `opt_efficiency` is tunable. The measured +`optimizer.time_us` carried in the JSON is displayed as a sanity reference. + +## Step 5 — totals (A2/A4: DP & PP comm hidden) + +``` +iter_time = pipe_compute + opt_time +``` +Throughput: +``` +tokens_per_iter = GBS * seq_length (= GA * DP * MBS * seq) +tokens_per_s = tokens_per_iter / iter_time +tokens_per_s_per_gpu = tokens_per_s / world_size +``` +TFLOP/s/GPU (matmul-flops convention, A14): per-microbatch model compute FLOPs +``` +F_mb = Σ_cr n[cr] * (flops_fwd[cr] + flops_bwd[cr]) + nonlayer_flops + where flops_*[cr] = Σ_module (module.flops or 0) over the cr breakdown + + mtp_inner + mtp_eh_proj + mtp_extra_logits + mtp_hc_head +flops_per_iter = F_mb * GA * DP (all microbatches, all DP replicas) +TFLOP_s_per_gpu = flops_per_iter / iter_time / world_size / 1e12 +``` +`tokens_per_s_per_gpu` is the headline metric (independent of FLOP convention); +`TFLOP/s/GPU` is reported for comparison with Primus' own logging. + +## Step 6 — MI455X scaling (A20-A23) + +Rescale every module time before re-running Steps 0-5: +``` +ratio_compute = peak_tflops_bf16[MI355] / peak_tflops_bf16[MI455] +ratio_memory = hbm_bandwidth[MI355] / hbm_bandwidth[MI455] + +time'(module) = module.time * ratio_compute / compute_efficiency if compute_bound + = module.time * ratio_memory if memory_bound +``` +`compute_efficiency` (default 1.0) is the MFU knob (A22). FLOPs are unchanged +(same math), so MI455 `tflops` rises by `1/ratio_compute * compute_efficiency`. +Optimizer scales by `ratio_memory`. Comm not rescaled (A23). + +## Step 7 — self-consistency check (A24) + +Configure `PP=1, VPP=1, EP=8, DP=1, MBS=1, GA=GA_capture` and confirm the model's +`iter_time` matches the measured single-node iteration time within tolerance. The +site shows this check on the MI355X page when capture metadata is present. + +## Worked control set (website inputs) + +GPU page (MI355X / MI455X), then: `world_size`, `PP`, `VPP`, `EP`, `DP` (or +derive), `CP`, `TP`, `MBS`, `GBS` (or `GA`), recompute mode, and the tunables +`opt_efficiency`, `compute_efficiency`, `bytes_per_param`. Every intermediate +(`layer_fwd/bwd`, `Df/Db` per stage, `pipe_compute`, bubble %, `opt_time`, +`iter_time`, `tokens/s/gpu`, `TFLOP/s/gpu`) is displayed step by step. diff --git a/examples/deepseek-v4/projection/design/05-deployment.md b/examples/deepseek-v4/projection/design/05-deployment.md new file mode 100644 index 000000000..3ca252ada --- /dev/null +++ b/examples/deepseek-v4/projection/design/05-deployment.md @@ -0,0 +1,58 @@ +# 05 — Deployment (GitHub Pages) + +The repository already publishes a single GitHub Pages site from `main` via +`.github/workflows/deploy-backend-gap-dashboard.yml`, which builds a bundle with +`tools/backend_gap_report/build_site_bundle.py` and deploys it with +`actions/deploy-pages`. A repo can only serve one Pages site, so the projection +site is published as a **subpath of that same bundle** rather than as a separate +deployment. + +## How it is wired + +`build_site_bundle.py` copies `examples/deepseek-v4/projection/site/` into the bundle at +`deepseek-v4-projection/` (after the backend-gap bundle is built, before +validation). The projection site uses only relative asset/data paths +(`./assets/...`, `./data/...`), so it works unchanged under a subpath. + +Result URL: + +``` +https://.github.io//deepseek-v4-projection/?model=pro +https://.github.io//deepseek-v4-projection/?model=flash +``` + +## Triggering a deploy + +The Pages workflow triggers on pushes to `main` touching its `paths:` list +(currently `docs/backend-gap/**`, `docs/weekly_reports/**`, +`docs/monthly_reports/**`, `tools/backend_gap_report/**`, and the workflow file). + +- The change to `tools/backend_gap_report/build_site_bundle.py` in this work is + itself under a watched path, so the **first** merge to `main` will rebuild and + publish the projection site automatically. +- To make **projection-only** changes (new `site/data/*.json`, site tweaks) also + auto-deploy, add the projection path to the workflow's `paths:` on `main`: + + ```yaml + # .github/workflows/deploy-backend-gap-dashboard.yml (on: push: paths:) + - "examples/deepseek-v4/projection/site/**" + ``` + +- Or trigger manually: the workflow has `workflow_dispatch` (Run workflow button). + +No `.nojekyll` is needed: the bundle is uploaded as a Pages artifact and served +directly (no Jekyll processing), and no asset path starts with `_`. + +## Local preview + +```bash +python3 -m http.server -d examples/deepseek-v4/projection/site 8011 +# http://localhost:8011/?model=pro +``` + +## Bundle-build smoke (optional, needs pandoc/weasyprint for the backend-gap PDFs) + +```bash +python3 tools/backend_gap_report/build_site_bundle.py --output-dir /tmp/primus-site +ls /tmp/primus-site/deepseek-v4-projection/ # index.html, assets/, data/ +``` diff --git a/examples/deepseek-v4/projection/design/06-calibration.md b/examples/deepseek-v4/projection/design/06-calibration.md new file mode 100644 index 000000000..72373ef03 --- /dev/null +++ b/examples/deepseek-v4/projection/design/06-calibration.md @@ -0,0 +1,73 @@ +# 06 — Calibration (single-node, measured) + +The projection is anchored to a real single-node run so its iteration time and +TFLOP/s line up with what Megatron reports. + +## FLOPs: ported V4 closed-form (exact) + +`tools/v4_flops.py` ports Megatron's `deepseek_v4_flops_patches` closed form. +Self-test against the measured flash 16-layer run (GBS64, seq4096): + +| component | analytic (TFLOP/gb) | measured (TFLOP/gb) | +|---|---:|---:| +| attn_qkv_o | 10766.4 | 10766.4 | +| attn_scores | 2476.0 | 2476.0 | +| compressor | 527.8 | 527.8 | +| indexer | 1650.9 | 1650.9 | +| moe | 17838.5 | 17818.7 | +| logits | 832.9 | 833.7 | +| **TOTAL** | **34112** | **34093** (0.05%) | + +The site uses these analytic FLOPs (per cr-layer, B=1, capture seq) for TFLOP/s, +matching Megatron's convention (fwd+bwd × FMA = 6×, recompute excluded). The +breakdown JSON carries them in `analytic_flops`. + +## Iteration time: single-layer → full-model bias + +Measured single-node anchor (`script/_calibrate_flash.sh`, full flash): + +| knob | value | +|---|---| +| layers / cr | 16, cr=[0×3, 4×6, 128×7] | +| parallel | PP1 / EP8 / DP8 (world 8), TP1/CP1 | +| GBS / GA / MBS / seq | 64 / 8 / 1 / 4096 | +| recompute | full (uniform, 1) | +| optimizer | adam + distributed optimizer | + +**Measured**: iter ≈ 6665 ms, 636 TFLOP/s/GPU, ~4917 tokens/s/GPU. + +**Projection (raw, calibFactor=1.0)**: iter 7177 ms (+7.7%), 586 TFLOP/s/GPU +(−7.9%). The per-layer time captured from the single-layer profile runs ~7-8% +high vs a layer inside the full model (single-layer capture has no neighbour- +layer overlap / cache reuse, and per-launch overhead is a larger share). This is +a systematic, near-constant bias. + +**Calibration**: a single `calibFactor = 0.93` on the pipeline compute time +brings it in line: + +| metric | measured | projection (calibFactor 0.93) | +|---|---:|---:| +| iter time | 6665 ms | ~6680 ms (+0.2%) | +| TFLOP/s/GPU | 636 | ~630 (−1%) | +| tokens/s/GPU | 4917 | ~4900 (−0.4%) | + +`calibFactor` is a site control (default 0.93). + +## Caveats + +- `calibFactor` is from one anchor (flash, 16L, PP1). Pro / other parallel + layouts may want a slightly different value; re-anchor with another + `_calibrate_*` run if precision matters. +- analytic FLOPs are evaluated at the capture seq (4096); changing seq in the + UI does not re-derive them (attention FLOPs are seq-dependent). +- Optimizer step is analytic (per-rank params / HBM-BW); DP/PP comm assumed + hidden (A2/A4). + +## Reproduce + +```bash +# measured anchor (single node, full model) +LAYERS=16 GBS=64 bash examples/deepseek-v4/projection/script/_calibrate_flash.sh # (helper; not committed) +# analytic flops self-test +python3 examples/deepseek-v4/projection/tools/v4_flops.py +``` diff --git a/examples/deepseek-v4/projection/design/07-iteration-timeline.md b/examples/deepseek-v4/projection/design/07-iteration-timeline.md new file mode 100644 index 000000000..1dcc38386 --- /dev/null +++ b/examples/deepseek-v4/projection/design/07-iteration-timeline.md @@ -0,0 +1,165 @@ +# 07 — Iteration timeline (3-level composition view) + +A visual, drill-down view of **how one training iteration's time is composed**, +layered from a single layer up to the whole pipeline schedule. It reuses the +projection math in `04-projection-math.md` (same controls, same per-layer times) +and adds only rendering + one pipeline-schedule simulator. Nothing here changes +the headline `iteration time` / `tokens/s/GPU` numbers; it explains them. + +All times are microseconds (µs) unless noted, scaled to the active GPU tab by +Step 6 (`rowScaledTime`) exactly like the rest of the site. + +## Why three levels + +The iteration time is built bottom-up: + +``` +module time --(sum per phase)--> layer fwd/bwd (Level 1) +layer times --(map to PP/VPP)--> per-device chunk cost (Level 2) +device costs --(1F1B schedule)--> iteration timeline (Level 3) +``` + +Each level answers one question: + +- **Level 1 — where does a single layer's time go?** (attn / mlp / a2a) +- **Level 2 — how is work distributed across pipeline ranks?** (layer granularity) +- **Level 3 — how do the ranks overlap in time, and where is the bubble?** + +## Module → category mapping (Level 1 granularity) + +The default minimum granularity is three categories plus an explicit +"unattributed" bucket, derived from the `module` field (`03-json-schema.md`): + +| category | modules | meaning | +|----------|---------|---------| +| `attn` | `attn.norm`, `attn.proj`, `attn.core`, `attn.indexer`, `attn.misc` | attention (varies by `cr`) | +| `mlp` | `moe.grouped_gemm`, `moe.shared_expert`, `moe.router` | expert / shared-expert compute (cr-independent) | +| `a2a` | `moe.dispatch`, `moe.combine` | EP all-to-all (captured at EP=8) | +| `misc` | any `*.misc` / unattributed | casts, scalar, control-flow; kept visible | + +Rules: + +- `attn.misc` counts as `attn` (it is attention-local unattributed time), while a + standalone `misc` category is only used if a non-attn/non-moe module is + unmapped. In practice every row maps to `attn` or `mlp`/`a2a`; the `misc` + bucket surfaces `*.misc` share the same way `unattributedShare` does today. +- Because MoE is identical across `cr` (A10), only three representative layers are + shown: `cr=0`, `cr=4`, `cr=128`. "Same params/category → show one" reduces to + "one per cr". +- Optional drill-down expands `attn` into `core/indexer/proj/norm` and `mlp` into + `grouped_gemm/shared_expert/router`. + +## Level 2 — per-device chunk composition + +Granularity is **one physical decoder layer**. The projection already maps every +layer to a `PP*VPP` chunk and a device (`04` Step 2). Level 2 exposes that map: + +- Each device (PP rank) is a row; within it, chunks (VPP virtual chunks) are laid + out in schedule order, each chunk a run of layers coloured by `cr`. +- Recomputed layers (their backward replays one forward, `04` Step 1) are marked + (hatched / outlined) because they cost extra in `Db`. +- Non-layer parts are drawn on their owning device: `embedding` on device 0, + `output`+`loss`+`MTP` on the last device. +- **Dedup:** devices with an identical ordered signature + `(cr-list, recompute-flags, hasEmb, hasOut, hasMtp)` are drawn once, annotated + `×N ranks (d..d)`. +- The **critical device** (`max Df` / `max Db`, the one that sets the pipeline + critical path) is highlighted; each row shows its `Df`/`Db` and share of the + critical stage, making load imbalance (the bubble's root cause) visible. + +## Level 3 — pipeline schedule (Megatron-2 Figure 4 style) + +A Gantt chart: y-axis = device, x-axis = time. Forward cells one colour, backward +another; VPP virtual chunks distinguished by lightness; bubbles are gaps. + +Reference: Narayanan et al., "Efficient Large-Scale Language Model Training on +GPU Clusters Using Megatron-LM", arXiv:2104.04473, Figure 4 (default 1F1B on top, +interleaved below). + +### Colour scheme + +- forward = `--accent` (#4f8cff), backward = `--accent-2` (#36c08f). +- VPP chunk index shifts lightness (chunk 0 lightest → deeper for higher chunks), + mirroring the paper's light/dark model-chunk shading. +- bubble = empty (optionally a faint diagonal hatch) with the fraction labelled. + +### Schedule simulator + +The site's analytic pipe time is +`pipe_compute = (GA + (PP-1)/VPP) * (Df_crit + Db_crit)` (`04` Step 3). To *draw* +the schedule we simulate 1F1B (optionally interleaved) microbatch ordering and +let bubbles emerge from the gaps. + +Inputs: `PP`, `VPP`, `GA`, and per-virtual-chunk forward/backward cost. Each +virtual chunk `k` (device `k % PP`, vpp `⌊k/PP⌋`) uses the **exact** per-chunk +sums from `schedule.chunks` (`f_chunk[k]`, `b_chunk[k]`), with the non-layer +parts folded into the first (`embedding`) and last (`output`/`loss`/`MTP`) virtual +chunk so the drawn per-device length stays consistent with `Df`/`Db`. The +non-interleaved view collapses to one chunk per device with `f=Df[d]`, `b=Db[d]`. +Large `GA` is capped to `TL_VIS_GA_CAP` (48) microbatches for display only. + +Algorithm (interleaved 1F1B, `VPP` model chunks per device, `k mod PP` device +map): + +``` +num_chunks = PP * VPP +num_warmup = min(GA, (PP - 1 - device) * ... ) # standard interleaved warmup +events[device] = ordered list of {kind:'F'|'B', mb, chunk, start, dur} +- time advances per device; a device starts an op when both its own timeline and + the producing/consuming neighbour dependency are satisfied (F flows forward + along devices, B flows backward), with p2p comm assumed zero (A4). +``` + +The simulator returns per-device event lists with `start`/`dur`; the drawn +iteration length `max_device(last_end)` is compared against the analytic +`pipe_compute` (pre-`calibFactor`). For a balanced schedule they agree; a +mismatch beyond tolerance is surfaced as a warning rather than hidden. The +analytic number remains the official iteration time; the Gantt chart is the +visualization and is scaled so its total width equals the analytic pipe time. + +### Interleaving follows VPP + +There is no separate interleaved/non-interleaved toggle: the schedule is driven +directly by the `VPP` control. `VPP=1` renders plain 1F1B (Figure 4 top); +`VPP>1` renders interleaved 1F1B (Figure 4 bottom), which shrinks the bubble from +`(PP-1)/GA` to `(PP-1)/(GA*VPP)` (A5). Change `VPP` in the projection controls to +compare. + +## UI: layout + cross-level linkage + +- **Layout toggle.** *Tabbed* shows one level at a time (L1/L2/L3 buttons); + *Stacked (all + link)* renders all three top-to-bottom with section headings. +- **Drill-down linkage** (works in both layouts, most visible when stacked): + - click a **cr** in Level 1 (or a layer cell in Level 2) → highlights that cr's + layers in Level 2 and dims the rest; Level 1 emphasises the matching row. + - click a **PP rank** in Level 2 (or a device row in Level 3) → highlights the + linked rank in Levels 2 and 3, and Level 1 highlights the cr types present on + that rank. + - a "Clear" bar removes the selection. +- **Export.** Level 3's Gantt has *Export SVG* / *Export PNG* buttons; the + serializer resolves the theme CSS variables and paints a background so the file + is self-contained (good for slides / the paper-style figure). +- **Fit + zoom.** Level 3 fits the whole schedule in the panel at 1× (no + scrollbar) for an at-a-glance overview. A zoom slider (1×–10×) widens the time + axis so the per-cell microbatch numbers become readable; above 1× the chart + scrolls horizontally. Zoom stretches only the time axis (font/row height fixed). +- **Cell tooltip.** Hovering a Gantt cell shows `compute µs` (the op's + duration) and `starts @ ms` (its start time measured from the iteration + start). The x-axis is that same wall-clock time; gaps between cells are bubble. + +## Consistency / self-checks + +- Level 1 category sums per cr == `layerTimes(cr).fwd/bwd` (no time lost in + categorization). +- Level 2 per-device `Df/Db` == `project().Df/Db` (same mapping, just detailed). +- Level 3 simulated iteration length ≈ analytic `pipe_compute` (balanced case); + otherwise warn. +- All three levels react live to the shared projection controls (GPU tab, PP/VPP, + GA/GBS/MBS/DP, recompute, manual layer-timing mode). + +## Scope / caveats (inherit from 02-assumptions) + +- `a2a` is captured at EP=8 intra-node; other EP values are not re-modeled (A7-A9). +- p2p PP comm is hidden; only the bubble is shown (A4). +- seq is fixed at the capture value (4096). +- MI455X tab rescales module times per Step 6 before all three levels are drawn. diff --git a/examples/deepseek-v4/projection/script/deepseek_v4_layer_trace-projection.sh b/examples/deepseek-v4/projection/script/deepseek_v4_layer_trace-projection.sh new file mode 100755 index 000000000..9a3d16a29 --- /dev/null +++ b/examples/deepseek-v4/projection/script/deepseek_v4_layer_trace-projection.sh @@ -0,0 +1,252 @@ +#!/bin/bash +############################################################################### +# DeepSeek-V4 single-layer, single-cr profiling launcher for the perf +# *projection* pipeline (examples/deepseek-v4/projection). +# +# Produces ONE chrome trace for ONE compression-ratio (cr) layer type, captured +# under production-representative conditions so the trace can be turned into a +# clean per-module forward/backward breakdown (see design/01-overview.md): +# +# * seq_length = 4096 (production per-microbatch token count) +# * num_layers = 1 (clean per-layer attribution; fits memory @4096) +# * compress_ratios = [CR] (one cr per trace; CR in {0,4,128}) +# * optimizer = adam, NON-distributed, fp32 states (dist-opt ON makes the +# ROCm Kineto profiler +# drop dense/HCA compute +# kernels; NOT muon) +# * overlap_grad_reduce/param_gather = False (num_layers=1 breaks +# Megatron's chained +# param-sync assert; and +# with overlap off every +# microbatch's compute is +# already clean) +# * GA = 2 => GBS = 2 * DP * MBS (parser averages the +# steady profiler window +# per microbatch) +# * recompute = OFF (capture pure fwd / pure bwd) +# * profiler ON, with_stack=True, window iter 6 -> 7 (kernel -> nn.module) +# +# Usage (inside the training container, repo root): +# CR=0 ./examples/deepseek-v4/projection/script/deepseek_v4_layer_trace-projection.sh +# CR=4 ./examples/deepseek-v4/projection/script/deepseek_v4_layer_trace-projection.sh +# CR=128 ./examples/deepseek-v4/projection/script/deepseek_v4_layer_trace-projection.sh +# MODEL=flash CR=4 ./examples/deepseek-v4/projection/script/deepseek_v4_layer_trace-projection.sh +# +# The trace lands under: +# output/$PRIMUS_TEAM/$PRIMUS_USER/$PRIMUS_EXP_NAME/tensorboard/*.pt.trace.json +############################################################################### +set -euo pipefail +set -x + +export HF_TOKEN="${HF_TOKEN:-}" + +# ---------- What to profile ------------------------------------------------- +export MODEL=${MODEL:-pro} # pro | flash +export CR=${CR:-4} # 0 | 4 | 128 (single cr per trace) + +# ---------- Model: DeepSeek-V4 (pro or flash) ------------------------------ +# Widths (hidden/heads/kv) come from the model yaml via PRIMUS_MODEL; we only +# override the MoE / indexer shape knobs the runner exposes, per variant. +export EXP=${EXP:-examples/megatron/configs/MI355X/deepseek_v4_flash-BF16-pretrain.yaml} +case "$MODEL" in + pro) + export PRIMUS_MODEL=${PRIMUS_MODEL:-deepseek_v4_pro} + export PRIMUS_NUM_EXPERTS=${PRIMUS_NUM_EXPERTS:-384} + export PRIMUS_MOE_TOPK=${PRIMUS_MOE_TOPK:-6} + export PRIMUS_MOE_FFN_HIDDEN_SIZE=${PRIMUS_MOE_FFN_HIDDEN_SIZE:-3072} + export PRIMUS_INDEX_TOPK=${PRIMUS_INDEX_TOPK:-1024} + ;; + flash) + export PRIMUS_MODEL=${PRIMUS_MODEL:-deepseek_v4_flash} + export PRIMUS_NUM_EXPERTS=${PRIMUS_NUM_EXPERTS:-256} + export PRIMUS_MOE_TOPK=${PRIMUS_MOE_TOPK:-6} + export PRIMUS_MOE_FFN_HIDDEN_SIZE=${PRIMUS_MOE_FFN_HIDDEN_SIZE:-2048} + export PRIMUS_INDEX_TOPK=${PRIMUS_INDEX_TOPK:-512} + ;; + *) + echo "[ERROR] MODEL must be 'pro' or 'flash', got '$MODEL'"; exit 1 ;; +esac +# Megatron aux-loss-free expert bias needs sigmoid; V4 uses sqrtsoftplus -> off. +export PRIMUS_MOE_ENABLE_EXPERT_BIAS=${PRIMUS_MOE_ENABLE_EXPERT_BIAS:-False} + +# ---------- Single layer per cr (or 3-layer mix) at production seq --------- +# Pure dense (cr=0) / HCA (cr=128) single layers get CUDA-graph/stream-captured +# (compute hidden from the trace). CR=mix runs a 3-layer [0,4,128] block: the +# dynamic CSA (cr=4) layer keeps the block out of graph capture, so all three +# attention types are visible in one trace and split by kernel name. +export PRIMUS_SEQ_LENGTH=${PRIMUS_SEQ_LENGTH:-4096} +export PRIMUS_MAX_POSITION_EMBEDDINGS=${PRIMUS_MAX_POSITION_EMBEDDINGS:-${PRIMUS_SEQ_LENGTH}} +case "$CR" in + 0|4|128) export PRIMUS_COMPRESS_RATIOS="[$CR]"; export PRIMUS_TOTAL_LAYERS=1 ;; + mix) export PRIMUS_COMPRESS_RATIOS="[0,4,128]"; export PRIMUS_TOTAL_LAYERS=3 ;; + *) echo "[ERROR] CR must be 0, 4, 128 or mix, got '$CR'"; exit 1 ;; +esac + +# ---------- Single-node EP=8 (intra-node; DP=8) ---------------------------- +export PRIMUS_TP=${PRIMUS_TP:-1} +export PRIMUS_PP=${PRIMUS_PP:-1} +export PRIMUS_EP=${PRIMUS_EP:-8} +export MBS=${MBS:-1} +# DP = world/(TP*PP). On one 8-GPU node with TP=PP=1 => DP=8. +export DP=${DP:-8} +# GA = 2 so the schedule is F1 B1 F2 B2: the clean (overlap-free) forward is F2 +# and the clean backward is B1. The parser averages the steady profiler window +# per microbatch, keeping scalar control-flow stalls that survive full-model +# calibration. +export GBS=${GBS:-$((2 * DP * MBS))} + +# ---------- Optimizer: adam + distributed optimizer (zero1) ---------------- +# Compute (fwd/bwd) is optimizer-independent; we use adam (a) to model the +# zero1 DP-comm overlap that GA=2 isolates, and (b) to dodge Muon's fp32 +# optimizer-state memory blow-up so seq=4096 fits. The optimizer step itself is +# modeled separately in the projection (design/04-projection-math.md Step 4). +export OPTIMIZER=${OPTIMIZER:-adam} +# CRITICAL: distributed optimizer (zero1) MUST be off. With it on, the ROCm +# Kineto profiler silently drops the compute GPU kernels for pure dense (cr=0) +# and HCA (cr=128) layers (only optimizer/comm/elementwise get recorded); +# turning it off makes every cr's kernels visible. dist-opt has no bearing on +# the captured fwd/bwd compute, which is what the projection needs (the +# optimizer step is modeled analytically, design/04 Step 4). +export USE_DISTRIBUTED_OPTIMIZER=${USE_DISTRIBUTED_OPTIMIZER:-False} +# Overlap OFF. With num_layers=1, Megatron's chained param-gather sync trips an +# assertion (param_and_grad_buffer.start_param_sync). More importantly, with +# overlap off every microbatch's compute is already overlap-free — exactly the +# clean per-kernel time we want; the parser averages the steady profiler window +# per microbatch. (Production DP comm is assumed hidden in the projection anyway; A2.) +export PRIMUS_OVERLAP_GRAD_REDUCE=${PRIMUS_OVERLAP_GRAD_REDUCE:-False} +export PRIMUS_OVERLAP_PARAM_GATHER=${PRIMUS_OVERLAP_PARAM_GATHER:-False} +# Distributed optimizer (zero1) is the default. The Kineto trace drops the +# compute GPU kernels for pure dense(cr=0)/HCA(cr=128) layers ONLY when the +# distributed optimizer is on; turning it off makes all kernels visible (but +# then precision-aware optimizer must be off too -> fp32 optimizer states). +DISTOPT_ARGS=(--use_distributed_optimizer "$USE_DISTRIBUTED_OPTIMIZER") +if [ "$USE_DISTRIBUTED_OPTIMIZER" = "False" ]; then + DISTOPT_ARGS+=(--use_precision_aware_optimizer False --main_grads_dtype fp32 --exp_avg_dtype fp32 --exp_avg_sq_dtype fp32) +fi + +# ---------- Perf knobs (production V4 attn backends + Turbo MoE) ------------ +export ENABLE_PRIMUS_TURBO=${ENABLE_PRIMUS_TURBO:-True} +export USE_TURBO_ATTENTION=${USE_TURBO_ATTENTION:-False} +export USE_TURBO_DEEPEP=${USE_TURBO_DEEPEP:-True} +export TURBO_USE_GROUPED_MLP=${TURBO_USE_GROUPED_MLP:-True} +export USE_V4_ATTENTION_BACKEND=${USE_V4_ATTENTION_BACKEND:-triton_v1} +export USE_V4_CSA_ATTENTION_BACKEND=${USE_V4_CSA_ATTENTION_BACKEND:-triton_v1} +export USE_V4_FP8_INDEXER=${USE_V4_FP8_INDEXER:-True} +export USE_V4_COMPILED_SINKHORN=${USE_V4_COMPILED_SINKHORN:-True} +export PRIMUS_V4_GROUPED_EXPERTS_SUPPORT_CLAMPED_SWIGLU=${PRIMUS_V4_GROUPED_EXPERTS_SUPPORT_CLAMPED_SWIGLU:-True} +export PRIMUS_V4_ATTN_BWD_USE_SPLIT=${PRIMUS_V4_ATTN_BWD_USE_SPLIT:-1} +export PRIMUS_V4_CSA_BWD_SEGREDUCE=${PRIMUS_V4_CSA_BWD_SEGREDUCE:-1} +export PRIMUS_STACK_GROUPED_WEIGHT_TRITON=${PRIMUS_STACK_GROUPED_WEIGHT_TRITON:-1} +export PRIMUS_ROPE_TRITON=${PRIMUS_ROPE_TRITON:-1} +export PRIMUS_SINKHORN_TRITON=${PRIMUS_SINKHORN_TRITON:-1} +export PRIMUS_HC_TRITON=${PRIMUS_HC_TRITON:-1} +export PRIMUS_INDEXER_TRITON=${PRIMUS_INDEXER_TRITON:-1} +export PRIMUS_INDEXER_TRITON_FULL=${PRIMUS_INDEXER_TRITON_FULL:-0} +export PRIMUS_V4_ROUTER_TRITON=${PRIMUS_V4_ROUTER_TRITON:-1} + +TURBO_DEEPEP_CLI_ARGS=() +if [ "$USE_TURBO_DEEPEP" = "True" ]; then + export TURBO_DEEPEP_NUM_CU=${TURBO_DEEPEP_NUM_CU:-80} + export TURBO_DEEPEP_USE_COMM_STREAM=${TURBO_DEEPEP_USE_COMM_STREAM:-False} + export MOE_ROUTER_DTYPE=${MOE_ROUTER_DTYPE:-fp32} + export MOE_SHARED_EXPERT_OVERLAP=${MOE_SHARED_EXPERT_OVERLAP:-False} + TURBO_DEEPEP_CLI_ARGS=( + --turbo_deepep_num_cu "$TURBO_DEEPEP_NUM_CU" + --turbo_deepep_use_comm_stream "$TURBO_DEEPEP_USE_COMM_STREAM" + --moe_router_dtype "$MOE_ROUTER_DTYPE" + --moe_shared_expert_overlap "$MOE_SHARED_EXPERT_OVERLAP" + ) +fi + +export HSA_NO_SCRATCH_RECLAIM=${HSA_NO_SCRATCH_RECLAIM:-1} +# Disable the loss NaN/Inf validation (check_for_nan_in_loss_and_grad). Its +# torch.isnan/isinf checks in loss_func force a device->host sync once per +# microbatch; in a 1-layer capture (nothing to overlap) the profiler bills that +# sync wait as a multi-ms "stall" kernel under the loss/rerun frames, which then +# pollutes per-layer attribution and gets multiplied by the layer count in the +# projection. It is a per-step validation cost, not per-layer compute, so we +# turn it off for a clean capture (the projection models it separately if needed). +# Profiler window must land in the STEADY state: the first ~9 iters are warm-up +# (kernel autotune / hipBLASLt + Triton compilation) and have noisy, inflated +# per-iter times (and inflated comm-kernel durations as ranks desync on the +# autotuning rank); from ~iter 10 onward the iteration time is stable. Default +# to a late, multi-step window so the captured steps are clean; all three are +# env-overridable. +export TRAIN_ITERS=${TRAIN_ITERS:-22} +export PROFILE_STEP_START=${PROFILE_STEP_START:-16} +export PROFILE_STEP_END=${PROFILE_STEP_END:-19} + +# ---------- Profiler: trace with python stacks for kernel->module ---------- +export PROFILE=True +# Optional GPU-only trace (drop CPU activity). With CPU activity on, pure dense +# (cr=0) / HCA (cr=128) layers' compute kernels can vanish from the trace +# (profiler/stream-capture interaction); GPU-only capture brings them back, at +# the cost of CPU-side module/stack attribution (fall back to kernel-name rules). +PROFILER_ACTIVITY_ARGS=() +if [ "${DISABLE_PROFILER_CPU:-False}" = "True" ]; then + PROFILER_ACTIVITY_ARGS=(--disable_profiler_activity_cpu True) +fi +export BACKEND_PATH=${BACKEND_PATH:-"$(pwd)/third_party/Megatron-LM"} +export PRIMUS_TEAM=${PRIMUS_TEAM:-amd} +export PRIMUS_USER=${PRIMUS_USER:-tas-mi355x-$(date +%Y%m%d)} +export PRIMUS_EXP_NAME=${PRIMUS_EXP_NAME:-projection_${MODEL}_cr${CR}_seq${PRIMUS_SEQ_LENGTH}_ep${PRIMUS_EP}} + +if [ ! -d "$BACKEND_PATH" ] || [ -z "$(ls -A "$BACKEND_PATH" 2>/dev/null)" ]; then + echo "[ERROR] BACKEND_PATH does not exist or is empty: $BACKEND_PATH" + echo "Run: git submodule update --init --recursive" + exit 1 +fi + +mkdir -p "output/$PRIMUS_TEAM/$PRIMUS_USER/$PRIMUS_EXP_NAME" + +./primus-cli direct \ + -- train pretrain --config "$EXP" \ + --backend_path "$BACKEND_PATH" \ + --num_layers "$PRIMUS_TOTAL_LAYERS" \ + --train_iters "$TRAIN_ITERS" \ + --lr_warmup_iters 0 \ + --lr_decay_iters "$TRAIN_ITERS" \ + --micro_batch_size "$MBS" \ + --global_batch_size "$GBS" \ + --seq_length "$PRIMUS_SEQ_LENGTH" \ + --max_position_embeddings "$PRIMUS_MAX_POSITION_EMBEDDINGS" \ + --rope_type rope \ + --tensor_model_parallel_size "$PRIMUS_TP" \ + --pipeline_model_parallel_size "$PRIMUS_PP" \ + --expert_model_parallel_size "$PRIMUS_EP" \ + --num_experts "$PRIMUS_NUM_EXPERTS" \ + --moe_router_topk "$PRIMUS_MOE_TOPK" \ + --moe_router_enable_expert_bias "$PRIMUS_MOE_ENABLE_EXPERT_BIAS" \ + --moe_ffn_hidden_size "$PRIMUS_MOE_FFN_HIDDEN_SIZE" \ + --index_topk "$PRIMUS_INDEX_TOPK" \ + --v4_grouped_experts_support_clamped_swiglu "$PRIMUS_V4_GROUPED_EXPERTS_SUPPORT_CLAMPED_SWIGLU" \ + --compress_ratios "$PRIMUS_COMPRESS_RATIOS" \ + --mtp_num_layers 0 \ + --mock_data True \ + --optimizer "$OPTIMIZER" \ + "${DISTOPT_ARGS[@]}" \ + --enable_primus_turbo "$ENABLE_PRIMUS_TURBO" \ + --use_turbo_attention "$USE_TURBO_ATTENTION" \ + --use_v4_attention_backend "$USE_V4_ATTENTION_BACKEND" \ + --use_v4_csa_attention_backend "$USE_V4_CSA_ATTENTION_BACKEND" \ + --use_v4_fp8_indexer "$USE_V4_FP8_INDEXER" \ + --use_v4_compiled_sinkhorn "$USE_V4_COMPILED_SINKHORN" \ + --use_turbo_deepep "$USE_TURBO_DEEPEP" \ + "${TURBO_DEEPEP_CLI_ARGS[@]}" \ + --use_turbo_grouped_gemm "$TURBO_USE_GROUPED_MLP" \ + --moe_use_legacy_grouped_gemm False \ + --fp8 null \ + --fp8_recipe null \ + --recompute_num_layers 0 \ + --check_for_nan_in_loss_and_grad False \ + --overlap_grad_reduce "$PRIMUS_OVERLAP_GRAD_REDUCE" \ + --overlap_param_gather "$PRIMUS_OVERLAP_PARAM_GATHER" \ + --disable_last_saving True \ + --disable_wandb True \ + --disable_tensorboard False \ + --profile True \ + --use_pytorch_profiler True \ + "${PROFILER_ACTIVITY_ARGS[@]}" \ + --profile_step_start "$PROFILE_STEP_START" \ + --profile_step_end "$PROFILE_STEP_END" \ + 2>&1 | tee "output/$PRIMUS_TEAM/$PRIMUS_USER/$PRIMUS_EXP_NAME/log_node_${NODE_RANK:-0}.txt" diff --git a/examples/deepseek-v4/projection/site/assets/app.js b/examples/deepseek-v4/projection/site/assets/app.js new file mode 100644 index 000000000..b1b0b32bd --- /dev/null +++ b/examples/deepseek-v4/projection/site/assets/app.js @@ -0,0 +1,1567 @@ +"use strict"; + +// DeepSeek-V4 performance projection — static, no-build. Implements the math in +// design/04-projection-math.md. All breakdown times are microseconds (us) for +// one microbatch (seq from capture); the projection scales to a full model run. + +const STATE = { + data: null, + gpu: "MI355X", + controls: null, + // iteration-timeline view (design/07): active level (1|2|3). Level 3 + // interleaving follows the VPP control directly (no separate toggle). + tlLevel: 1, + // layout: "tabs" (one level via buttons) or "stacked" (all three + linkage). + tlView: "stacked", + // Level 3 Gantt horizontal zoom (1 = fit-to-view, no scrollbar; >1 widens the + // chart and reveals per-cell microbatch numbers, with horizontal scroll). + tlZoom: 1, + // cross-level selection for drill-down highlight. cr: focus a compression + // ratio; devices: focus one or more PP ranks. Mutually exclusive. + tlSel: { cr: null, devices: null }, +}; + +const $ = (sel) => document.querySelector(sel); +const el = (tag, attrs = {}, ...kids) => { + const n = document.createElement(tag); + for (const [k, v] of Object.entries(attrs)) { + if (k === "class") n.className = v; + else if (k === "html") n.innerHTML = v; + else n.setAttribute(k, v); + } + for (const kid of kids) n.append(kid?.nodeType ? kid : document.createTextNode(kid ?? "")); + return n; +}; +const fmt = (x, d = 1) => + x == null || !isFinite(x) ? "—" : Number(x).toLocaleString(undefined, { maximumFractionDigits: d, minimumFractionDigits: d }); +const fmtInt = (x) => (x == null || !isFinite(x) ? "—" : Math.round(x).toLocaleString()); + +// --------------------------------------------------------------------------- +// Load +// --------------------------------------------------------------------------- +function modelFromQuery() { + const m = new URLSearchParams(location.search).get("model"); + return m === "flash" || m === "pro" ? m : "pro"; +} + +async function loadModel(model) { + const res = await fetch(`./data/${model}.json`, { cache: "no-store" }); + if (!res.ok) throw new Error(`failed to load data/${model}.json (${res.status})`); + return res.json(); +} + +// --------------------------------------------------------------------------- +// Controls +// --------------------------------------------------------------------------- +function defaultControls(data) { + const hw = data.hardware || {}; + const m355 = hw.MI355X || { peak_tflops_bf16: 2500, hbm_bandwidth_gbps: 8000 }; + const m455 = hw.MI455X || { peak_tflops_bf16: 5000, hbm_bandwidth_gbps: 16000 }; + const isPro = data.model === "pro"; + const optBytes = data.optimizer?.bytes_per_param && data.optimizer.bytes_per_param !== 18 + ? data.optimizer.bytes_per_param : 30; + return { + world: isPro ? 256 : 32, + pp: isPro ? 16 : 4, vpp: 1, ep: 8, tp: 1, cp: 1, + mbs: 1, gbs: isPro ? 1024 : 256, + recompute: isPro ? "full" : "first-n", + recomputeLayers: isPro ? 0 : 3, + ppLayout: data.model_config?.pipeline_layout || "", + optEff: 0.7, computeEff: 1.0, calibFactor: 0.91, bytesPerParam: optBytes, + peak355: m355.peak_tflops_bf16, bw355: m355.hbm_bandwidth_gbps, + peak455: m455.peak_tflops_bf16, bw455: m455.hbm_bandwidth_gbps, + // Modeling mode: "trace" (derive per-layer fwd/bwd from the breakdown JSON) + // or "manual" (user types per-cr fwd/bwd directly). Manual values are stored + // per GPU because a hand-entered time already targets a specific GPU, so the + // MI355->MI455 scaling is bypassed in manual mode. + modelMode: "trace", + man: { + MI355X: emptyManual(), + MI455X: emptyManual(), + }, + }; +} + +// Per-cr manual fwd/bwd holders (µs). null = "not set yet" -> falls back to the +// trace-derived value, so toggling into manual mode never changes the result +// until the user actually edits a field. +function emptyManual() { + return { + f0: null, b0: null, f4: null, b4: null, f128: null, b128: null, + // non-layer + MTP overrides (per iteration, one device) + emb_f: null, emb_b: null, out_f: null, out_b: null, loss_f: null, loss_b: null, mtp_f: null, mtp_b: null, + }; +} + +const MANUAL_CR_KEYS = { "0": ["f0", "b0"], "4": ["f4", "b4"], "128": ["f128", "b128"] }; +// Manually-overridable non-layer parts: key prefix, label, and the JSON section +// (mtp is synthesized, not a non_layer entry). +const MANUAL_NONLAYER = [ + { key: "emb", label: "embedding", which: "embedding" }, + { key: "out", label: "output", which: "output" }, + { key: "loss", label: "loss", which: "loss" }, + { key: "mtp", label: "MTP", which: "mtp" }, +]; +const MANUAL_NONLAYER_KEYS = MANUAL_NONLAYER.flatMap((n) => [`${n.key}_f`, `${n.key}_b`]); + +const CONTROL_DEFS = [ + { key: "world", label: "World size (GPUs)", kind: "int" }, + { key: "pp", label: "PP (pipeline)", kind: "int" }, + { key: "vpp", label: "VPP (interleave)", kind: "int" }, + { key: "ep", label: "EP (expert)", kind: "int" }, + { key: "dp", label: "DP (derived)", kind: "ro" }, + { key: "tp", label: "TP (tensor)", kind: "int" }, + { key: "cp", label: "CP (context)", kind: "int" }, + { key: "mbs", label: "Micro batch size", kind: "int" }, + { key: "gbs", label: "Global batch size", kind: "int" }, + { key: "recompute", label: "Recompute", kind: "sel" }, + { key: "recomputeLayers", label: "Recompute layers", kind: "int" }, + { key: "ppLayout", label: "PP layout", kind: "txt", full: true }, + { key: "bytesPerParam", label: "Optim bytes/param", kind: "int" }, + { key: "calibFactor", label: "Calibration factor", kind: "f" }, + { key: "optEff", label: "Optim efficiency", kind: "f" }, + { key: "computeEff", label: "MI455 compute eff", kind: "f" }, + { key: "peak355", label: "MI355 peak TFLOPs", kind: "int" }, + { key: "bw355", label: "MI355 HBM GB/s", kind: "int" }, + { key: "peak455", label: "MI455 peak TFLOPs", kind: "int" }, + { key: "bw455", label: "MI455 HBM GB/s", kind: "int" }, +]; + +// DP is derived from the user-set world size: DP = world / (PP*TP*CP). EP is a +// sub-grouping of DP (EP <= DP) and does not multiply world size. +const derivedDP = (c) => { + const denom = c.pp * c.tp * c.cp; + if (!Number.isFinite(denom) || denom <= 0 || !Number.isFinite(c.world)) return NaN; + return c.world / denom; +}; + +function parseControlValue(input, kind) { + if (kind === "sel" || kind === "txt") return input.value; + if (kind === "int") return input.value.trim() === "" ? NaN : Number(input.value); + if (kind === "f") return input.value.trim() === "" ? NaN : Number(input.value); + return input.value; +} + +const isPositiveInt = (x) => Number.isInteger(x) && x > 0; +const isNonNegativeInt = (x) => Number.isInteger(x) && x >= 0; +const isPositiveNumber = (x) => Number.isFinite(x) && x > 0; +const isNonNegativeNumber = (x) => Number.isFinite(x) && x >= 0; + +function renderControls() { + const grid = $("#controls-grid"); + grid.innerHTML = ""; + const c = STATE.controls; + for (const def of CONTROL_DEFS) { + const { key, label, kind } = def; + const field = el("div", { class: "field" }); + if (def.full) field.classList.add("field--full"); + field.append(el("span", {}, label)); + let input; + if (kind === "sel") { + input = el("select", { id: `ctl-${key}` }); + for (const opt of ["none", "full", "first-n"]) { + const o = el("option", { value: opt }, opt); + if (c[key] === opt) o.selected = true; + input.append(o); + } + } else if (kind === "ro") { + const dp = derivedDP(c); + input = el("input", { id: `ctl-${key}`, value: Number.isFinite(dp) ? dp : "—", disabled: "true" }); + } else if (kind === "txt") { + input = el("input", { id: `ctl-${key}`, type: "text", value: c[key] || "" }); + } else { + input = el("input", { id: `ctl-${key}`, type: "number", value: c[key], step: kind === "f" ? "0.05" : "1" }); + } + if (kind !== "ro") { + input.addEventListener("change", () => { + c[key] = parseControlValue(input, kind); + renderAll(); + }); + } + field.append(input); + grid.append(field); + } +} + +// Prefill the active GPU's manual fields from the trace-derived times so that +// switching into manual mode (or switching GPU while in manual mode) starts from +// the current baseline instead of empty boxes. Already-set fields are kept. +function prefillManual(gpu) { + const c = STATE.controls; + const m = c.man[gpu]; + const lt = {}; + for (const cr of ["0", "4", "128"]) { + const [fk, bk] = MANUAL_CR_KEYS[cr]; + const t = layerTimes(STATE.data, cr, gpu, c); + lt[cr] = t; + if (!isPositiveNumber(m[fk])) m[fk] = Math.round(t.fwd); + if (!isPositiveNumber(m[bk])) m[bk] = Math.round(t.bwd); + } + // non-layer parts (embedding / output / loss). Skip seeding fields whose trace + // value is 0 (e.g. output/loss backward) — leaving them unset shows the "0" + // placeholder and falls back to trace, instead of pinning an explicit 0. + const seed = (key, val) => { if (!isPositiveNumber(m[key]) && Math.round(val) > 0) m[key] = Math.round(val); }; + for (const which of ["embedding", "output", "loss"]) { + const key = NONLAYER_KEY[which]; + seed(`${key}_f`, nonLayer(STATE.data, which, "forward", gpu, c)); + seed(`${key}_b`, nonLayer(STATE.data, which, "backward", gpu, c)); + } + // MTP (only when the model uses it) + if ((STATE.data.model_config.mtp_num_layers || 0) > 0) { + const base = mtpTimes(STATE.data, gpu, c, lt); + seed("mtp_f", base.fwd); + seed("mtp_b", base.bwd); + } +} + +function renderModeSwitch() { + document.querySelectorAll(".mode-tab").forEach((t) => { + t.classList.toggle("is-active", t.dataset.mode === STATE.controls.modelMode); + }); +} + +function renderManualGrid() { + const host = $("#manual-grid"); + if (!host) return; + host.hidden = STATE.controls.modelMode !== "manual"; + host.innerHTML = ""; + if (host.hidden) return; + const c = STATE.controls, gpu = STATE.gpu; + const counts = STATE.data.model_config.cr_layer_counts || {}; + const m = c.man[gpu]; + const header = el("div", { class: "manual-grid__header" }); + header.append(el("p", { class: "muted manual-grid__hint" }, + `Per-layer and non-layer fwd/bwd (µs) for ${gpu}. Set values override the trace; placeholders show the current trace baseline. Embedding is on PP stage 0; output / loss / MTP are on the last PP stage.`)); + const resetBtn = el("button", { class: "manual-reset" }, "Restore defaults"); + resetBtn.addEventListener("click", () => { + c.man[gpu] = emptyManual(); + prefillManual(gpu); + renderAll(); + }); + header.append(resetBtn); + host.append(header); + + // one fwd/bwd input row + const makeRow = (labelNode, fk, bk, traceF, traceB) => { + const row = el("div", { class: "manual-row" }); + row.append(labelNode); + for (const [label, key, traceVal] of [["fwd", fk, traceF], ["bwd", bk, traceB]]) { + const field = el("div", { class: "field" }); + field.append(el("span", {}, `${label} µs`)); + const input = el("input", { + id: `man-${gpu}-${key}`, type: "number", step: "1", min: "0", + placeholder: String(Math.round(traceVal || 0)), + }); + if (isPositiveNumber(m[key])) input.value = m[key]; + input.addEventListener("change", () => { + const v = input.value.trim(); + m[key] = v === "" ? null : Number(v); + renderAll(); + }); + field.append(input); + row.append(field); + } + return row; + }; + + const rows = el("div", { class: "manual-rows" }); + for (const cr of ["0", "4", "128"]) { + if (!(counts[cr] > 0)) continue; + const [fk, bk] = MANUAL_CR_KEYS[cr]; + const t = layerTimes(STATE.data, cr, gpu, c); + const lab = el("span", { class: `manual-row__lab cr-tag cr-${cr}` }, `cr=${cr} ×${counts[cr] || 0}`); + rows.append(makeRow(lab, fk, bk, t.fwd, t.bwd)); + } + host.append(rows); + + // non-layer + MTP rows + host.append(el("p", { class: "muted manual-grid__subhead" }, "Non-layer (once per iteration)")); + const nlRows = el("div", { class: "manual-rows" }); + const lt = {}; + for (const cr of ["0", "4", "128"]) lt[cr] = layerTimes(STATE.data, cr, gpu, c); + for (const nl of MANUAL_NONLAYER) { + if (nl.which === "mtp" && !((STATE.data.model_config.mtp_num_layers || 0) > 0)) continue; + let tF, tB; + if (nl.which === "mtp") { + const base = mtpTimes(STATE.data, gpu, c, lt); + tF = base.fwd; tB = base.bwd; + } else { + tF = nonLayer(STATE.data, nl.which, "forward", gpu, c); + tB = nonLayer(STATE.data, nl.which, "backward", gpu, c); + } + const lab = el("span", { class: "manual-row__lab manual-row__lab--nl" }, nl.label); + nlRows.append(makeRow(lab, `${nl.key}_f`, `${nl.key}_b`, tF, tB)); + } + host.append(nlRows); +} + +// --------------------------------------------------------------------------- +// Hardware scaling (Step 6) +// --------------------------------------------------------------------------- +function rowScaledTime(row, gpu, c) { + if (gpu === "MI355X") return row.time_us; + const compute = row.class === "compute_bound"; + if (compute) return row.time_us * (c.peak355 / c.peak455) / c.computeEff; + return row.time_us * (c.bw355 / c.bw455); +} +function rowTflops(row, scaledTimeUs) { + if (!row.flops || !scaledTimeUs) return null; + return row.flops / (scaledTimeUs * 1e-6) / 1e12; +} + +const sumTime = (list, gpu, c) => list.reduce((a, r) => a + rowScaledTime(r, gpu, c), 0); +const sumFlops = (list) => list.reduce((a, r) => a + (r.flops || 0), 0); + +// --------------------------------------------------------------------------- +// Per-layer base (Step 0/1) +// --------------------------------------------------------------------------- +function layerTimes(data, cr, gpu, c) { + const L = data.layers[cr]; + if (!L) return { fwd: 0, bwd: 0, fFlops: 0, bFlops: 0 }; + const aF = L.attention.forward, aB = L.attention.backward; + const mF = L.moe.forward, mB = L.moe.backward; + let fwd = sumTime(aF, gpu, c) + sumTime(mF, gpu, c); + let bwd = sumTime(aB, gpu, c) + sumTime(mB, gpu, c); + let fFlops = sumFlops(aF) + sumFlops(mF); + let bFlops = sumFlops(aB) + sumFlops(mB); + return { fwd, bwd, fFlops, bFlops }; +} + +// Effective per-layer time used by the projection. In "manual" mode a set field +// overrides the trace-derived time for the active GPU; unset fields fall back to +// trace. FLOPs always stay analytic/trace-derived (manual only overrides time), +// so TFLOP/s/GPU remains meaningful. +function effectiveLayerTimes(data, cr, gpu, c) { + const trace = layerTimes(data, cr, gpu, c); + if (c.modelMode !== "manual") return trace; + const m = (c.man && c.man[gpu]) || {}; + const [fk, bk] = MANUAL_CR_KEYS[cr] || []; + return { + fwd: isPositiveNumber(m[fk]) ? m[fk] : trace.fwd, + bwd: isPositiveNumber(m[bk]) ? m[bk] : trace.bwd, + fFlops: trace.fFlops, + bFlops: trace.bFlops, + }; +} + +function expandLayoutRepeats(layout) { + let out = layout; + let prev; + do { + prev = out; + out = out.replace(/\(([^()]+)\)\*(\d+)/g, (_m, body, count) => body.repeat(Number(count))); + } while (out !== prev); + return out; +} + +function parsePipelineLayout(layout, numLayers, chunks) { + const raw = String(layout ?? "").trim(); + if (!raw) return { ok: true, stages: null, counts: [], normalized: "", message: "empty layout; using balanced fallback" }; + const normalized = expandLayoutRepeats(raw.replace(/^['"]|['"]$/g, "")); + if (/[()]/.test(normalized)) { + return { ok: false, stages: null, counts: [], normalized, message: "unsupported nested or malformed repeat expression" }; + } + const specs = normalized.split("|").map((x) => x.trim()).filter(Boolean); + if (specs.length !== chunks) { + return { + ok: false, + stages: null, + counts: specs.map((spec) => [...spec.matchAll(/[tT](?:\*(\d+))?/g)].reduce((a, m) => a + Number(m[1] || 1), 0)), + normalized, + message: `layout has ${specs.length} stages, expected PP*VPP=${chunks}`, + }; + } + let nextLayer = 0; + const out = []; + const counts = []; + for (const spec of specs) { + const layers = []; + for (const m of spec.matchAll(/[tT](?:\*(\d+))?/g)) { + const n = m[1] ? Number(m[1]) : 1; + for (let i = 0; i < n && nextLayer < numLayers; i++) layers.push(nextLayer++); + } + counts.push(layers.length); + out.push(layers); + } + if (nextLayer !== numLayers) { + return { + ok: false, + stages: null, + counts, + normalized, + message: `layout maps ${nextLayer} decoder layers, expected ${numLayers}`, + }; + } + return { ok: true, stages: out, counts, normalized, message: "layout applied" }; +} + +function recomputeLayer(c, stageOrdinal) { + if (c.recompute === "full") return true; + if (c.recompute === "first-n") return stageOrdinal < Math.max(0, c.recomputeLayers || 0); + return false; +} + +function validateControls(data, c, gpu = STATE.gpu) { + const errors = []; + const warnings = []; + const ints = ["world", "pp", "vpp", "ep", "tp", "cp", "mbs", "gbs", "bytesPerParam", "peak355", "bw355", "peak455", "bw455"]; + for (const key of ints) { + if (!isPositiveInt(c[key])) errors.push(`${key} must be a positive integer.`); + } + if (!isNonNegativeInt(c.recomputeLayers)) errors.push("recomputeLayers must be a non-negative integer."); + for (const key of ["calibFactor", "optEff", "computeEff"]) { + if (!isPositiveNumber(c[key])) errors.push(`${key} must be a positive number.`); + } + if (!["none", "full", "first-n"].includes(c.recompute)) errors.push(`unsupported recompute mode: ${c.recompute}`); + + const denom = c.pp * c.tp * c.cp; + const dp = derivedDP(c); + if (isPositiveInt(c.world) && isPositiveInt(denom) && c.world % denom !== 0) { + errors.push(`world must be divisible by PP*TP*CP (${denom}); got world=${c.world}.`); + } + if (Number.isFinite(dp) && isPositiveInt(c.gbs) && isPositiveInt(c.mbs) && !Number.isInteger(c.gbs / (dp * c.mbs))) { + errors.push(`GA must be an integer: GBS / (DP*MBS) = ${c.gbs} / (${dp}*${c.mbs}).`); + } + if (Number.isFinite(dp) && isPositiveInt(c.ep) && c.ep > dp) { + errors.push(`EP must be <= DP; got EP=${c.ep}, DP=${dp}.`); + } + + const chunks = c.pp * c.vpp; + const layout = parsePipelineLayout(c.ppLayout, data.model_config.compress_ratios.length, chunks); + if (!layout.ok) errors.push(`PP layout invalid: ${layout.message}.`); + + if (c.modelMode === "manual") { + const man = (c.man && c.man[gpu]) || {}; + for (const cr of ["0", "4", "128"]) { + const count = data.model_config.cr_layer_counts?.[cr] || 0; + if (!count) continue; // cr not used by this model; ignore its inputs + for (const k of MANUAL_CR_KEYS[cr]) { + const v = man[k]; + if (v != null && v !== "" && !isNonNegativeNumber(Number(v))) { + errors.push(`manual ${k} (cr=${cr}) must be a non-negative number (µs).`); + } + } + } + for (const k of MANUAL_NONLAYER_KEYS) { + const v = man[k]; + if (v != null && v !== "" && !isNonNegativeNumber(Number(v))) { + errors.push(`manual ${k} must be a non-negative number (µs).`); + } + } + warnings.push(`Manual mode for ${gpu}: per-cr layer, non-layer (embedding/output/loss) and MTP fwd/bwd you set override the trace; unset fields fall back to trace.`); + } + + const captureEp = data.capture?.ep; + if (captureEp && c.ep !== captureEp) { + warnings.push(`EP=${c.ep} is only partially modeled; traces captured EP=${captureEp}, so MoE dispatch/combine are not re-estimated.`); + } else { + warnings.push(`EP is a consistency control only; captured MoE dispatch/combine are reused from EP=${captureEp || 8}.`); + } + if (c.tp !== 1 || c.cp !== 1) { + warnings.push("TP/CP values affect derived DP/GA/optimizer only; TP/CP layer compute and communication are not re-modeled."); + } + if (gpu === "MI355X") warnings.push("MI455 peak/bandwidth/compute-eff controls affect only the MI455X tab."); + warnings.push(`Sequence length is fixed at captured seq=${data.capture.seq_length}; changing seq is not currently exposed.`); + + return { errors, warnings, layout, dp }; +} + +function nonLayer(data, which, phase, gpu, c) { + const bd = data.non_layer[which]; + return bd ? sumTime(bd[phase], gpu, c) : 0; +} + +// Non-layer time with manual override (embedding / output / loss). Falls back to +// the trace time when the field is unset or not in manual mode. +const NONLAYER_KEY = { embedding: "emb", output: "out", loss: "loss" }; +function effectiveNonLayer(data, which, phase, gpu, c) { + const trace = nonLayer(data, which, phase, gpu, c); + if (c.modelMode !== "manual") return trace; + const m = (c.man && c.man[gpu]) || {}; + const v = m[`${NONLAYER_KEY[which]}_${phase === "forward" ? "f" : "b"}`]; + return isPositiveNumber(v) ? v : trace; +} +function nonLayerFlops(data, which, phase) { + const bd = data.non_layer[which]; + return bd ? sumFlops(bd[phase]) : 0; +} + +function mtpTimes(data, gpu, c, lt) { + const cfg = data.model_config; + const depth = cfg.mtp_num_layers || 0; + if (!depth) return { fwd: 0, bwd: 0, ehUs: 0 }; + + const cr = String((cfg.mtp_compress_ratios && cfg.mtp_compress_ratios[0]) || 0); + const inner = lt[cr] || lt["0"]; + const innerBwd = inner.bwd + (c.recompute === "full" ? inner.fwd : 0); + const outF = nonLayer(data, "output", "forward", gpu, c) + nonLayer(data, "loss", "forward", gpu, c); + const outB = nonLayer(data, "output", "backward", gpu, c) + nonLayer(data, "loss", "backward", gpu, c); + + // No MTP trace row exists yet. Approximate eh_proj as GEMM-like work scaled + // from the measured output projection time, then split fwd/bwd as 1:2. + const af = data.analytic_flops || {}; + const mtp = af.mtp || {}; + const outFlops = af.output_flops || 0; + const outUs = outF + outB; + const ehUs = outFlops > 0 ? outUs * ((mtp.eh_proj_flops || 0) / outFlops) : 0; + + return { + fwd: depth * (inner.fwd + outF) + ehUs / 3, + bwd: depth * (innerBwd + outB) + (2 * ehUs) / 3, + ehUs, + }; +} + +// MTP time with manual override. Falls back to the analytic estimate. +function effectiveMtp(data, gpu, c, lt) { + const base = mtpTimes(data, gpu, c, lt); + if (c.modelMode !== "manual" || !((data.model_config.mtp_num_layers || 0) > 0)) return base; + const m = (c.man && c.man[gpu]) || {}; + return { + fwd: isPositiveNumber(m.mtp_f) ? m.mtp_f : base.fwd, + bwd: isPositiveNumber(m.mtp_b) ? m.mtp_b : base.bwd, + ehUs: base.ehUs, + }; +} + +// --------------------------------------------------------------------------- +// Param estimate (Step 4) +// --------------------------------------------------------------------------- +function estimateParams(cfg) { + // Prefer the exact V4 count emitted by parse_trace (MLA low-rank attention + + // MoE + tied-free embedding/output). Fall back to a crude estimate. + if (cfg.total_params) return cfg.total_params; + const h = cfg.hidden_size, exp = cfg.num_experts, mff = cfg.moe_ffn_hidden_size; + const sff = cfg.moe_shared_expert_intermediate_size || mff, V = cfg.vocab_size, L = cfg.num_layers; + const perLayer = 4 * h * h + exp * 3 * h * mff + 3 * h * sff; + return L * perLayer + 2 * V * h; +} + +function estimateOptimizerParams(data, c, dp) { + const cfg = data.model_config; + const totalParams = estimateParams(cfg); + const pp = Math.max(1, c.pp); + const tp = Math.max(1, c.tp); + const dpSize = Math.max(1, dp); + + // total_params is a full-model count. CP does not shard weights; EP is already + // represented in the full expert count and cancels out with the DP replica + // count for ZeRO-1 optimizer sharding, so the average rank owns total/(PP*TP) + // model params and updates total/(PP*TP*DP) params per optimizer step. + const localModelParams = totalParams / (pp * tp); + const perRankParams = localModelParams / dpSize; + const measuredUs = data.optimizer?.time_us ?? null; + return { totalParams, localModelParams, perRankParams, measuredUs }; +} + +// --------------------------------------------------------------------------- +// Pipeline mapping + projection (Steps 2-5) +// --------------------------------------------------------------------------- +function project(data, gpu, c, validation = validateControls(data, c, gpu)) { + if (validation.errors.length) return null; + const cfg = data.model_config; + const crs = cfg.compress_ratios; + const L = crs.length; + const dp = validation.dp; + const world = c.world; + const lt = {}; + for (const cr of ["0", "4", "128"]) lt[cr] = effectiveLayerTimes(data, cr, gpu, c); + + // Step 2: assign layers to PP*VPP chunks -> devices + const C = c.pp * c.vpp; + const perChunk = Math.ceil(L / C); + const Df = new Array(c.pp).fill(0), Db = new Array(c.pp).fill(0); + const layoutInfo = validation.layout; + const layoutStages = layoutInfo.ok ? layoutInfo.stages : null; + const stageOrdinals = new Array(c.pp).fill(0); + // Per virtual-chunk detail (k = 0..C-1): device = k % PP, vpp index = floor(k/PP). + // Kept for the iteration-timeline view (design/07); does not affect Df/Db math. + const chunks = []; + for (let k = 0; k < C; k++) { + chunks.push({ chunk: k, device: k % c.pp, vpp: Math.floor(k / c.pp), layers: [], fwd: 0, bwd: 0 }); + } + const addLayer = (chunkIdx, i) => { + const dev = chunkIdx % c.pp; + const t = lt[String(crs[i])] || { fwd: 0, bwd: 0 }; + const recompute = recomputeLayer(c, stageOrdinals[dev]++); + const effBwd = t.bwd + (recompute ? t.fwd : 0); + Df[dev] += t.fwd; + Db[dev] += effBwd; + const ch = chunks[chunkIdx]; + ch.layers.push({ globalIdx: i, cr: crs[i], recompute, fwd: t.fwd, bwd: effBwd }); + ch.fwd += t.fwd; + ch.bwd += effBwd; + }; + if (layoutStages) { + layoutStages.forEach((layers, chunk) => { + for (const i of layers) addLayer(chunk, i); + }); + } else { + for (let i = 0; i < L; i++) addLayer(Math.floor(i / perChunk), i); + } + // non-layer parts on first / last device (manual-overridable) + const embFwd = effectiveNonLayer(data, "embedding", "forward", gpu, c); + const embBwd = effectiveNonLayer(data, "embedding", "backward", gpu, c); + Df[0] += embFwd; + Db[0] += embBwd; + const last = c.pp - 1; + const outFwd = effectiveNonLayer(data, "output", "forward", gpu, c) + effectiveNonLayer(data, "loss", "forward", gpu, c); + const outBwd = effectiveNonLayer(data, "output", "backward", gpu, c) + effectiveNonLayer(data, "loss", "backward", gpu, c); + Df[last] += outFwd; + Db[last] += outBwd; + const mtp = effectiveMtp(data, gpu, c, lt); + Df[last] += mtp.fwd; + Db[last] += mtp.bwd; + + const critF = Math.max(...Df), critB = Math.max(...Db); + + // Assemble per-device schedule detail (design/07). Non-layer parts are attached + // to their owning device so the timeline can render them; Df/Db already include + // them above. + const critFdev = Df.indexOf(critF), critBdev = Db.indexOf(critB); + const devices = []; + for (let d = 0; d < c.pp; d++) { + devices.push({ + device: d, + chunks: chunks.filter((ch) => ch.device === d).sort((a, b) => a.vpp - b.vpp), + Df: Df[d], Db: Db[d], + hasEmb: d === 0, hasOut: d === last, hasMtp: d === last && mtp.fwd > 0, + embFwd: d === 0 ? embFwd : 0, embBwd: d === 0 ? embBwd : 0, + outFwd: d === last ? outFwd : 0, outBwd: d === last ? outBwd : 0, + mtpFwd: d === last ? mtp.fwd : 0, mtpBwd: d === last ? mtp.bwd : 0, + isCritF: d === critFdev, isCritB: d === critBdev, + }); + } + const schedule = { chunks, devices, C, critFdev, critBdev }; + + // Step 3: pipeline compute time (us) ; GA = gbs/(dp*mbs) + // calibFactor corrects the single-layer-capture -> full-model bias (~0.93, + // from the flash 16-layer single-node calibration; see design/06). + const ga = c.gbs / (dp * c.mbs); + const pipeUs = (ga + (c.pp - 1) / c.vpp) * (critF + critB) * c.calibFactor; + const bubbleFrac = (c.pp - 1) / c.vpp / (ga + (c.pp - 1) / c.vpp); + + // Step 4: optimizer (memory-bound, zero1-sharded over DP; CP does not shard params) + const optParams = estimateOptimizerParams(data, c, dp); + const totalParams = optParams.totalParams; + const perRankParams = optParams.perRankParams; + const bw = (gpu === "MI355X" ? c.bw355 : c.bw455) * 1e9; // bytes/s + const optTimeS = (perRankParams * c.bytesPerParam) / bw / c.optEff; + const optUs = optTimeS * 1e6; + + // Step 5: totals + const iterUs = pipeUs + optUs; + const iterS = iterUs * 1e-6; + const seq = data.capture.seq_length; + const tokIter = c.gbs * seq; + const tokS = tokIter / iterS; + const tokSgpu = tokS / world; + + // FLOPs/iter — Megatron-convention V4 analytic model FLOPs (independent of + // recompute; recompute adds time, not model flops). Falls back to breakdown + // gemm flops if analytic_flops is absent. + const counts = cfg.cr_layer_counts; + let fMb = 0; + const af = data.analytic_flops; + if (af && af.per_cr_layer_flops) { + for (const cr of ["0", "4", "128"]) fMb += (counts[cr] || 0) * (af.per_cr_layer_flops[cr] || 0); + fMb += af.output_flops || 0; + if (af.mtp) { + fMb += (af.mtp.inner_layer_flops || 0) + + (af.mtp.eh_proj_flops || 0) + + (af.mtp.extra_logits_flops || 0) + + (af.mtp.hc_head_flops || 0); + } + } else { + for (const cr of ["0", "4", "128"]) fMb += (counts[cr] || 0) * (lt[cr].fFlops + lt[cr].bFlops); + fMb += nonLayerFlops(data, "output", "forward") + nonLayerFlops(data, "output", "backward"); + } + const flopsIter = fMb * ga * dp; + const tflopsGpu = flopsIter / iterS / world / 1e12; + + return { + lt, Df, Db, critF, critB, ga, pipeUs, bubbleFrac, world, dp, totalParams, + layoutApplied: Boolean(layoutStages), + layoutCounts: layoutInfo.counts, + layoutMessage: layoutInfo.message, + localModelParams: optParams.localModelParams, perRankParams, measuredOptUs: optParams.measuredUs, + mtp, optUs, iterUs, tokIter, tokS, tokSgpu, flopsIter, tflopsGpu, seq, + schedule, + }; +} + +// --------------------------------------------------------------------------- +// Rendering +// --------------------------------------------------------------------------- +function renderConfig() { + const cfg = STATE.data.model_config; + const grid = $("#config-grid"); + grid.innerHTML = ""; + const items = [ + ["Model", STATE.data.model.toUpperCase()], + ["Layers", cfg.num_layers], + ["Hidden", cfg.hidden_size], + ["Attn heads", cfg.num_attention_heads], + ["Experts", cfg.num_experts], + ["Router top-k", cfg.moe_router_topk], + ["MoE FFN", cfg.moe_ffn_hidden_size], + ["Index top-k", cfg.index_topk], + ["Vocab", cfg.vocab_size], + ["MTP depths", cfg.mtp_num_layers || 0], + ["Capture seq", STATE.data.capture.seq_length], + ["cr=0 layers", cfg.cr_layer_counts["0"]], + ["cr=4 layers", cfg.cr_layer_counts["4"]], + ["cr=128 layers", cfg.cr_layer_counts["128"]], + ]; + for (const [k, v] of items) { + const kv = el("div", { class: "kv" }); + kv.append(el("b", {}, k), el("span", {}, String(v))); + grid.append(kv); + } + // cr schedule strip + const strip = $("#cr-schedule"); + strip.innerHTML = ""; + const row = el("div", { class: "cr-schedule" }); + for (const cr of cfg.compress_ratios) row.append(el("span", { class: `cr-cell cr-${cr}`, title: `cr=${cr}` }, "x")); + strip.append(row); + strip.append(el("div", { class: "cr-legend", html: + 'cr=0 dense+SWAcr=4 CSAcr=128 HCA' })); +} + +function breakdownBlock(title, fwdList, bwdList) { + const c = STATE.controls, gpu = STATE.gpu; + const block = el("div", { class: "bd-block" }); + block.append(el("h3", {}, title)); + const scroll = el("div", { class: "bd-scroll" }); + const table = el("table", { class: "bd" }); + + const fwd = fwdList.map((r) => ({ r, t: rowScaledTime(r, gpu, c), phase: "F" })); + const bwd = bwdList.map((r) => ({ r, t: rowScaledTime(r, gpu, c), phase: "B" })).reverse(); + const cols = [...fwd, ...bwd]; + const dividerIdx = fwd.length; + + const head = el("tr"); + head.append(el("th", { class: "rowlab" }, "Module")); + cols.forEach((col, i) => { + const th = el("th", { class: i === dividerIdx ? "divider" : "" }); + th.append(el("div", {}, col.r.module.replace(/^(attn|moe)\./, ""))); + th.append(el("div", { class: "phase-tag" }, col.phase)); + head.append(th); + }); + table.append(head); + + const timeRow = el("tr"); + timeRow.append(el("td", { class: "rowlab" }, "Time µs")); + cols.forEach((col, i) => { + const cls = (col.r.class === "compute_bound" ? "cell-compute" : "cell-memory") + (i === dividerIdx ? " divider" : ""); + timeRow.append(el("td", { class: cls }, fmt(col.t, 0))); + }); + table.append(timeRow); + + const tfRow = el("tr"); + tfRow.append(el("td", { class: "rowlab" }, "TFLOP/s (kernel)")); + cols.forEach((col, i) => { + const tf = rowTflops(col.r, col.t); + tfRow.append(el("td", { class: i === dividerIdx ? "divider" : "" }, tf ? fmt(tf, 0) : "—")); + }); + table.append(tfRow); + + scroll.append(table); + block.append(scroll); + return block; +} + +function renderValidation(validation) { + const host = $("#validation-panel"); + if (!host) return; + host.innerHTML = ""; + if (!validation.errors.length && !validation.warnings.length) { + host.hidden = true; + return; + } + host.hidden = false; + if (validation.errors.length) { + const box = el("div", { class: "validation validation--error" }); + box.append(el("b", {}, "Errors")); + const ul = el("ul"); + for (const msg of validation.errors) ul.append(el("li", {}, msg)); + box.append(ul); + host.append(box); + } + if (validation.warnings.length) { + const box = el("div", { class: "validation validation--warn" }); + box.append(el("b", {}, "Warnings")); + const ul = el("ul"); + for (const msg of validation.warnings) ul.append(el("li", {}, msg)); + box.append(ul); + host.append(box); + } +} + +function renderBreakdown() { + $("#breakdown-gpu").textContent = `· ${STATE.gpu}`; + const panel = $("#breakdown-panel"); + if (panel) panel.classList.toggle("is-muted", STATE.controls?.modelMode === "manual"); + const note = $("#breakdown-manual-note"); + if (note) note.hidden = STATE.controls?.modelMode !== "manual"; + const host = $("#breakdown-blocks"); + host.innerHTML = ""; + const d = STATE.data; + host.append(breakdownBlock("Embedding", d.non_layer.embedding.forward, d.non_layer.embedding.backward)); + for (const cr of ["0", "4", "128"]) { + const L = d.layers[cr]; + host.append(breakdownBlock( + `Layer cr=${cr} — attention`, L.attention.forward, L.attention.backward)); + host.append(breakdownBlock( + `Layer cr=${cr} — MoE`, L.moe.forward, L.moe.backward)); + } + host.append(breakdownBlock("Output (norm + lm_head)", d.non_layer.output.forward, d.non_layer.output.backward)); + host.append(breakdownBlock("Loss", d.non_layer.loss.forward, d.non_layer.loss.backward)); +} + +function step(label, value, sub) { + const s = el("div", { class: "step" }); + s.append(el("span", { class: "num" }, value)); + s.append(el("b", {}, label)); + if (sub) { s.append(document.createElement("br")); s.append(el("small", {}, sub)); } + return s; +} + +function renderResults(validation) { + const c = STATE.controls, gpu = STATE.gpu, d = STATE.data; + $("#results-gpu").textContent = `· ${gpu}`; + const p = project(d, gpu, c, validation); + + const head = $("#results-headline"); + head.innerHTML = ""; + const steps = $("#results-steps"); + steps.innerHTML = ""; + if (!p) { + const errs = (validation && validation.errors) || []; + const summary = errs.length === 0 ? "Invalid controls" : `${errs.length} error${errs.length > 1 ? "s" : ""}`; + head.append(el("div", { class: "metric metric--error" }, el("b", {}, "Projection blocked"), el("span", {}, summary))); + if (errs.length) { + for (const msg of errs) { + const s = el("div", { class: "step step--error" }); + s.append(el("b", {}, "⚠ Validation error")); + s.append(document.createElement("br")); + s.append(el("small", {}, msg)); + steps.append(s); + } + } else { + steps.append(step("Fix validation errors", "", "Projection is not recomputed while errors are present.")); + } + return; + } + const mk = (label, val, primary) => { + const m = el("div", { class: "metric" + (primary ? " metric--primary" : "") }); + m.append(el("b", {}, label), el("span", {}, val)); + return m; + }; + head.append(mk("tokens/s/GPU", fmtInt(p.tokSgpu), true)); + head.append(mk("TFLOP/s/GPU", fmt(p.tflopsGpu, 0))); + head.append(mk("Iteration time", `${fmt(p.iterUs / 1000, 1)} ms`)); + head.append(mk("Pipeline bubble", `${fmt(p.bubbleFrac * 100, 1)} %`)); + + const ltDesc = ["0", "4", "128"].map((cr) => + `cr${cr}: F ${fmt(p.lt[cr].fwd, 0)} / B ${fmt(p.lt[cr].bwd, 0)} µs`).join(" · "); + const recomputeDesc = c.recompute === "first-n" ? `first ${c.recomputeLayers} layers/stage` : (c.recompute === "full" ? "recompute on" : "no recompute"); + const sourceTag = c.modelMode === "manual" ? "manual" : "trace"; + steps.append(step(`Per-layer fwd/bwd (µs, ${sourceTag}, ${recomputeDesc})`, "", ltDesc)); + steps.append(step("Critical PP stage (µs)", `F ${fmt(p.critF, 0)} + B ${fmt(p.critB, 0)}`, + `max over ${c.pp} stages; layout ${p.layoutApplied ? "applied" : "balanced fallback"} (${p.layoutMessage}); counts=[${p.layoutCounts.join(", ")}]; per-device fwd=[${p.Df.map((x) => fmt(x / 1000, 1)).join(", ")}] ms`)); + if ((d.model_config.mtp_num_layers || 0) > 0) { + steps.append(step("MTP on last stage", `F ${fmt(p.mtp.fwd, 0)} + B ${fmt(p.mtp.bwd, 0)} µs`, + `${d.model_config.mtp_num_layers} depth(s), inner cr=${(d.model_config.mtp_compress_ratios || [0])[0]}, eh_proj estimated from output GEMM throughput`)); + } + steps.append(step("GA (microbatches)", fmt(p.ga, 0), `GA = GBS ${c.gbs} / (DP ${p.dp} × MBS ${c.mbs})`)); + steps.append(step("Pipeline compute / iter", `${fmt(p.pipeUs / 1000, 2)} ms`, + `(GA + (PP−1)/VPP) × (F+B)_crit ; bubble ${fmt(p.bubbleFrac * 100, 1)}%`)); + const optHint = p.measuredOptUs + ? `; one-layer trace optimizer ref ${fmt(p.measuredOptUs / 1000, 2)} ms` + : ""; + steps.append(step("Optimizer step / iter", `${fmt(p.optUs / 1000, 2)} ms`, + `zero1: ${fmtInt(p.perRankParams / 1e6)}M optim params/rank (${fmtInt(p.localModelParams / 1e6)}M local model params) × ${c.bytesPerParam}B / HBM-BW / eff ${c.optEff}${optHint}`)); + steps.append(step("Iteration time", `${fmt(p.iterUs / 1000, 2)} ms`, "pipeline compute + optimizer (DP/PP comm assumed hidden)")); + steps.append(step("World size", fmtInt(p.world), `PP ${c.pp} × TP ${c.tp} × CP ${c.cp} × DP ${p.dp} (derived); EP ${c.ep} ≤ DP`)); + steps.append(step("Tokens / iter", fmtInt(p.tokIter), `GBS ${c.gbs} × seq ${p.seq}`)); + steps.append(step("tokens/s/GPU", fmtInt(p.tokSgpu), `${fmtInt(p.tokS)} tok/s ÷ ${p.world} GPUs`)); + steps.append(step("TFLOP/s/GPU", fmt(p.tflopsGpu, 0), "V4 analytic model FLOPs (Megatron convention)")); + + // self-consistency hint + const cap = d.capture; + if (cap && cap.measured_iter_time_ms) { + steps.append(step("Self-consistency (measured)", `${fmt(cap.measured_iter_time_ms, 1)} ms`, + "set PP=1,VPP=1,DP=1,EP=8,MBS=1,GBS=2 to compare against capture")); + } +} + +// --------------------------------------------------------------------------- +// Iteration timeline (design/07): 3-level composition view +// --------------------------------------------------------------------------- +const TL_CATS = ["attn", "mlp", "a2a", "misc"]; +const TL_CAT_LABEL = { attn: "attn", mlp: "mlp", a2a: "a2a (dispatch+combine)", misc: "misc/unattrib" }; +const CR_HEX = { "0": "#6b4a2d", "4": "#2d6b4a", "128": "#2d4a6b" }; + +// Map a module name to one of the Level-1 categories (design/07). +function moduleCategory(module) { + const m = String(module || ""); + if (m.startsWith("attn.")) return "attn"; + if (m === "moe.dispatch" || m === "moe.combine") return "a2a"; + if (/(^|\.)misc$|unattrib/.test(m)) return "misc"; + return "mlp"; // moe.grouped_gemm / shared_expert / router and any other moe.* +} + +// Per-cr forward/backward time split into categories, scaled to the active GPU. +// In manual mode the trace composition is rescaled so the bar total matches the +// effective (manual-overridden) per-layer time used by the projection. +function categoryBreakdown(data, cr, gpu, c) { + const out = { forward: { attn: 0, mlp: 0, a2a: 0, misc: 0 }, backward: { attn: 0, mlp: 0, a2a: 0, misc: 0 } }; + const L = data.layers[cr]; + if (!L) return out; + for (const bucket of [L.attention, L.moe]) { + for (const phase of ["forward", "backward"]) { + for (const r of bucket[phase]) out[phase][moduleCategory(r.module)] += rowScaledTime(r, gpu, c); + } + } + if (c.modelMode === "manual") { + const eff = effectiveLayerTimes(data, cr, gpu, c); + for (const phase of ["forward", "backward"]) { + const sum = TL_CATS.reduce((a, k) => a + out[phase][k], 0); + const target = phase === "forward" ? eff.fwd : eff.bwd; + if (sum > 0 && target > 0) for (const k of TL_CATS) out[phase][k] *= target / sum; + } + } + return out; +} + +// True when, in manual mode, the user has actually changed this cr's fwd/bwd away +// from the trace-derived baseline (comparison is rounded to µs so the prefilled +// baseline itself does not count as an edit). +function layerManualEdited(data, cr, gpu, c) { + if (c.modelMode !== "manual") return false; + const trace = layerTimes(data, cr, gpu, c); + const eff = effectiveLayerTimes(data, cr, gpu, c); + return Math.round(eff.fwd) !== Math.round(trace.fwd) || Math.round(eff.bwd) !== Math.round(trace.bwd); +} + +// Small hex lighten/darken for VPP chunk shading (Level 3). +function shadeHex(hex, amt) { + const n = parseInt(hex.slice(1), 16); + const clamp = (x) => Math.max(0, Math.min(255, Math.round(x))); + const r = clamp(((n >> 16) & 255) + amt), g = clamp(((n >> 8) & 255) + amt), b = clamp((n & 255) + amt); + return `#${((r << 16) | (g << 8) | b).toString(16).padStart(6, "0")}`; +} + +// Group PP devices with an identical ordered layer/recompute/non-layer signature +// so identical middle stages are drawn once (design/07 Level 2 dedup). +function dedupDevices(devices) { + const groups = []; + const byKey = new Map(); + for (const dev of devices) { + const sig = JSON.stringify({ + layers: dev.chunks.map((ch) => ch.layers.map((l) => `${l.cr}${l.recompute ? "r" : ""}`)), + emb: dev.hasEmb, out: dev.hasOut, mtp: dev.hasMtp, + }); + if (byKey.has(sig)) byKey.get(sig).members.push(dev.device); + else { + const g = { rep: dev, members: [dev.device] }; + byKey.set(sig, g); + groups.push(g); + } + } + return groups; +} + +// 1F1B (optionally interleaved) schedule simulator (design/07 Level 3). Returns +// per-device event lists with start/dur (µs) plus the drawn iteration length. +const TL_VIS_GA_CAP = 48; +function simulateSchedule(p, c, { interleaved }) { + const PP = c.pp; + const VPP = interleaved ? Math.max(1, c.vpp) : 1; + const C = PP * VPP; + const gaFull = Math.round(p.ga); + const GA = Math.min(gaFull, TL_VIS_GA_CAP); + const capped = gaFull > GA; + + // per-virtual-chunk fwd/bwd durations (k = 0..C-1, device = k % PP) + const fdur = new Array(C).fill(0), bdur = new Array(C).fill(0); + if (VPP === 1) { + for (let d = 0; d < PP; d++) { fdur[d] = p.Df[d]; bdur[d] = p.Db[d]; } + } else { + for (const ch of p.schedule.chunks) { fdur[ch.chunk] = ch.fwd; bdur[ch.chunk] = ch.bwd; } + // fold non-layer parts into first/last virtual chunk so the drawn length is + // consistent with the per-device Df/Db (which include them). + const dev0 = p.schedule.devices[0], devL = p.schedule.devices[PP - 1]; + fdur[0] += dev0.embFwd; bdur[0] += dev0.embBwd; + fdur[C - 1] += devL.outFwd + devL.mtpFwd; bdur[C - 1] += devL.outBwd + devL.mtpBwd; + } + + const total = GA * VPP; + const group = PP * VPP; + const fwdChunkMb = (i) => { + const inGroup = i % group; + const v = Math.floor(inGroup / PP); + const m = Math.floor(i / group) * PP + (inGroup % PP); + return { v, m }; + }; + + const ops = []; // per device ordered op list + for (let d = 0; d < PP; d++) { + const warmup = Math.min( + VPP === 1 ? PP - 1 - d : (PP - d - 1) * 2 + (VPP - 1) * PP, + total, + ); + const list = []; + for (let i = 0; i < warmup; i++) { + const { v, m } = fwdChunkMb(i); + list.push({ kind: "F", m, k: v * PP + d }); + } + let fptr = warmup, bptr = 0; + const n1f1b = total - warmup; + for (let i = 0; i < n1f1b; i++) { + const f = fwdChunkMb(fptr++); + list.push({ kind: "F", m: f.m, k: f.v * PP + d }); + const b = fwdChunkMb(bptr++); + list.push({ kind: "B", m: b.m, k: (VPP - 1 - b.v) * PP + d }); + } + for (let i = 0; i < warmup; i++) { + const b = fwdChunkMb(bptr++); + list.push({ kind: "B", m: b.m, k: (VPP - 1 - b.v) * PP + d }); + } + ops.push(list); + } + + // ASAP scheduling: forward flows k-1 -> k, backward flows k+1 -> k (p2p hidden). + const endF = new Map(), endB = new Map(); + const kf = (m, k) => `${m}:${k}`; + const ptr = new Array(PP).fill(0); + const free = new Array(PP).fill(0); + const events = Array.from({ length: PP }, () => []); + let remaining = ops.reduce((a, l) => a + l.length, 0); + let guard = remaining * 4 + 16; + while (remaining > 0 && guard-- > 0) { + let progressed = false; + for (let d = 0; d < PP; d++) { + if (ptr[d] >= ops[d].length) continue; + const op = ops[d][ptr[d]]; + let dep = 0, ready = true; + if (op.kind === "F") { + if (op.k > 0) { + const e = endF.get(kf(op.m, op.k - 1)); + if (e == null) ready = false; else dep = e; + } + } else { + if (op.k < C - 1) { + const e = endB.get(kf(op.m, op.k + 1)); + if (e == null) ready = false; else dep = e; + } else { + const e = endF.get(kf(op.m, op.k)); + if (e == null) ready = false; else dep = e; + } + } + if (!ready) continue; + const dur = op.kind === "F" ? fdur[op.k] : bdur[op.k]; + const start = Math.max(free[d], dep); + const end = start + dur; + events[d].push({ kind: op.kind, m: op.m, k: op.k, vpp: Math.floor(op.k / PP), start, dur }); + free[d] = end; + (op.kind === "F" ? endF : endB).set(kf(op.m, op.k), end); + ptr[d]++; remaining--; progressed = true; + } + if (!progressed) break; // dependency stall guard + } + const drawnUs = Math.max(0, ...free); + return { events, drawnUs, GA, capped, PP, VPP, C }; +} + +// --- cross-level selection (drill-down linkage) --- +function tlSelActive() { + return STATE.tlSel.cr != null || (STATE.tlSel.devices && STATE.tlSel.devices.length); +} +function tlSelectCr(cr) { + STATE.tlSel = STATE.tlSel.cr === cr ? { cr: null, devices: null } : { cr, devices: null }; + renderTimeline(); +} +function tlSelectDevices(devices) { + const same = STATE.tlSel.devices && STATE.tlSel.devices.length === devices.length && STATE.tlSel.devices.every((d, i) => d === devices[i]); + STATE.tlSel = same ? { cr: null, devices: null } : { cr: null, devices }; + renderTimeline(); +} +function tlClearSel() { + STATE.tlSel = { cr: null, devices: null }; + renderTimeline(); +} +// Compression ratios "active" under the current selection (drives L1 highlight). +function tlActiveCrSet(p) { + if (STATE.tlSel.cr != null) return new Set([String(STATE.tlSel.cr)]); + if (STATE.tlSel.devices && STATE.tlSel.devices.length) { + const s = new Set(); + for (const d of STATE.tlSel.devices) { + for (const ch of p.schedule.devices[d].chunks) for (const l of ch.layers) s.add(String(l.cr)); + } + return s; + } + return null; +} +const tlDevSet = () => (STATE.tlSel.devices && STATE.tlSel.devices.length ? new Set(STATE.tlSel.devices) : null); + +function renderTimeline() { + const host = $("#tl-body"); + if (!host) return; + $("#timeline-gpu").textContent = `· ${STATE.gpu}`; + const stacked = STATE.tlView === "stacked"; + document.querySelectorAll(".tl-view").forEach((t) => t.classList.toggle("is-active", (t.dataset.view === "stacked") === stacked)); + document.querySelectorAll(".tl-tab").forEach((t) => t.classList.toggle("is-active", Number(t.dataset.level) === STATE.tlLevel)); + const tabs = $("#tl-tabs"); + if (tabs) tabs.style.display = stacked ? "none" : ""; + host.innerHTML = ""; + const validation = validateControls(STATE.data, STATE.controls, STATE.gpu); + const p = project(STATE.data, STATE.gpu, STATE.controls, validation); + if (!p) { + host.append(el("p", { class: "tl-warn" }, "Timeline unavailable: fix the validation errors above.")); + return; + } + + // selection / linkage indicator: a fixed-position floating card appended to + // (NOT into #tl-body) so it is fully outside the timeline's flow and + // toggling a selection never shifts the page layout at all. + document.getElementById("tl-selbar-float")?.remove(); + if (tlSelActive()) { + const bar = el("div", { id: "tl-selbar-float", class: "tl-selbar" }); + const txt = el("div", { class: "tl-selbar__txt" }); + const desc = STATE.tlSel.cr != null + ? `Focused on cr=${STATE.tlSel.cr}` + : `Focused on PP rank(s) ${STATE.tlSel.devices.join(", ")}`; + txt.append(el("b", {}, desc)); + txt.append(el("span", { class: "muted" }, stacked ? " — highlighted across all levels" : " — highlighted; switch to Stacked to see all levels")); + bar.append(txt); + const btn = el("button", { class: "tl-clear" }, "Clear"); + btn.addEventListener("click", tlClearSel); + bar.append(btn); + document.body.append(bar); + } + + if (stacked) { + const section = (title, sub, fn) => { + const sec = el("div", { class: "tl-section" }); + const h = el("h3", { class: "tl-section__title" }, title); + if (sub) h.append(el("span", { class: "tl-section__sub" }, sub)); + sec.append(h); + const body = el("div", {}); + fn(body); + sec.append(body); + host.append(sec); + }; + section("Level 1 · single layer", "attn / mlp / a2a — click a cr to link", (b) => renderTimelineL1(b, p)); + section("Level 2 · pipeline ranks", "layer granularity — click a rank or a layer to link", (b) => renderTimelineL2(b, p)); + section("Level 3 · pipeline schedule", "1F1B / interleaved — click a device to link", (b) => renderTimelineL3(b, p)); + } else if (STATE.tlLevel === 1) renderTimelineL1(host, p); + else if (STATE.tlLevel === 2) renderTimelineL2(host, p); + else renderTimelineL3(host, p); +} + +// --- gantt export (SVG / PNG) --- +function downloadBlob(blob, filename) { + const url = URL.createObjectURL(blob); + const a = el("a", { href: url, download: filename }); + document.body.append(a); + a.click(); + a.remove(); + setTimeout(() => URL.revokeObjectURL(url), 2000); +} +function serializeGantt(svg) { + const clone = svg.cloneNode(true); + const cs = getComputedStyle(document.documentElement); + const bg = (cs.getPropertyValue("--bg").trim() || "#0f1218"); + let markup = new XMLSerializer().serializeToString(clone); + for (const v of ["--panel-2", "--border", "--muted", "--text", "--bg"]) { + markup = markup.split(`var(${v})`).join(cs.getPropertyValue(v).trim() || "#888"); + } + if (!markup.includes("xmlns=")) markup = markup.replace("]*>)/, `$1`); + return markup; +} +function exportGanttSvg(svg) { + downloadBlob(new Blob([serializeGantt(svg)], { type: "image/svg+xml" }), `dsv4-pp-schedule-${STATE.gpu}.svg`); +} +function exportGanttPng(svg) { + const markup = serializeGantt(svg); + const vb = svg.viewBox.baseVal, scale = 2; + const img = new Image(); + img.onload = () => { + const canvas = el("canvas"); + canvas.width = vb.width * scale; + canvas.height = vb.height * scale; + const ctx = canvas.getContext("2d"); + ctx.scale(scale, scale); + ctx.drawImage(img, 0, 0); + canvas.toBlob((b) => downloadBlob(b, `dsv4-pp-schedule-${STATE.gpu}.png`)); + }; + img.src = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(markup))); +} + +function catLegend() { + return el("div", { class: "tl-legend", html: + 'attn' + + 'mlp (experts)' + + 'a2a (dispatch+combine)' + + 'misc/unattributed' }); +} + +function stackedTrack(phaseObj, maxTotal) { + const track = el("div", { class: "tl-bar__track" }); + const total = TL_CATS.reduce((a, k) => a + phaseObj[k], 0); + for (const cat of TL_CATS) { + const t = phaseObj[cat]; + if (t <= 0) continue; + const w = maxTotal > 0 ? (t / maxTotal) * 100 : 0; + const seg = el("div", { + class: `tl-seg tl-c-${cat}`, + style: `width:${w}%`, + title: `${TL_CAT_LABEL[cat]}: ${fmt(t, 0)} µs (${fmt(total > 0 ? (t / total) * 100 : 0, 0)}%)`, + }, w > 6 ? cat : ""); + track.append(seg); + } + return { track, total }; +} + +// Single-segment bar used when a cr's layer time is manually set: no module split +// is possible, so show only the total fwd/bwd. +function aggregateTrack(timeUs, maxTotal, label) { + const track = el("div", { class: "tl-bar__track" }); + const w = maxTotal > 0 ? (timeUs / maxTotal) * 100 : 0; + track.append(el("div", { + class: "tl-seg tl-c-manual", style: `width:${w}%`, + title: `${label}: ${fmt(timeUs, 0)} µs (manual per-layer)`, + }, w > 6 ? "manual" : "")); + return { track, total: timeUs }; +} + +function renderTimelineL1(host, p) { + const d = STATE.data, gpu = STATE.gpu, c = STATE.controls; + host.append(el("p", { class: "tl-note" }, + "One representative layer per compression ratio (MoE is cr-independent, so only attention differs). Forward and backward split into attn / mlp / a2a; bar length is comparable across cr.")); + const crs = ["0", "4", "128"].filter((cr) => (d.model_config.cr_layer_counts?.[cr] || 0) > 0); + const crSet = tlActiveCrSet(p); + // Per-cr: whether the layer time is manually overridden (then no module split), + // the module breakdown, and the fwd/bwd totals used for the shared scale. + const info = {}; + let maxTotal = 0; + let anyManual = false; + for (const cr of crs) { + const edited = layerManualEdited(d, cr, gpu, c); + const bd = edited ? null : categoryBreakdown(d, cr, gpu, c); + const eff = effectiveLayerTimes(d, cr, gpu, c); + const fT = edited ? eff.fwd : TL_CATS.reduce((a, k) => a + bd.forward[k], 0); + const bT = edited ? eff.bwd : TL_CATS.reduce((a, k) => a + bd.backward[k], 0); + info[cr] = { edited, bd, fT, bT }; + anyManual = anyManual || edited; + maxTotal = Math.max(maxTotal, fT, bT); + } + for (const cr of crs) { + const { edited, bd, fT, bT } = info[cr]; + const isSel = STATE.tlSel.cr === cr; + const dim = crSet && !crSet.has(cr); + const row = el("div", { class: "tl-l1-row tl-clickable" + (isSel ? " is-sel" : "") + (dim ? " tl-dim" : "") }); + row.addEventListener("click", () => tlSelectCr(cr)); + const lab = el("div", { class: "tl-l1-lab" }); + lab.append(el("b", {}, `cr=${cr}`), el("small", {}, `×${d.model_config.cr_layer_counts[cr]} layers${edited ? " · manual" : ""}`)); + row.append(lab); + const bars = el("div", { class: "tl-bars" }); + for (const [tag, phase, tot] of [["fwd", "forward", fT], ["bwd", "backward", bT]]) { + const bar = el("div", { class: "tl-bar" }); + bar.append(el("span", { class: "tl-bar__tag" }, tag)); + const { track, total } = edited ? aggregateTrack(tot, maxTotal, `${tag} layer`) : stackedTrack(bd[phase], maxTotal); + bar.append(track); + bar.append(el("span", { class: "tl-bar__total" }, `${fmt(total, 0)} µs`)); + bars.append(bar); + } + row.append(bars); + host.append(row); + } + if (anyManual) { + host.append(el("div", { class: "tl-legend", html: + 'manual per-layer total (no module split)' })); + host.append(el("p", { class: "tl-note" }, + "A cr marked “manual” uses a hand-entered whole-layer fwd/bwd time, so it cannot be split into attn / mlp / a2a — only the total is shown. Switch layer timing back to “Trace-derived”, or click “Restore defaults”, to see the per-module breakdown again.")); + } + host.append(catLegend()); +} + +function renderTimelineL2(host, p) { + const c = STATE.controls; + host.append(el("p", { class: "tl-note" }, + "Each pipeline rank's layers (coloured by cr; hatched = recomputed). Identical ranks are drawn once. The critical stage (max fwd/bwd, sets the pipeline critical path) is outlined.")); + const groups = dedupDevices(p.schedule.devices); + const maxTotal = Math.max(1, ...p.schedule.devices.map((dv) => dv.Df + dv.Db)); + const selDevices = tlDevSet(); + const selCr = STATE.tlSel.cr != null ? String(STATE.tlSel.cr) : null; + for (const g of groups) { + const dev = g.rep; + const isCrit = dev.isCritF || dev.isCritB; + const isSelGroup = selDevices && g.members.some((m) => selDevices.has(m)); + const dimRow = (selDevices && !isSelGroup) || (selCr && !g.members.some((m) => p.schedule.devices[m].chunks.some((ch) => ch.layers.some((l) => String(l.cr) === selCr)))); + const row = el("div", { class: "tl-l2-row tl-clickable" + (isCrit ? " is-critical" : "") + (isSelGroup ? " is-sel" : "") + (dimRow ? " tl-dim" : "") }); + row.addEventListener("click", () => tlSelectDevices(g.members)); + const lab = el("div", { class: "tl-l2-lab" }); + const members = g.members; + const rangeTxt = members.length > 1 ? `PP ranks ${members[0]}–${members[members.length - 1]} (×${members.length})` : `PP rank ${members[0]}`; + lab.append(el("b", {}, rangeTxt)); + lab.append(el("small", {}, `${dev.chunks.reduce((a, ch) => a + ch.layers.length, 0)} layers · ${dev.chunks.length} vpp chunk(s)${isCrit ? " · critical" : ""}`)); + row.append(lab); + + const total = dev.Df + dev.Db; + const strip = el("div", { class: "tl-strip", style: `width:${(total / maxTotal) * 100}%` }); + if (dev.hasEmb) { + const t = dev.embFwd + dev.embBwd; + strip.append(el("div", { class: "tl-cell tl-cell--emb", style: `width:${(t / total) * 100}%`, title: `embedding F ${fmt(dev.embFwd, 0)} / B ${fmt(dev.embBwd, 0)} µs` })); + } + dev.chunks.forEach((ch, ci) => { + if (ci > 0 || dev.hasEmb) strip.append(el("div", { class: "tl-chunk-gap" })); + for (const l of ch.layers) { + const t = l.fwd + l.bwd; + const cellSel = selCr && String(l.cr) === selCr; + const cellDim = selCr && String(l.cr) !== selCr; + const cell = el("div", { + class: "tl-cell tl-clickable" + (l.recompute ? " tl-cell--recompute" : "") + (cellSel ? " tl-cell--sel" : "") + (cellDim ? " tl-dim" : ""), + style: `width:${(t / total) * 100}%; background:${CR_HEX[String(l.cr)] || "#555"}`, + title: `layer #${l.globalIdx} cr=${l.cr}${l.recompute ? " (recompute)" : ""} · F ${fmt(l.fwd, 0)} / B ${fmt(l.bwd, 0)} µs`, + }); + cell.addEventListener("click", (e) => { e.stopPropagation(); tlSelectCr(String(l.cr)); }); + strip.append(cell); + } + }); + if (dev.hasOut) { + const t = dev.outFwd + dev.outBwd + dev.mtpFwd + dev.mtpBwd; + strip.append(el("div", { class: "tl-chunk-gap" })); + strip.append(el("div", { class: "tl-cell tl-cell--out", style: `width:${(t / total) * 100}%`, title: `output/loss${dev.hasMtp ? "+MTP" : ""} F ${fmt(dev.outFwd + dev.mtpFwd, 0)} / B ${fmt(dev.outBwd + dev.mtpBwd, 0)} µs` })); + } + row.append(strip); + row.append(el("div", { class: "tl-l2-meta" }, `Df ${fmt(dev.Df / 1000, 2)} / Db ${fmt(dev.Db / 1000, 2)} ms`)); + host.append(row); + } + host.append(el("div", { class: "cr-legend", html: + 'cr=0cr=4cr=128' + + 'embeddingoutput/loss/MTP' })); +} + +function renderTimelineL3(host, p) { + const c = STATE.controls; + // Interleaving is driven directly by the VPP control (no separate toggle): + // VPP=1 is plain 1F1B, VPP>1 is interleaved 1F1B. + const interleaved = c.vpp > 1; + + const controls = el("div", { class: "tl-controls" }); + controls.append(el("span", { class: "mode-switch__label" }, `Schedule · 1F1B${interleaved ? ` (interleaved, VPP=${c.vpp})` : ""}`)); + + // Zoom slider: 1x fits the whole schedule in view (no scrollbar); zooming in + // widens the chart so the per-cell microbatch numbers become readable. + const zoomWrap = el("span", { class: "tl-zoom-wrap" }); + zoomWrap.append(el("span", { class: "mode-switch__label" }, "Zoom")); + const zoom = el("input", { type: "range", min: "1", max: "10", step: "0.5", value: String(STATE.tlZoom), class: "tl-zoom" }); + const zlab = el("span", { class: "tl-zoom-val" }, `${STATE.tlZoom}×`); + zoom.addEventListener("input", () => { zlab.textContent = `${zoom.value}×`; }); + zoom.addEventListener("change", () => { STATE.tlZoom = Number(zoom.value); renderTimeline(); }); + zoomWrap.append(zoom, zlab); + controls.append(zoomWrap); + host.append(controls); + if (!interleaved) { + host.append(el("p", { class: "tl-note" }, "VPP=1 → plain 1F1B. Set VPP>1 in the controls to interleave the schedule and shrink the pipeline bubble.")); + } + + const sim = simulateSchedule(p, c, { interleaved }); + const PP = sim.PP; + const rowH = 30, gap = 6, padL = 54, padT = 8, padB = 26; + // Zoom widens the coordinate system itself (more px per µs; font size stays + // fixed so cells become readable) instead of CSS-scaling the SVG. At 1x the + // chart is sized to the actual right-content width so it fits with no + // scrollbar; >1x overflows and scrolls horizontally, undistorted. + const avail = Math.max(600, (($("#tl-body")?.clientWidth) || 1100) - 16); + const plotW = Math.round((avail - padL) * STATE.tlZoom); + const width = padL + plotW + 8; + const scale = sim.drawnUs > 0 ? plotW / sim.drawnUs : 0; + const height = padT + PP * (rowH + gap) + padB; + + const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg"); + svg.setAttribute("class", "tl-gantt"); + svg.setAttribute("viewBox", `0 0 ${width} ${height}`); + svg.setAttribute("width", String(width)); + svg.setAttribute("height", String(height)); + const mkEl = (tag, attrs, text) => { + const n = document.createElementNS("http://www.w3.org/2000/svg", tag); + for (const [k, v] of Object.entries(attrs)) n.setAttribute(k, v); + if (text != null) n.textContent = text; + return n; + }; + + const selDevices = tlDevSet(); + for (let d = 0; d < PP; d++) { + const y = padT + d * (rowH + gap); + const isSel = selDevices ? selDevices.has(d) : false; + const g = mkEl("g", { class: "tl-devrow" }); + if (selDevices) g.setAttribute("opacity", isSel ? "1" : "0.3"); + g.append(mkEl("text", { x: padL - 8, y: y + rowH / 2 + 4, "text-anchor": "end", fill: "#e6eaf2", "font-size": "11" }, `dev ${d}`)); + const bgRect = mkEl("rect", { + x: padL, y, width: plotW, height: rowH, rx: "3", + fill: isSel ? "var(--panel)" : "var(--panel-2)", + stroke: isSel ? "#36c08f" : "var(--border)", "stroke-width": isSel ? "2" : "1", + style: "cursor:pointer", + }); + bgRect.addEventListener("click", () => tlSelectDevices([d])); + g.append(bgRect); + for (const ev of sim.events[d]) { + const x = padL + ev.start * scale; + const w = Math.max(1, ev.dur * scale); + const base = ev.kind === "F" ? "#4f8cff" : "#36c08f"; + const fill = shadeHex(base, ev.vpp * -26); + const rect = mkEl("rect", { + x, y: y + 2, width: w, height: rowH - 4, rx: "2", + fill, class: ev.kind === "F" ? "tl-fwd" : "tl-bwd", + }); + rect.append(mkEl("title", {}, `${ev.kind === "F" ? "Forward" : "Backward"} · microbatch ${ev.m}${sim.VPP > 1 ? ` · vpp chunk ${ev.vpp}` : ""} · compute ${fmt(ev.dur, 0)} µs · starts @ ${fmt(ev.start / 1000, 2)} ms (from iter start)`)); + g.append(rect); + if (w >= 8) g.append(mkEl("text", { x: x + w / 2, y: y + rowH / 2 + 3, "text-anchor": "middle", fill: "#000000", "font-size": "9" }, String(ev.m))); + } + svg.append(g); + } + // time axis ticks + const yb = padT + PP * (rowH + gap); + for (let i = 0; i <= 4; i++) { + const tx = padL + (plotW * i) / 4; + svg.append(mkEl("text", { x: tx, y: yb + 16, "text-anchor": "middle", fill: "#8d97a8", "font-size": "10" }, `${fmt((sim.drawnUs * i) / 4 / 1000, 1)} ms`)); + } + + const toolbar = el("div", { class: "tl-export" }); + const svgBtn = el("button", { class: "mode-tab" }, "Export SVG"); + const pngBtn = el("button", { class: "mode-tab" }, "Export PNG"); + svgBtn.addEventListener("click", () => exportGanttSvg(svg)); + pngBtn.addEventListener("click", () => exportGanttPng(svg)); + toolbar.append(svgBtn, pngBtn); + host.append(toolbar); + + const wrap = el("div", { class: "tl-gantt-wrap" }); + wrap.append(svg); + host.append(wrap); + + // legend + axis/tooltip explanation + self-check vs analytic pipe time + host.append(el("div", { class: "tl-legend", html: + 'forwardbackward' + + (sim.VPP > 1 ? 'lighter→darker = VPP chunk 0→' + (sim.VPP - 1) + '' : '') + + 'gaps = pipeline bubble (idle)' })); + host.append(el("p", { class: "tl-note tl-axis-note" }, + "X-axis = wall-clock time measured from the start of the pipeline (ms). Each cell is one microbatch's forward or backward on that device; the number inside is the microbatch index. Hover a cell to read its compute duration in µs (the “… µs” before @) and its start time in ms from the iteration start (the “… ms” after @).")); + + const analyticPipeUs = (p.ga + (c.pp - 1) / sim.VPP) * (p.critF + p.critB); // pre-calibFactor, matches the drawn schedule's VPP + const diff = analyticPipeUs > 0 ? Math.abs(sim.drawnUs - analyticPipeUs) / analyticPipeUs : 0; + const summary = el("div", { class: "tl-summary" }); + const gaTxt = sim.capped ? `${sim.GA} of ${Math.round(p.ga)} microbatches (capped for display)` : `${sim.GA} microbatches`; + summary.append(el("div", {}, `Drawn iteration (pipeline compute): ${fmt(sim.drawnUs / 1000, 2)} ms over ${gaTxt}; PP=${c.pp}, VPP=${interleaved ? c.vpp : 1}. Analytic bubble fraction ${fmt(p.bubbleFrac * 100, 1)}%.`)); + if (!sim.capped) { + const cls = diff > 0.08 ? "tl-warn" : ""; + summary.append(el("div", { class: cls }, + `Self-check vs analytic (GA + (PP−1)/VPP)·(F+B)_crit = ${fmt(analyticPipeUs / 1000, 2)} ms → ${fmt(diff * 100, 1)}% ${diff > 0.08 ? "difference (imbalanced stages; analytic uses per-device max)" : "match"}. Official iteration time uses the analytic value × calibFactor.`)); + } + host.append(summary); +} + +function renderAll() { + const validation = validateControls(STATE.data, STATE.controls, STATE.gpu); + // keep the derived-DP readonly box in sync + const w = STATE.controls; + const roDp = document.getElementById("ctl-dp"); + if (roDp) roDp.value = Number.isFinite(validation.dp) ? validation.dp : "—"; + renderValidation(validation); + renderModeSwitch(); + renderManualGrid(); + renderConfig(); + renderBreakdown(); + renderResults(validation); + renderTimeline(); +} + +// --------------------------------------------------------------------------- +// Bootstrap +// --------------------------------------------------------------------------- +async function init(model) { + try { + STATE.data = await loadModel(model); + STATE.controls = defaultControls(STATE.data); + $("#mock-badge").hidden = !(STATE.data.provenance && STATE.data.provenance.mock); + $("#model-select").value = STATE.data.model; + renderControls(); + renderAll(); + } catch (e) { + const err = $("#error-state"); + err.hidden = false; + err.textContent = String(e); + } +} + +globalThis.DSV4Projection = { + defaultControls, + derivedDP, + expandLayoutRepeats, + parsePipelineLayout, + validateControls, + effectiveLayerTimes, + project, + moduleCategory, + categoryBreakdown, + dedupDevices, + simulateSchedule, +}; + +if (typeof document !== "undefined") { + $("#model-select").addEventListener("change", (e) => { + const m = e.target.value; + const u = new URL(location); + u.searchParams.set("model", m); + history.replaceState(null, "", u); + init(m); + }); + + document.querySelectorAll(".tab").forEach((tab) => { + tab.addEventListener("click", () => { + document.querySelectorAll(".tab").forEach((t) => t.classList.remove("is-active")); + tab.classList.add("is-active"); + STATE.gpu = tab.dataset.gpu; + if (STATE.controls?.modelMode === "manual") prefillManual(STATE.gpu); + renderAll(); + }); + }); + + document.querySelectorAll(".mode-tab").forEach((tab) => { + tab.addEventListener("click", () => { + const mode = tab.dataset.mode; + if (!STATE.controls || STATE.controls.modelMode === mode) return; + STATE.controls.modelMode = mode; + if (mode === "manual") prefillManual(STATE.gpu); + renderAll(); + }); + }); + + document.querySelectorAll(".tl-tab").forEach((tab) => { + tab.addEventListener("click", () => { + STATE.tlLevel = Number(tab.dataset.level); + renderTimeline(); + }); + }); + + document.querySelectorAll(".tl-view").forEach((tab) => { + tab.addEventListener("click", () => { + STATE.tlView = tab.dataset.view; + renderTimeline(); + }); + }); + + // Re-fit the schedule Gantt to the content width when the window is resized. + let _tlResizeTimer; + window.addEventListener("resize", () => { + clearTimeout(_tlResizeTimer); + _tlResizeTimer = setTimeout(() => { if (STATE.data) renderTimeline(); }, 150); + }); + + init(modelFromQuery()); +} diff --git a/examples/deepseek-v4/projection/site/assets/style.css b/examples/deepseek-v4/projection/site/assets/style.css new file mode 100644 index 000000000..8d9d9d805 --- /dev/null +++ b/examples/deepseek-v4/projection/site/assets/style.css @@ -0,0 +1,333 @@ +:root { + --bg: #0f1218; + --panel: #171c26; + --panel-2: #1e2532; + --accent: #4f8cff; + --accent-2: #36c08f; + --text: #e6eaf2; + --muted: #8d97a8; + --border: #2a3342; + --warn: #e0a03a; + --compute: #2d4a6b; + --memory: #3a3346; +} + +* { box-sizing: border-box; } + +body { + margin: 0; + font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + background: var(--bg); + color: var(--text); + line-height: 1.45; +} + +.hero { + background: linear-gradient(160deg, #1b2330, #11151d); + border-bottom: 1px solid var(--border); + padding: 28px 0; +} +.hero__inner, .layout { max-width: 1600px; margin: 0 auto; padding: 0 24px; } +.eyebrow { color: var(--accent); font-weight: 600; letter-spacing: .08em; text-transform: uppercase; font-size: 12px; margin: 0 0 4px; } +.hero h1 { margin: 0 0 8px; font-size: 28px; } +.hero__summary { color: var(--muted); max-width: 760px; margin: 0 0 16px; } +.hero__row { display: flex; align-items: center; gap: 14px; margin-bottom: 14px; } + +.tabs { display: flex; gap: 8px; } +.tab { + background: var(--panel); color: var(--text); border: 1px solid var(--border); + padding: 9px 16px; border-radius: 8px 8px 0 0; cursor: pointer; font-size: 14px; +} +.tab.is-active { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; } + +/* Sticky switcher bar: model select + GPU tabs stay pinned to the top so the + user can switch without scrolling back up. */ +.switcher { + position: sticky; + top: 0; + z-index: 50; + background: rgba(15, 18, 24, .9); + backdrop-filter: blur(10px); + border-bottom: 1px solid var(--border); +} +.switcher__inner { + max-width: 1600px; + margin: 0 auto; + padding: 10px 24px; + display: flex; + align-items: center; + gap: 14px; + flex-wrap: wrap; +} +.switcher .tabs { margin-left: auto; } +.switcher .tab { border-radius: 8px; } + +.layout { padding-top: 22px; padding-bottom: 60px; display: flex; flex-direction: column; gap: 18px; } + +/* Split layout: sticky Projection-controls sidebar on the left (scrolls on its + own), everything else in a scrolling content column on the right. Lets you + tweak a control and watch the relevant panel on the right update in place. */ +.layout--split { flex-direction: row; align-items: flex-start; } +.sidebar { + flex: 0 0 300px; + position: sticky; + top: 72px; + max-height: calc(100vh - 88px); + overflow-y: auto; + overscroll-behavior: contain; /* wheel inside the panel does not scroll the page */ +} +.content { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 18px; } + +.panel { background: var(--panel); border: 1px solid var(--border); border-radius: 12px; padding: 18px 20px; } +.panel--accent { border-color: #2d4f7a; } +.panel h2 { margin: 0 0 12px; font-size: 18px; } +.panel__head { margin-bottom: 12px; } +.muted { color: var(--muted); font-weight: 400; font-size: 13px; } +.two-col { display: grid; grid-template-columns: 360px 1fr; gap: 18px; align-items: start; } + +.badge { font-size: 11px; padding: 3px 8px; border-radius: 999px; font-weight: 700; letter-spacing: .04em; } +.badge--warn { background: var(--warn); color: #1a1206; } + +select, input { + background: var(--panel-2); color: var(--text); border: 1px solid var(--border); + border-radius: 7px; padding: 7px 9px; font-size: 14px; +} +/* Drop the native number spinner buttons: in the narrow sidebar they eat ~17px + and clip long values (e.g. 15824 shown as "1582"). Values are typed, not + stepped, so the arrows add no value. */ +input[type="number"] { -moz-appearance: textfield; appearance: textfield; } +input[type="number"]::-webkit-outer-spin-button, +input[type="number"]::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; } +/* Slightly tighter type + padding for the sidebar grids so 5-6 digit numbers + fit their column without truncation. */ +.controls-grid input, .manual-row input { font-size: 13px; padding-left: 8px; padding-right: 8px; } +.field { display: flex; flex-direction: column; gap: 4px; } +.field--inline { flex-direction: row; align-items: center; gap: 8px; } +.field > span { font-size: 12px; color: var(--muted); } + +.kv-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 10px; } +.kv { background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; padding: 8px 10px; } +.kv b { display: block; font-size: 12px; color: var(--muted); font-weight: 500; } +.kv span { font-size: 15px; font-variant-numeric: tabular-nums; } + +.cr-schedule { margin-top: 12px; display: flex; flex-wrap: wrap; gap: 3px; } +.cr-cell { width: 16px; height: 16px; border-radius: 3px; font-size: 0; } +.cr-0 { background: #6b4a2d; } .cr-4 { background: #2d6b4a; } .cr-128 { background: #2d4a6b; } +.cr-legend { display: flex; gap: 14px; margin-top: 8px; font-size: 12px; color: var(--muted); } +.cr-legend i { display: inline-block; width: 12px; height: 12px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; } + +.bd-block { margin-bottom: 16px; } +.bd-block h3 { font-size: 14px; margin: 0 0 6px; } +.bd-scroll { overflow-x: auto; } +table.bd { border-collapse: collapse; font-size: 12px; min-width: 100%; } +table.bd th, table.bd td { border: 1px solid var(--border); padding: 5px 8px; text-align: right; white-space: nowrap; font-variant-numeric: tabular-nums; } +table.bd th { background: var(--panel-2); color: var(--muted); font-weight: 600; } +table.bd td.rowlab, table.bd th.rowlab { text-align: left; color: var(--muted); position: sticky; left: 0; background: var(--panel); } +.cell-compute { background: rgba(79,140,255,.10); } +.cell-memory { background: rgba(160,140,200,.07); } +.divider { border-left: 2px solid var(--accent) !important; } +.phase-tag { font-size: 10px; color: var(--accent-2); } + +/* Layer-timing mode switch (trace vs manual), styled like the GPU tabs. */ +.mode-switch { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; } +.mode-switch__label { font-size: 12px; color: var(--muted); } +.mode-switch__tabs { display: flex; gap: 6px; } +.mode-tab { + background: var(--panel-2); color: var(--text); border: 1px solid var(--border); + padding: 6px 12px; border-radius: 7px; cursor: pointer; font-size: 13px; +} +.mode-tab.is-active { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; } + +.manual-grid { margin-bottom: 14px; } +.manual-grid__header { display: flex; align-items: flex-start; justify-content: space-between; gap: 12px; } +.manual-grid__hint { margin: 0 0 10px; flex: 1; } +.manual-reset { + background: var(--panel-2); color: var(--text); border: 1px solid var(--border); + padding: 5px 12px; border-radius: 7px; cursor: pointer; font-size: 12px; white-space: nowrap; +} +.manual-reset:hover { border-color: var(--accent); } +.manual-rows { display: flex; flex-direction: column; gap: 8px; } +.manual-grid__subhead { margin: 12px 0 6px; font-size: 11px; text-transform: uppercase; letter-spacing: .05em; } +.manual-row__lab--nl { + display: inline-block; padding: 3px 7px; border-radius: 6px; align-self: center; + background: var(--panel-2); border: 1px solid var(--border); color: var(--text); font-size: 12px; +} +.manual-row { + display: grid; grid-template-columns: 92px 1fr 1fr; gap: 10px; align-items: end; +} +.manual-row__lab { font-size: 12px; padding-bottom: 8px; } +.cr-tag { + display: inline-block; padding: 3px 7px; border-radius: 6px; color: #e6eaf2; + font-variant-numeric: tabular-nums; align-self: center; padding-bottom: 3px; +} +.manual-row .cr-tag { margin-bottom: 0; } +.manual-row .field { min-width: 0; } +.manual-row input { width: 100%; min-width: 0; } + +#breakdown-panel.is-muted #breakdown-blocks { opacity: .5; } +.manual-note { margin: 8px 0 0; color: var(--warn); } + +.controls-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; } +.controls-grid .field--full { grid-column: 1 / -1; } +/* number inputs have an intrinsic min-width that overflows the 1fr column and + overlaps the results panel; force them to fit their grid cell. */ +.controls-grid .field { min-width: 0; } +.controls-grid input, .controls-grid select { width: 100%; min-width: 0; } + +.validation { + margin-top: 10px; + border-radius: 8px; + padding: 9px 11px; + font-size: 12px; +} +.validation + .validation { margin-top: 8px; } +.validation b { display: block; margin-bottom: 4px; } +.validation ul { margin: 0; padding-left: 18px; } +.validation--error { background: #3a1c1c; border: 1px solid #6b2d2d; color: #ffb4b4; } +.validation--warn { background: #352913; border: 1px solid #6a4d1c; color: #ffd28a; } + +.headline { display: flex; flex-wrap: wrap; gap: 14px; margin-bottom: 16px; } +.metric { background: var(--panel-2); border: 1px solid var(--border); border-radius: 10px; padding: 12px 16px; min-width: 150px; } +.metric b { display: block; font-size: 12px; color: var(--muted); } +.metric span { font-size: 22px; font-weight: 700; font-variant-numeric: tabular-nums; } +.metric.metric--primary { border-color: var(--accent-2); } +.metric.metric--primary span { color: var(--accent-2); } +.metric.metric--error { border-color: #6b2d2d; } +.metric.metric--error span { color: #ffb4b4; font-size: 18px; } + +.steps { display: flex; flex-direction: column; gap: 8px; } +.step { background: var(--panel-2); border: 1px solid var(--border); border-radius: 8px; padding: 9px 12px; font-size: 13px; } +.step b { color: var(--accent); } +.step .num { float: right; font-variant-numeric: tabular-nums; color: var(--text); } +.step small { color: var(--muted); } +.step--error { background: #2a1616; border-color: #6b2d2d; } +.step--error b { color: #ffb4b4; } +.step--error small { color: #ffd0d0; } + +.state--error { background: #3a1c1c; border: 1px solid #6b2d2d; color: #ffb4b4; padding: 12px 16px; border-radius: 8px; } +.site-footer { border-top: 1px solid var(--border); padding: 20px 24px; color: var(--muted); font-size: 13px; max-width: 1600px; margin: 0 auto; } +code { background: var(--panel-2); padding: 1px 5px; border-radius: 4px; } + +@media (max-width: 880px) { + .two-col { grid-template-columns: 1fr; } + .layout--split { flex-direction: column; } + .sidebar { position: static; max-height: none; flex-basis: auto; width: 100%; overflow: visible; } +} + +/* --------------------------------------------------------------------------- + Iteration timeline (design/07): 3-level composition view + --------------------------------------------------------------------------- */ +.tl-tabs { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; } +.tl-tab { + background: var(--panel-2); color: var(--text); border: 1px solid var(--border); + padding: 6px 14px; border-radius: 7px; cursor: pointer; font-size: 13px; +} +.tl-tab.is-active { background: var(--accent); border-color: var(--accent); color: #fff; font-weight: 600; } + +.tl-note { color: var(--muted); font-size: 12px; margin: 0 0 12px; } +.tl-controls { display: flex; align-items: center; gap: 10px; margin-bottom: 12px; flex-wrap: wrap; } + +/* category colours (Level 1 / shared legend) */ +.tl-legend { display: flex; gap: 16px; flex-wrap: wrap; margin: 10px 0 0; font-size: 12px; color: var(--muted); } +.tl-legend i { display: inline-block; width: 12px; height: 12px; border-radius: 3px; margin-right: 5px; vertical-align: -1px; } +.tl-c-attn { background: #4f8cff; } +.tl-c-mlp { background: #36c08f; } +.tl-c-a2a { background: #e0a03a; } +.tl-c-misc { background: #8d97a8; } +.tl-c-emb { background: #a06cd5; } +.tl-c-out { background: #d5675a; } +.tl-c-manual { background: #9a6cff; } + +/* Level 1: per-cr bidirectional stacked bars */ +.tl-l1-row { display: grid; grid-template-columns: 120px 1fr; gap: 12px; align-items: center; margin-bottom: 14px; } +.tl-l1-lab { font-size: 13px; } +.tl-l1-lab small { display: block; color: var(--muted); font-size: 11px; } +.tl-bars { display: flex; flex-direction: column; gap: 6px; } +.tl-bar { display: flex; align-items: center; gap: 8px; } +.tl-bar__tag { width: 34px; font-size: 11px; color: var(--muted); text-align: right; } +.tl-bar__track { position: relative; flex: 1; height: 26px; background: var(--panel-2); border: 1px solid var(--border); border-radius: 6px; overflow: hidden; display: flex; } +.tl-seg { height: 100%; display: flex; align-items: center; justify-content: center; font-size: 10px; color: #08101c; overflow: hidden; white-space: nowrap; cursor: default; } +.tl-seg.tl-c-misc, .tl-seg.tl-c-out, .tl-seg.tl-c-manual { color: #f5f7fb; } +.tl-bar__total { width: 70px; font-size: 12px; font-variant-numeric: tabular-nums; color: var(--text); } + +/* Level 2: per-device layer strips */ +.tl-l2-row { display: grid; grid-template-columns: 150px 1fr 120px; gap: 12px; align-items: center; margin-bottom: 8px; } +.tl-l2-lab { font-size: 12px; } +.tl-l2-lab small { display: block; color: var(--muted); font-size: 11px; } +.tl-strip { display: flex; height: 22px; border: 1px solid var(--border); border-radius: 5px; overflow: hidden; background: var(--panel-2); } +.tl-cell { height: 100%; min-width: 2px; border-right: 1px solid rgba(0,0,0,.25); } +.tl-cell:last-child { border-right: none; } +.tl-cell--recompute { background-image: repeating-linear-gradient(45deg, transparent, transparent 3px, rgba(255,255,255,.35) 3px, rgba(255,255,255,.35) 5px); } +.tl-chunk-gap { width: 3px; background: var(--bg); } +.tl-cell--emb { background: #a06cd5 !important; } +.tl-cell--out { background: #d5675a !important; } +.tl-l2-meta { font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; } +.tl-l2-row.is-critical .tl-strip { outline: 2px solid var(--accent-2); outline-offset: 1px; } +.tl-l2-row.is-critical .tl-l2-lab { color: var(--accent-2); } + +/* Level 3: pipeline schedule Gantt */ +.tl-gantt-wrap { overflow-x: auto; } +.tl-gantt { display: block; } +/* text colours are set via inline `fill` attributes in app.js (a stylesheet + `fill` here would override those SVG presentation attributes, e.g. forcing the + in-cell microbatch numbers grey instead of black). */ +.tl-gantt rect.tl-fwd { stroke: rgba(0,0,0,.35); stroke-width: .5; } +.tl-gantt rect.tl-bwd { stroke: rgba(0,0,0,.35); stroke-width: .5; } +.tl-gantt rect.tl-bubble { fill: transparent; } +.tl-summary { font-size: 12px; color: var(--muted); margin-top: 10px; font-variant-numeric: tabular-nums; } +.tl-warn { color: var(--warn); } + +/* layout switch + level tabs on one row */ +.tl-head-row { display: flex; align-items: center; gap: 18px; flex-wrap: wrap; margin-top: 10px; } +.tl-viewswitch { display: flex; align-items: center; gap: 8px; } +.tl-view { + background: var(--panel-2); color: var(--text); border: 1px solid var(--border); + padding: 6px 12px; border-radius: 7px; cursor: pointer; font-size: 13px; +} +.tl-view.is-active { background: var(--accent-2); border-color: var(--accent-2); color: #06231a; font-weight: 600; } + +/* stacked sections */ +.tl-section { border-top: 1px solid var(--border); padding-top: 14px; margin-top: 14px; } +.tl-section:first-of-type { border-top: none; padding-top: 0; margin-top: 4px; } +.tl-section__title { font-size: 14px; margin: 0 0 10px; } +.tl-section__sub { font-size: 12px; color: var(--muted); font-weight: 400; margin-left: 8px; } + +/* linkage selection indicator — floating card, does not affect page layout */ +.tl-selbar { + position: fixed; + right: 24px; + bottom: 24px; + z-index: 60; + display: flex; align-items: center; gap: 14px; + background: rgba(23, 28, 38, .97); + border: 1px solid var(--accent-2); + box-shadow: 0 8px 26px rgba(0, 0, 0, .5); + border-radius: 10px; + padding: 10px 14px; font-size: 12px; color: var(--text); + max-width: 360px; + backdrop-filter: blur(6px); +} +.tl-selbar__txt { line-height: 1.35; } +.tl-clear { + background: var(--panel-2); color: var(--text); border: 1px solid var(--border); + padding: 5px 14px; border-radius: 7px; cursor: pointer; font-size: 12px; white-space: nowrap; +} +.tl-clear:hover { border-color: var(--accent-2); } +@media (max-width: 640px) { .tl-selbar { left: 16px; right: 16px; bottom: 16px; max-width: none; } } + +/* linkage highlight states (shared) */ +.tl-clickable { cursor: pointer; } +.tl-dim { opacity: .32; transition: opacity .12s; } +.tl-l1-row.is-sel, .tl-l2-row.is-sel .tl-strip { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 6px; } +.tl-l1-row.is-sel { background: rgba(79,140,255,.08); border-radius: 8px; } +.tl-cell--sel { outline: 2px solid #fff; outline-offset: -2px; } + +/* gantt export toolbar */ +.tl-export { display: flex; gap: 8px; margin-bottom: 8px; } +.tl-export .mode-tab { background: var(--panel-2); } + +/* gantt zoom slider */ +.tl-zoom-wrap { display: inline-flex; align-items: center; gap: 8px; margin-left: 6px; } +.tl-zoom { width: 160px; accent-color: var(--accent); } +.tl-zoom-val { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; min-width: 30px; } +.tl-axis-note { margin: 8px 0 0; } diff --git a/examples/deepseek-v4/projection/site/data/flash.json b/examples/deepseek-v4/projection/site/data/flash.json new file mode 100644 index 000000000..31a744e44 --- /dev/null +++ b/examples/deepseek-v4/projection/site/data/flash.json @@ -0,0 +1,1727 @@ +{ + "schema_version": 1, + "model": "flash", + "analytic_flops": { + "per_cr_layer_flops": { + "0": 28775232307200, + "4": 36583482851328, + "128": 29596695134208 + }, + "output_flops": 13013750906880, + "mtp": { + "num_layers": 1, + "compress_ratio": 4, + "inner_layer_flops": 36583482851328, + "eh_proj_flops": 824633720832, + "extra_logits_flops": 13013750906880, + "hc_head_flops": 3221225472 + }, + "seq": 4096, + "note": "per-layer and MTP (B=1) Megatron-convention FLOPs at capture seq; site multiplies by GA x DP" + }, + "generated_at": "2026-06-30T13:59:46Z", + "provenance": { + "traces": { + "0": "/apps/tas/0_public/data/traces/dsv4_projection/projection_flash_cr0_seq4096_ep8/tensorboard/primus-megatron-exp[projection_flash_cr0_seq4096_ep8]-rank[0].1782149271209635673.pt.trace.json", + "4": "/apps/tas/0_public/data/traces/dsv4_projection/projection_flash_cr4_seq4096_ep8/tensorboard/primus-megatron-exp[projection_flash_cr4_seq4096_ep8]-rank[0].1782148745954636194.pt.trace.json", + "128": "/apps/tas/0_public/data/traces/dsv4_projection/projection_flash_cr128_seq4096_ep8/tensorboard/primus-megatron-exp[projection_flash_cr128_seq4096_ep8]-rank[0].1782149357925690324.pt.trace.json" + }, + "graphed_crs_estimated_from_cr4": [], + "dropped_stall_us_per_mb": { + "0": 0.0, + "4": 19280.7, + "128": 0.0 + }, + "note": "cr in graphed_crs_estimated_from_cr4 were CUDA-graph/stream-captured (compute not visible in trace); their breakdown is copied from cr=4 as an estimate. Re-run those cr with graph capture disabled for exact numbers. dropped_stall_us_per_mb: per-mb layer-compute kernel time dropped as implausible one-off device stalls (> _MAX_PLAUSIBLE_LAUNCH_US)." + }, + "capture": { + "gpu": "MI355X", + "seq_length": 4096, + "micro_batch_size": 1, + "tokens_per_microbatch": 4096, + "ep": 8, + "ga_for_capture": 2, + "optimizer": "adam", + "distributed_optimizer": false, + "recompute": "off", + "measured_iter_time_ms": 4076.0, + "measured_anchor": { + "config": "PP8/VPP1/EP8/TP1, world 64, MBS1/GBS128, seq4096, full recompute, layout Et*4|t*5|(t*6|)*5,t*4mL (8-node MI355X)", + "iter_ms": 4076.0, + "tflops_gpu": 722, + "tok_s_gpu": 2010, + "calib_factor": 0.87, + "tolerance": "<0.1%" + } + }, + "model_config": { + "num_layers": 43, + "hidden_size": 4096, + "num_attention_heads": 64, + "kv_channels": 512, + "num_experts": 256, + "moe_router_topk": 6, + "moe_ffn_hidden_size": 2048, + "moe_shared_expert_intermediate_size": 2048, + "index_topk": 512, + "vocab_size": 129280, + "mtp_num_layers": 1, + "mtp_compress_ratios": [ + 4 + ], + "pipeline_layout": "Et*10|t*11|t*11|t*11mL", + "compress_ratios": [ + 0, + 0, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 0 + ], + "cr_layer_counts": { + "0": 3, + "4": 20, + "128": 20 + }, + "total_params": 290419900416 + }, + "hardware": { + "MI355X": { + "peak_tflops_bf16": 2500.0, + "hbm_bandwidth_gbps": 8000.0 + }, + "MI455X": { + "peak_tflops_bf16": 10000.0, + "hbm_bandwidth_gbps": 19600.0 + } + }, + "layers": { + "0": { + "attention": { + "forward": [ + { + "module": "attn.misc", + "time_us": 2768.835, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::bfloat16_copy_kern", + "time_us": 600.524 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::AUnaryFunctor", + "time_us": 252.844 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 8, at::native::gpu_kernel", + "time_us": 190.953 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespa", + "time_us": 151.259 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 4, at::native::gpu_kernel", + "time_us": 136.028 + } + ] + }, + { + "module": "attn.proj", + "time_us": 732.222, + "class": "compute_bound", + "flop_class": "gemm", + "flops": 449360953344.0, + "tflops": 613.7, + "kernels": [ + { + "name": "Cijk_Ailk_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x16x32_MI16x16x1_SN_LDSB1_AFC1", + "time_us": 260.835 + }, + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x256x64_MI16x16x1_CMS_SN_LDSB0", + "time_us": 226.171 + }, + { + "name": "Custom_Cijk_Alik_Bljk_BBS_BH_MT256x256x64_MI16x16x1_UserArgs_shortname1_gfx950", + "time_us": 96.207 + }, + { + "name": "Cijk_Alik_Bljk_S_B_Bias_HA_S_SAV_UserArgs_MT32x64x128_MI16x16x1_SN_LDSB1_AFC1_AF", + "time_us": 56.9 + }, + { + "name": "Cijk_Alik_Bljk_S_B_Bias_HA_S_SAV_UserArgs_MT64x64x128_MI16x16x1_SN_LDSB1_AFC1_AF", + "time_us": 36.157 + }, + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x128x64_MI16x16x1_SN_LDSB1_AFC", + "time_us": 29.943 + } + ] + }, + { + "module": "attn.norm", + "time_us": 496.029, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespa", + "time_us": 93.8 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 4, at::native::gpu_kernel", + "time_us": 92.809 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::bfloat16_copy_kern", + "time_us": 83.298 + }, + { + "name": "void at::native::reduce_kernel<512, 1, at::native::ReduceOp(HIP_vector_type(int const*, int*, int ", + "time_us": 111.084 + }, + { + "name": "void primus_turbo::deep_ep::layout::get_dispatch_layout<256, 4, 8>(long const*, ", + "time_us": 19.553 + } + ] + }, + { + "module": "moe.combine", + "time_us": 321.999, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::combine(hip_bfloat", + "time_us": 231.632 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 90.367 + } + ] + }, + { + "module": "moe.shared_expert", + "time_us": 112.521, + "class": "compute_bound", + "flop_class": "gemm", + "flops": 68719476736.0, + "tflops": 610.7, + "kernels": [ + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x256x64_MI16x16x1_CMS_SN_LDSB0", + "time_us": 78.11 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 8, at::native::gpu_kernel", + "time_us": 21.219 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::(anonymous namespa", + "time_us": 5.396 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::BinaryFunctor", + "time_us": 8.02 + }, + { + "name": "void at::native::_scatter_gather_elementwise_kernel<256, 4, at::native::_cuda_sc", + "time_us": 6.869 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 4, at::native::gpu_kernel", + "time_us": 5.616 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespa", + "time_us": 4.652 + } + ] + } + ], + "backward": [ + { + "module": "moe.grouped_gemm", + "time_us": 1916.666, + "class": "compute_bound", + "flop_class": "grouped_gemm", + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void ck_tile::kentry<1, ck_tile::GroupedGemmKernel(std::bfl", + "time_us": 115.553 + }, + { + "name": "void primus_turbo::compute_grouped_gemm_args(hip_bfloat", + "time_us": 238.449 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 59.93 + } + ] + }, + { + "module": "moe.dispatch", + "time_us": 242.331, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::dispatch<8, 1024>(HIP_vector_type(int const*, int", + "time_us": 20.566 + } + ] + }, + { + "module": "moe.router", + "time_us": 44.476, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "_sinkhorn_bwd_kernel", + "time_us": 44.476 + } + ] + } + ] + } + }, + "4": { + "attention": { + "forward": [ + { + "module": "attn.misc", + "time_us": 2773.299, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::bfloat16_copy_kern", + "time_us": 603.359 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::AUnaryFunctor", + "time_us": 247.801 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 8, at::native::gpu_kernel", + "time_us": 192.373 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespa", + "time_us": 150.529 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 4, at::native::gpu_kernel", + "time_us": 136.255 + } + ] + }, + { + "module": "attn.indexer", + "time_us": 797.919, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 8, at::native::gpu_kernel", + "time_us": 152.083 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::(anonymous namespa", + "time_us": 98.513 + }, + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x256x32_MI16x16x1_SN_LDSB1_AFC", + "time_us": 86.043 + }, + { + "name": "void at::native::reduce_kernel<128, 4, at::native::ReduceOp(hip_bfloat", + "time_us": 230.235 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 100.447 + } + ] + }, + { + "module": "moe.dispatch", + "time_us": 279.761, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::dispatch<8, 1024>(HIP_vector_type(int const*, int*, int ", + "time_us": 27.653 + }, + { + "name": "void primus_turbo::deep_ep::layout::get_dispatch_layout<256, 4, 8>(long const*, ", + "time_us": 19.776 + } + ] + }, + { + "module": "moe.shared_expert", + "time_us": 112.854, + "class": "compute_bound", + "flop_class": "gemm", + "flops": 68719476736.0, + "tflops": 608.9, + "kernels": [ + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x256x64_MI16x16x1_CMS_SN_LDSB0", + "time_us": 78.307 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 8, at::native::gpu_kernel", + "time_us": 21.372 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::(anonymous namespa", + "time_us": 5.393 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::BinaryFunctor", + "time_us": 8.086 + }, + { + "name": "void at::native::_scatter_gather_elementwise_kernel<256, 4, at::native::_cuda_sc", + "time_us": 6.905 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 4, at::native::gpu_kernel", + "time_us": 5.669 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespa", + "time_us": 4.696 + } + ] + } + ], + "backward": [ + { + "module": "moe.grouped_gemm", + "time_us": 1896.364, + "class": "compute_bound", + "flop_class": "grouped_gemm", + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void ck_tile::kentry<1, ck_tile::GroupedGemmKernel(std::bfl", + "time_us": 114.644 + }, + { + "name": "void primus_turbo::compute_grouped_gemm_variable_k_args(HIP_vector_type(int const*, int", + "time_us": 178.088 + } + ] + }, + { + "module": "moe.combine", + "time_us": 357.633, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::combine(hip_bfloat", + "time_us": 237.535 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 120.097 + } + ] + }, + { + "module": "moe.router", + "time_us": 44.726, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "_sinkhorn_bwd_kernel", + "time_us": 44.726 + } + ] + } + ] + } + }, + "128": { + "attention": { + "forward": [ + { + "module": "attn.misc", + "time_us": 2784.297, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::bfloat16_copy_kern", + "time_us": 605.113 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::AUnaryFunctor", + "time_us": 248.877 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 8, at::native::gpu_kernel", + "time_us": 191.946 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespa", + "time_us": 151.909 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 4, at::native::gpu_kernel", + "time_us": 136.028 + } + ] + }, + { + "module": "attn.norm", + "time_us": 851.58, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::(anonymous namespace)::CatArrayBatchedCopy(HIP_vector_type(int const*, int*, int ", + "time_us": 60.447 + }, + { + "name": "void primus_turbo::deep_ep::layout::get_dispatch_layout<256, 4, 8>(long const*, ", + "time_us": 20.186 + } + ] + }, + { + "module": "moe.combine", + "time_us": 309.929, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::combine(hip_bfloat", + "time_us": 227.382 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 82.547 + } + ] + }, + { + "module": "moe.shared_expert", + "time_us": 112.514, + "class": "compute_bound", + "flop_class": "gemm", + "flops": 68719476736.0, + "tflops": 610.8, + "kernels": [ + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x256x64_MI16x16x1_CMS_SN_LDSB0", + "time_us": 77.993 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 8, at::native::gpu_kernel", + "time_us": 21.266 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::(anonymous namespa", + "time_us": 5.43 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::BinaryFunctor", + "time_us": 14.203 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::bfloat16tofloat32_", + "time_us": 10.482 + }, + { + "name": "void at::native::_scatter_gather_elementwise_kernel<256, 4, at::native::_cuda_sc", + "time_us": 7.482 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 4, at::native::gpu_kernel", + "time_us": 6.356 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespa", + "time_us": 4.886 + } + ] + } + ], + "backward": [ + { + "module": "moe.grouped_gemm", + "time_us": 1888.822, + "class": "compute_bound", + "flop_class": "grouped_gemm", + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void ck_tile::kentry<1, ck_tile::GroupedGemmKernel(std::bfl", + "time_us": 113.277 + }, + { + "name": "void primus_turbo::compute_grouped_gemm_variable_k_args(hip_bfloat", + "time_us": 233.195 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 110.397 + } + ] + }, + { + "module": "moe.dispatch", + "time_us": 225.508, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::dispatch<8, 1024>(HIP_vector_type(int const*, int", + "time_us": 4.29 + } + ] + }, + { + "module": "moe.router", + "time_us": 44.606, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "_sinkhorn_bwd_kernel", + "time_us": 44.606 + } + ] + } + ] + } + } + }, + "non_layer": { + "embedding": { + "forward": [ + { + "module": "embedding", + "time_us": 7.84, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::vectorized_gather_kernel<16, long>(char*, char*, long*, int, lo", + "time_us": 7.84 + } + ] + } + ], + "backward": [ + { + "module": "embedding", + "time_us": 86.591, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::(anonymous namespace)::compute_grad_weight", + "time_us": 33.993 + }, + { + "name": "void rocprim::ROCPRIM_400200_NS::detail::trampoline_kernel(lon", + "time_us": 21.21 + }, + { + "name": "void rocprim::ROCPRIM_400200_NS::detail::init_lookback_scan_state_kernel(long*, ", + "time_us": 2.08 + }, + { + "name": "void at::native::(anonymous namespace)::krn_partials_per_segment(long*, lo", + "time_us": 2.023 + } + ] + } + ] + }, + "output": { + "forward": [ + { + "module": "output", + "time_us": 1553.935, + "class": "compute_bound", + "flop_class": "gemm", + "flops": 2171374403584.0, + "tflops": 1397.3, + "kernels": [ + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x256x64_MI16x16x1_CMS_SN_LDSB0", + "time_us": 1529.095 + }, + { + "name": "Cijk_Alik_Bljk_S_B_Bias_HA_S_SAV_UserArgs_MT16x16x512_MI16x16x1_SN_LDSB1_AFC1_AF", + "time_us": 24.84 + } + ] + } + ], + "backward": [] + }, + "loss": { + "forward": [ + { + "module": "loss", + "time_us": 368.303, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "cross_entropy_kernel", + "time_us": 235.982 + }, + { + "name": "online_softmax_kernel", + "time_us": 132.321 + } + ] + } + ], + "backward": [ + { + "module": "loss", + "time_us": 197.392, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "element_mul_kernel", + "time_us": 197.392 + } + ] + } + ] + } + }, + "optimizer": { + "type": "adam", + "measured_params": null, + "time_us": 18459.0, + "bytes_per_param": 30, + "class": "memory_bound", + "note": "Adam mixed-precision step traffic in bytes/param; measured one-layer optimizer-step kernel time is a sanity reference" + }, + "comm": { + "ep_dispatch_us": null, + "ep_combine_us": null, + "note": "EP dispatch/combine are memory_bound rows inside moe; informational" + } +} diff --git a/examples/deepseek-v4/projection/site/data/pro.json b/examples/deepseek-v4/projection/site/data/pro.json new file mode 100644 index 000000000..8cfd34cb4 --- /dev/null +++ b/examples/deepseek-v4/projection/site/data/pro.json @@ -0,0 +1,1750 @@ +{ + "schema_version": 1, + "model": "pro", + "analytic_flops": { + "per_cr_layer_flops": { + "0": 76885870510080, + "4": 95420801875968, + "128": 78425716948992 + }, + "output_flops": 22774064087040, + "mtp": { + "num_layers": 1, + "compress_ratio": 4, + "inner_layer_flops": 95420801875968, + "eh_proj_flops": 2525440770048, + "extra_logits_flops": 22774064087040, + "hc_head_flops": 5637144576 + }, + "seq": 4096, + "note": "per-layer and MTP (B=1) Megatron-convention FLOPs at capture seq; site multiplies by GA x DP" + }, + "generated_at": "2026-06-30T13:59:43Z", + "provenance": { + "traces": { + "0": "/apps/tas/wenx/workspace/Primus-deepseek-v4/output/amd/tas-mi355x-20260618/projection_pro_cr0_seq4096_ep8/tensorboard/primus-megatron-exp[projection_pro_cr0_seq4096_ep8]-rank[0].1781792532762897620.pt.trace.json", + "4": "/apps/tas/wenx/workspace/Primus-deepseek-v4/output/amd/tas-mi355x-20260618/projection_pro_cr4_seq4096_ep8/tensorboard/primus-megatron-exp[projection_pro_cr4_seq4096_ep8]-rank[0].1781792606762968569.pt.trace.json", + "128": "/apps/tas/wenx/workspace/Primus-deepseek-v4/output/amd/tas-mi355x-20260618/projection_pro_cr128_seq4096_ep8/tensorboard/primus-megatron-exp[projection_pro_cr128_seq4096_ep8]-rank[0].1781792680203624431.pt.trace.json" + }, + "graphed_crs_estimated_from_cr4": [], + "dropped_stall_us_per_mb": { + "0": 36705.7, + "4": 0.0, + "128": 0.0 + }, + "note": "cr in graphed_crs_estimated_from_cr4 were CUDA-graph/stream-captured (compute not visible in trace); their breakdown is copied from cr=4 as an estimate. Re-run those cr with graph capture disabled for exact numbers. dropped_stall_us_per_mb: per-mb layer-compute kernel time dropped as implausible one-off device stalls (> _MAX_PLAUSIBLE_LAUNCH_US)." + }, + "capture": { + "gpu": "MI355X", + "seq_length": 4096, + "micro_batch_size": 1, + "tokens_per_microbatch": 4096, + "ep": 8, + "ga_for_capture": 2, + "optimizer": "adam", + "distributed_optimizer": false, + "recompute": "off", + "measured_iter_time_ms": null, + "measured_anchor": null + }, + "model_config": { + "num_layers": 61, + "hidden_size": 7168, + "num_attention_heads": 128, + "kv_channels": 512, + "num_experts": 384, + "moe_router_topk": 6, + "moe_ffn_hidden_size": 3072, + "moe_shared_expert_intermediate_size": 3072, + "index_topk": 1024, + "vocab_size": 129280, + "mtp_num_layers": 1, + "mtp_compress_ratios": [ + 4 + ], + "pipeline_layout": "", + "compress_ratios": [ + 128, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 4, + 128, + 0 + ], + "cr_layer_counts": { + "0": 1, + "4": 29, + "128": 31 + }, + "total_params": 1597579198464 + }, + "hardware": { + "MI355X": { + "peak_tflops_bf16": 2500.0, + "hbm_bandwidth_gbps": 8000.0 + }, + "MI455X": { + "peak_tflops_bf16": 10000.0, + "hbm_bandwidth_gbps": 19600.0 + } + }, + "layers": { + "0": { + "attention": { + "forward": [ + { + "module": "attn.misc", + "time_us": 7402.198, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::bfloat16_copy_kern", + "time_us": 1437.355 + }, + { + "name": "void at::native::reduce_kernel<512, 1, at::native::ReduceOp", + "time_us": 709.616 + }, + { + "name": "void at::native::unrolled_elementwise_kernel(hip_", + "time_us": 386.653 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 41.18 + } + ] + }, + { + "module": "moe.dispatch", + "time_us": 422.925, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::dispatch<8, 1024, true>(HIP_vector_type(int const*, int*, int ", + "time_us": 20.72 + }, + { + "name": "void primus_turbo::deep_ep::layout::get_dispatch_layout<256, 4, 8>(long const*, ", + "time_us": 19.396 + } + ] + }, + { + "module": "moe.shared_expert", + "time_us": 252.402, + "class": "compute_bound", + "flop_class": "gemm", + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT192x256x64_MI16x16x1_SN_LDSB0_AFC", + "time_us": 141.417 + }, + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x256x64_MI16x16x1_CMS_SN_LDSB0", + "time_us": 71.463 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 8, at::native::gpu_kernel", + "time_us": 21.462 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::(anonymous namespa", + "time_us": 7.096 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::BinaryFunctor", + "time_us": 14.06 + }, + { + "name": "void at::native::_scatter_gather_elementwise_kernel<256, 4, at::native::_cuda_sc", + "time_us": 7.365 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespa", + "time_us": 6.666 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 4, at::native::gpu_kernel", + "time_us": 5.559 + } + ] + } + ], + "backward": [ + { + "module": "moe.grouped_gemm", + "time_us": 5933.968, + "class": "compute_bound", + "flop_class": "grouped_gemm", + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void ck_tile::kentry<1, ck_tile::GroupedGemmKernel(std::bfl", + "time_us": 423.883 + }, + { + "name": "void primus_turbo::compute_grouped_gemm_variable_k_args(hip_", + "time_us": 394.596 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 192.271 + } + ] + }, + { + "module": "moe.dispatch", + "time_us": 449.116, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::dispatch<8, 1024, true>(HIP_vector_type(int const*, int", + "time_us": 73.65 + } + ] + }, + { + "module": "moe.router", + "time_us": 44.656, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "_sinkhorn_bwd_kernel", + "time_us": 44.656 + } + ] + } + ] + } + }, + "4": { + "attention": { + "forward": [ + { + "module": "attn.misc", + "time_us": 7425.221, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::bfloat16_copy_kern", + "time_us": 1447.247 + }, + { + "name": "void at::native::reduce_kernel<512, 1, at::native::ReduceOp", + "time_us": 709.102 + }, + { + "name": "void at::native::unrolled_elementwise_kernel(HIP_vector_type(int const*, int*, int ", + "time_us": 82.547 + }, + { + "name": "void primus_turbo::deep_ep::layout::get_dispatch_layout<256, 4, 8>(long const*, ", + "time_us": 19.383 + } + ] + }, + { + "module": "moe.combine", + "time_us": 470.04, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::combine(hip_", + "time_us": 385.316 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 84.724 + } + ] + }, + { + "module": "moe.shared_expert", + "time_us": 254.469, + "class": "compute_bound", + "flop_class": "gemm", + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT192x256x64_MI16x16x1_SN_LDSB0_AFC", + "time_us": 143.571 + }, + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x256x64_MI16x16x1_CMS_SN_LDSB0", + "time_us": 71.73 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 8, at::native::gpu_kernel", + "time_us": 21.249 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::(anonymous namespa", + "time_us": 6.96 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::BinaryFunctor", + "time_us": 13.973 + }, + { + "name": "void at::native::_scatter_gather_elementwise_kernel<256, 4, at::native::_cuda_sc", + "time_us": 7.535 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespa", + "time_us": 6.562 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 4, at::native::gpu_kernel", + "time_us": 5.852 + } + ] + } + ], + "backward": [ + { + "module": "moe.grouped_gemm", + "time_us": 5908.193, + "class": "compute_bound", + "flop_class": "grouped_gemm", + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void ck_tile::kentry<1, ck_tile::GroupedGemmKernel(std::bfl", + "time_us": 425.279 + }, + { + "name": "void primus_turbo::compute_grouped_gemm_variable_k_args(HIP_vector_type(int const*, int", + "time_us": 204.325 + } + ] + }, + { + "module": "moe.combine", + "time_us": 503.433, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::combine(hip_", + "time_us": 394.109 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 109.324 + } + ] + }, + { + "module": "moe.router", + "time_us": 44.866, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "_sinkhorn_bwd_kernel", + "time_us": 44.866 + } + ] + } + ] + } + }, + "128": { + "attention": { + "forward": [ + { + "module": "attn.misc", + "time_us": 7410.422, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::bfloat16_copy_kern", + "time_us": 1443.783 + }, + { + "name": "void at::native::reduce_kernel<512, 1, at::native::ReduceOp", + "time_us": 708.356 + }, + { + "name": "void at::native::unrolled_elementwise_kernel(hip_", + "time_us": 387.153 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 127.304 + } + ] + }, + { + "module": "moe.dispatch", + "time_us": 448.916, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::dispatch<8, 1024, true>(HIP_vector_type(int const*, int*, int ", + "time_us": 45.283 + }, + { + "name": "void primus_turbo::deep_ep::layout::get_dispatch_layout<256, 4, 8>(long const*, ", + "time_us": 19.383 + } + ] + }, + { + "module": "moe.shared_expert", + "time_us": 254.795, + "class": "compute_bound", + "flop_class": "gemm", + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT192x256x64_MI16x16x1_SN_LDSB0_AFC", + "time_us": 143.427 + }, + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x256x64_MI16x16x1_CMS_SN_LDSB0", + "time_us": 71.993 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 8, at::native::gpu_kernel", + "time_us": 21.433 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::(anonymous namespa", + "time_us": 7.026 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<8, at::native::BinaryFunctor", + "time_us": 13.92 + }, + { + "name": "void at::native::_scatter_gather_elementwise_kernel<256, 4, at::native::_cuda_sc", + "time_us": 7.152 + }, + { + "name": "void at::native::vectorized_elementwise_kernel<4, at::native::(anonymous namespa", + "time_us": 6.402 + }, + { + "name": "void at::native::elementwise_kernel_manual_unroll<128, 4, at::native::gpu_kernel", + "time_us": 5.779 + } + ] + } + ], + "backward": [ + { + "module": "moe.grouped_gemm", + "time_us": 5906.347, + "class": "compute_bound", + "flop_class": "grouped_gemm", + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void ck_tile::kentry<1, ck_tile::GroupedGemmKernel(std::bfl", + "time_us": 423.59 + }, + { + "name": "void primus_turbo::compute_grouped_gemm_variable_k_args(hip_", + "time_us": 394.309 + }, + { + "name": "void primus_turbo::deep_ep::intranode::cached_notify_combine<8>(void**, int*, in", + "time_us": 84.787 + } + ] + }, + { + "module": "moe.dispatch", + "time_us": 408.969, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void primus_turbo::deep_ep::intranode::dispatch<8, 1024, true>(HIP_vector_type(int const*, int", + "time_us": 33.353 + } + ] + }, + { + "module": "moe.router", + "time_us": 45.489, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "_sinkhorn_bwd_kernel", + "time_us": 45.489 + } + ] + } + ] + } + } + }, + "non_layer": { + "embedding": { + "forward": [ + { + "module": "embedding", + "time_us": 14.566, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::vectorized_gather_kernel<16, long>(char*, char*, long*, int, lo", + "time_us": 14.566 + } + ] + } + ], + "backward": [ + { + "module": "embedding", + "time_us": 129.501, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "void at::native::(anonymous namespace)::compute_grad_weight", + "time_us": 60.12 + }, + { + "name": "void at::native::(anonymous namespace)::sum_and_scatter(lon", + "time_us": 38.046 + }, + { + "name": "void rocprim::ROCPRIM_400200_NS::detail::trampoline_kernel(long*, lo", + "time_us": 2.026 + }, + { + "name": "void at::native::(anonymous namespace)::krn_partial_segment_offset(long*, ", + "time_us": 2.023 + } + ] + } + ] + }, + "output": { + "forward": [ + { + "module": "output", + "time_us": 2688.195, + "class": "compute_bound", + "flop_class": "gemm", + "flops": 3799905206272.0, + "tflops": 1413.6, + "kernels": [ + { + "name": "Cijk_Alik_Bljk_BBS_BH_Bias_HA_S_SAV_UserArgs_MT256x256x64_MI16x16x1_CMS_SN_LDSB0", + "time_us": 2634.481 + }, + { + "name": "Cijk_Alik_Bljk_S_B_Bias_HA_S_SAV_UserArgs_MT16x16x512_MI16x16x1_SN_LDSB1_AFC1_AF", + "time_us": 53.713 + } + ] + } + ], + "backward": [] + }, + "loss": { + "forward": [ + { + "module": "loss", + "time_us": 350.465, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "cross_entropy_kernel", + "time_us": 222.888 + }, + { + "name": "online_softmax_kernel", + "time_us": 127.577 + } + ] + } + ], + "backward": [ + { + "module": "loss", + "time_us": 195.848, + "class": "memory_bound", + "flop_class": null, + "flops": null, + "tflops": null, + "kernels": [ + { + "name": "element_mul_kernel", + "time_us": 195.848 + } + ] + } + ] + } + }, + "optimizer": { + "type": "adam", + "measured_params": null, + "time_us": 87037.8, + "bytes_per_param": 30, + "class": "memory_bound", + "note": "Adam mixed-precision step traffic in bytes/param; measured one-layer optimizer-step kernel time is a sanity reference" + }, + "comm": { + "ep_dispatch_us": null, + "ep_combine_us": null, + "note": "EP dispatch/combine are memory_bound rows inside moe; informational" + } +} diff --git a/examples/deepseek-v4/projection/site/index.html b/examples/deepseek-v4/projection/site/index.html new file mode 100644 index 000000000..47c29c364 --- /dev/null +++ b/examples/deepseek-v4/projection/site/index.html @@ -0,0 +1,134 @@ + + + + + + DeepSeek-V4 Performance Projection + + + +
+
+

Primus · DeepSeek-V4

+

Training Performance Projection

+

+ Trace-driven single-layer breakdown on MI355X, scaled to a full-model, + multi-GPU projection. Page 1 is measured-MI355X; page 2 scales to MI455X + by theoretical hardware ratios. +

+
+
+ +
+
+ + + +
+
+ +
+ + +
+ + +
+

Model configuration

+
+
+
+ +
+
+

Per-layer breakdown

+

+ Forward (left→right) then backward (right→left). Time in µs / one + microbatch (seq 4096). The TFLOP/s row is per-kernel achieved + (gemm / grouped_gemm / attn only); the headline TFLOP/s/GPU below uses + the V4 analytic model FLOPs instead. +

+ +
+
+
+ +
+

Projected throughput

+
+
+
+ +
+
+

Iteration timeline

+

+ How one iteration's time is composed, bottom-up: a single layer + (attn / mlp / a2a) → each pipeline rank's chunks (layer granularity) → + the whole 1F1B pipeline schedule. Reacts live to the controls on the left. + See design/07-iteration-timeline.md. +

+
+
+ Layout +
+ + +
+
+
+ + + +
+
+
+
+
+
+
+ +
+

+ Generated from examples/deepseek-v4/projection/. Methodology & + assumptions in design/. Numbers are only as good as the input + trace; see the assumptions list before citing. +

+
+ + + + diff --git a/examples/deepseek-v4/projection/tools/gen_mock_data.py b/examples/deepseek-v4/projection/tools/gen_mock_data.py new file mode 100755 index 000000000..1b223a619 --- /dev/null +++ b/examples/deepseek-v4/projection/tools/gen_mock_data.py @@ -0,0 +1,208 @@ +#!/usr/bin/env python3 +"""Generate MOCK breakdown JSON for site development / demo. + +Numbers are placeholders in the right *order of magnitude*, seeded from the +published P57 single-layer attention micro-bench (V4-Flash widths: B=1, H=64, +Sq=4096, D=512) and the P40 EP=8 MoE/kernel attribution. They are NOT measured +ground truth — replace with `parse_trace.py` output once real traces exist +(every file is marked provenance.mock = true). + +Usage: + python3 examples/deepseek-v4/projection/tools/gen_mock_data.py +""" + +from __future__ import annotations + +import json +from datetime import datetime, timezone +from pathlib import Path + +OUT_DIR = Path(__file__).resolve().parent.parent / "site" / "data" + +PRO_COMPRESS = [128, 128] + [4 if i % 2 == 0 else 128 for i in range(2, 60)] + [0] +FLASH_COMPRESS = [0, 0] + [4 if i % 2 == 0 else 128 for i in range(2, 42)] + [0] + +MODEL_CONFIGS = { + "pro": { + "num_layers": 61, + "hidden_size": 7168, + "num_attention_heads": 128, + "kv_channels": 512, + "num_experts": 384, + "moe_router_topk": 6, + "moe_ffn_hidden_size": 3072, + "moe_shared_expert_intermediate_size": 3072, + "index_topk": 1024, + "vocab_size": 129280, + "compress_ratios": PRO_COMPRESS, + }, + "flash": { + "num_layers": 43, + "hidden_size": 4096, + "num_attention_heads": 64, + "kv_channels": 512, + "num_experts": 256, + "moe_router_topk": 6, + "moe_ffn_hidden_size": 2048, + "moe_shared_expert_intermediate_size": 2048, + "index_topk": 512, + "vocab_size": 129280, + "compress_ratios": FLASH_COMPRESS, + }, +} + +# MI355X: BF16 matrix 2.5 PFLOPS, HBM3E 8 TB/s (AMD product page). +# MI455X (MI400): HBM4 19.6 TB/s; BF16 dense not officially published — +# estimated ~10 PFLOPS (half of the 20 PFLOPS FP8 spec). +HARDWARE = { + "MI355X": {"peak_tflops_bf16": 2500.0, "hbm_bandwidth_gbps": 8000.0}, + "MI455X": {"peak_tflops_bf16": 10000.0, "hbm_bandwidth_gbps": 19600.0}, +} + + +def row(module, time_us, flop_class=None, tflops=None): + flops = (tflops * time_us * 1e6) if (flop_class and tflops) else None + return { + "module": module, + "time_us": round(time_us, 1), + "class": "compute_bound" if flop_class else "memory_bound", + "flop_class": flop_class, + "flops": flops, + "tflops": tflops, + "kernels": [], + } + + +# Base (flash) attention core fwd/bwd by cr, from P57 micro-bench (ms -> us). +ATTN_CORE = { + "0": {"fwd": 500.0, "bwd": 2080.0}, + "4": {"fwd": 1430.0, "bwd": 5110.0}, + "128": {"fwd": 570.0, "bwd": 2810.0}, +} + + +def attention_breakdown(cr, s): + """s = linear scale factor vs flash widths.""" + fwd = [ + row("attn.qkv_proj", 220 * s, "gemm", 480), + row("attn.core", ATTN_CORE[cr]["fwd"] * s, "attn", 210), + row("attn.rope", 35 * s), + row("attn.o_proj", 160 * s, "gemm", 470), + row("attn.norm", 25 * s), + ] + bwd = [ + row("attn.qkv_proj", 440 * s, "gemm", 480), + row("attn.core", ATTN_CORE[cr]["bwd"] * s, "attn", 180), + row("attn.rope", 45 * s), + row("attn.o_proj", 320 * s, "gemm", 470), + row("attn.norm", 30 * s), + ] + if cr == "4": # CSA uses the Indexer/Compressor + fwd.insert(2, row("attn.indexer", 300 * s, "gemm", 300)) + bwd.insert(2, row("attn.indexer", 600 * s, "gemm", 300)) + return {"forward": fwd, "backward": bwd} + + +def moe_breakdown(sg, sc): + """sg = grouped-gemm scale, sc = comm/act scale vs flash.""" + fwd = [ + row("moe.router", 55 * sc), + row("moe.dispatch", 820 * sc), + row("moe.grouped_gemm", 1850 * sg, "grouped_gemm", 430), + row("moe.act", 110 * sc), + row("moe.shared_expert", 210 * sg, "gemm", 460), + row("moe.combine", 990 * sc), + ] + bwd = [ + row("moe.router", 75 * sc), + row("moe.dispatch", 900 * sc), + row("moe.grouped_gemm", 3700 * sg, "grouped_gemm", 430), + row("moe.act", 150 * sc), + row("moe.shared_expert", 420 * sg, "gemm", 460), + row("moe.combine", 1050 * sc), + ] + return {"forward": fwd, "backward": bwd} + + +def non_layer(s): + return { + "embedding": {"forward": [row("embedding", 120 * s)], "backward": [row("embedding", 60 * s)]}, + "output": { + "forward": [row("output", 900 * s, "gemm", 300)], + "backward": [row("output", 1800 * s, "gemm", 300)], + }, + "loss": {"forward": [row("loss", 80)], "backward": [row("loss", 60)]}, + } + + +def cr_counts(compress): + c = {"0": 0, "4": 0, "128": 0} + for x in compress: + c[str(x)] += 1 + return c + + +def build(model): + cfg = MODEL_CONFIGS[model] + # scale factors vs flash baseline + if model == "pro": + s_attn, s_grouped, s_comm, s_out = 1.75, 2.0, 1.3, 1.75 + opt_params, opt_time = 2_300_000_000, 1600.0 + else: + s_attn, s_grouped, s_comm, s_out = 1.0, 1.0, 1.0, 1.0 + opt_params, opt_time = 1_000_000_000, 850.0 + + layers = { + cr: {"attention": attention_breakdown(cr, s_attn), "moe": moe_breakdown(s_grouped, s_comm)} + for cr in ("0", "4", "128") + } + return { + "schema_version": 1, + "model": model, + "generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "provenance": { + "mock": True, + "note": "MOCK data (P57/P40 order-of-magnitude); replace with parse_trace.py output", + }, + "capture": { + "gpu": "MI355X", + "seq_length": 4096, + "micro_batch_size": 1, + "tokens_per_microbatch": 4096, + "ep": 8, + "ga_for_capture": 2, + "optimizer": "adam", + "distributed_optimizer": True, + "recompute": "off", + "measured_iter_time_ms": None, + }, + "model_config": {**cfg, "cr_layer_counts": cr_counts(cfg["compress_ratios"])}, + "hardware": HARDWARE, + "layers": layers, + "non_layer": non_layer(s_out), + "optimizer": { + "type": "adam", + "measured_params": opt_params, + "time_us": opt_time, + "bytes_per_param": 18, + "class": "memory_bound", + }, + "comm": { + "ep_dispatch_us": None, + "ep_combine_us": None, + "note": "EP dispatch/combine included in moe rows; informational", + }, + } + + +def main(): + OUT_DIR.mkdir(parents=True, exist_ok=True) + for model in ("pro", "flash"): + doc = build(model) + path = OUT_DIR / f"{model}.json" + path.write_text(json.dumps(doc, indent=2)) + print(f"[gen_mock_data] wrote {path}") + + +if __name__ == "__main__": + main() diff --git a/examples/deepseek-v4/projection/tools/kernel_module_map.py b/examples/deepseek-v4/projection/tools/kernel_module_map.py new file mode 100644 index 000000000..fd6ddbcea --- /dev/null +++ b/examples/deepseek-v4/projection/tools/kernel_module_map.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 +"""Kernel / nn.module -> logical-module + flop-class mapping for the V4 +projection breakdown. + +Two independent classifications are provided: + +1. ``module_from_stack(stack)`` — primary: derive the logical module from the + python call stack captured by ``with_stack=True`` (matches nn.module class + names appearing in the stack frames). This is the accurate path (A13). + +2. ``module_from_kernel(name)`` — fallback: derive the logical module purely from + the GPU kernel name when no usable stack is attached. + +``flop_class_from_kernel(name)`` returns the compute-bound FLOP class +(``gemm`` / ``grouped_gemm`` / ``attn``) or ``None`` (memory-bound, A14). + +The rules are intentionally data-driven so they can be extended as kernels are +renamed. Order matters: the first matching rule wins. +""" + +from __future__ import annotations + +# --- logical module taxonomy (kept small per the design) -------------------- +# attention sub-modules: +# attn.qkv_proj, attn.rope, attn.indexer, attn.core, attn.o_proj, attn.norm +# moe sub-modules: +# moe.router, moe.dispatch, moe.grouped_gemm, moe.act, moe.combine, +# moe.shared_expert +# non-layer: embedding, output, loss + +# (substring, logical_module) — matched against the python call stack text. +# nn.module class names / function names seen in V4 forward stacks. +STACK_MODULE_RULES: list[tuple[str, str]] = [ + ("Indexer", "attn.indexer"), + ("Compressor", "attn.indexer"), + ("apply_rotary", "attn.rope"), + ("rope", "attn.rope"), + ("linear_qkv", "attn.qkv_proj"), + ("q_layernorm", "attn.norm"), + ("k_layernorm", "attn.norm"), + ("linear_proj", "attn.o_proj"), + ("o_proj", "attn.o_proj"), + ("core_attention", "attn.core"), + ("DeepseekV4Attention", "attn.core"), + ("MLASelfAttention", "attn.core"), + ("SelfAttention", "attn.core"), + ("input_layernorm", "attn.norm"), + ("pre_mlp_layernorm", "moe.router"), + ("TopKRouter", "moe.router"), + ("router", "moe.router"), + ("sinkhorn", "moe.router"), + ("shared_expert", "moe.shared_expert"), + ("token_dispatch", "moe.dispatch"), + ("dispatch", "moe.dispatch"), + ("combine", "moe.combine"), + ("GroupedMLP", "moe.grouped_gemm"), + ("SequentialMLP", "moe.grouped_gemm"), + ("grouped", "moe.grouped_gemm"), + ("experts", "moe.grouped_gemm"), + ("activation", "moe.act"), + ("swiglu", "moe.act"), + ("MoELayer", "moe.grouped_gemm"), + ("word_embeddings", "embedding"), + ("embedding", "embedding"), + ("output_layer", "output"), + ("lm_head", "output"), + ("loss", "loss"), + ("cross_entropy", "loss"), +] + +# (substring, logical_module) — matched against the GPU kernel name (fallback). +KERNEL_MODULE_RULES: list[tuple[str, str]] = [ + ("_v4_csa_attention", "attn.core"), + ("_v4_attention", "attn.core"), + ("_hc_compute", "attn.core"), + ("hc_compute", "attn.core"), + ("_indexer", "attn.indexer"), + ("indexer", "attn.indexer"), + ("_compressor", "attn.indexer"), + ("compressor", "attn.indexer"), + ("apply_rope", "attn.rope"), + ("rotary", "attn.rope"), + ("rope", "attn.rope"), + ("_sinkhorn", "moe.router"), + ("sinkhorn", "moe.router"), + ("_v4_router", "moe.router"), + ("deep_ep::", "moe.dispatch"), # refined to dispatch/combine below + ("dispatch", "moe.dispatch"), + ("combine", "moe.combine"), + ("GroupedGemm", "moe.grouped_gemm"), + ("_stack_grouped_weight", "moe.grouped_gemm"), + ("group_gemm", "moe.grouped_gemm"), + ("swiglu", "moe.act"), + ("embedding", "embedding"), + ("cross_entropy", "loss"), + ("nll_loss", "loss"), +] + +# (substring, flop_class) — matched against the GPU kernel name. +# Order matters: grouped GEMM must be checked before generic GEMM. +FLOP_CLASS_RULES: list[tuple[str, str]] = [ + ("GroupedGemmKernel", "grouped_gemm"), + ("grouped_gemm", "grouped_gemm"), + ("group_gemm", "grouped_gemm"), + ("_v4_csa_attention", "attn"), + ("_v4_attention", "attn"), + ("attention_fwd", "attn"), + ("attention_bwd", "attn"), + # generic dense GEMM kernels (hipBLASLt / rocBLAS / CK tile / Triton matmul) + ("GemmKernel", "gemm"), + ("Cijk_", "gemm"), + ("gemm", "gemm"), + ("matmul", "gemm"), +] + + +def module_from_stack(stack: str | None) -> str | None: + """Return the logical module from a python call-stack string, or None.""" + if not stack: + return None + for needle, module in STACK_MODULE_RULES: + if needle in stack: + return module + return None + + +def module_from_kernel(name: str) -> str: + """Return the logical module from a kernel name (fallback).""" + lowered = name + for needle, module in KERNEL_MODULE_RULES: + if needle in lowered: + if module == "moe.dispatch" and "combine" in lowered: + return "moe.combine" + return module + return "other" + + +def flop_class_from_kernel(name: str) -> str | None: + """Return 'gemm' | 'grouped_gemm' | 'attn', or None for memory-bound.""" + for needle, klass in FLOP_CLASS_RULES: + if needle in name: + return klass + return None + + +def is_compute_bound(name: str) -> bool: + return flop_class_from_kernel(name) is not None diff --git a/examples/deepseek-v4/projection/tools/parse_trace.py b/examples/deepseek-v4/projection/tools/parse_trace.py new file mode 100755 index 000000000..dad6291f7 --- /dev/null +++ b/examples/deepseek-v4/projection/tools/parse_trace.py @@ -0,0 +1,651 @@ +#!/usr/bin/env python3 +"""Turn per-cr chrome traces into a projection breakdown JSON. + +Input: one rank-0 PyTorch/Kineto chrome trace per compression-ratio (cr), each +captured by ``script/deepseek_v4_layer_trace-projection.sh`` (1 layer, seq 4096, +GA=2, recompute off, overlap off, profiler window iter 6->7). + +Output: a single ``.json`` matching ``design/03-json-schema.md``. + +Attribution (validated against the real ROCm/Kineto trace): + * GPU kernels (cat=="kernel") link to their launching CPU op via the shared + ``External id`` arg. Optimizer (``multi_tensor_apply``) and DP-comm + (``nccl``) kernels carry no External id and are classified by name. + * The module comes from the enclosing ``nn.Module: _n`` python_function + events (with_stack) on the CPU op's thread; fwd/bwd from "Backward"/ + "autograd" in the CPU op name or an enclosing frame. + * Clean per-call time = ``min`` over launches grouped by + ``(phase, module, kernel, input-dims)`` (overlap is off, so this just + removes warm-up/jitter); calls-per-microbatch is treated as 1 for a single + captured layer. + * Compute-bound FLOP class from the kernel name; GEMM FLOPs from input dims. +""" + +from __future__ import annotations + +import argparse +import bisect +import json +from collections import defaultdict +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + +# Merge gap (us) used when reconstructing the backward GPU-time window from the +# linked backward kernels, so an unlinked kernel sitting in a small stall +# between two backward kernels is still counted as backward. +_BWD_WINDOW_GAP_US = 150.0 + +# A single layer-compute GPU kernel launch at seq=4096/B=1 realistically tops +# out around ~10 ms (e.g. the V4 attention bwd or a grouped GEMM). Some captures +# bill a one-off device-side stall / busy-wait to an ordinary elementwise kernel +# (observed: 3 launches of ~145 ms attributed to aten::mul/add_/div in the cr=0 +# pro trace, 17x the cr=4/128 value). Those are capture artifacts, not per-layer +# cost, so layer-compute launches above this cap are dropped (and reported in +# provenance). Optimizer/comm kernels are routed out before this cap applies. +_MAX_PLAUSIBLE_LAUNCH_US = 50_000.0 + +from kernel_module_map import flop_class_from_kernel, module_from_kernel +from v4_flops import FB_FMA, layer_fmac, model_total_params, mtp_flops, nonlayer_fmac + +PRO_COMPRESS = [128, 128] + [4 if i % 2 == 0 else 128 for i in range(2, 60)] + [0] +FLASH_COMPRESS = [0, 0] + [4 if i % 2 == 0 else 128 for i in range(2, 42)] + [0] + +MODEL_CONFIGS: dict[str, dict[str, Any]] = { + "pro": { + "num_layers": 61, + "hidden_size": 7168, + "num_attention_heads": 128, + "kv_channels": 512, + "num_experts": 384, + "moe_router_topk": 6, + "moe_ffn_hidden_size": 3072, + "moe_shared_expert_intermediate_size": 3072, + "index_topk": 1024, + "vocab_size": 129280, + "mtp_num_layers": 1, + "mtp_compress_ratios": [4], + "pipeline_layout": "", + "compress_ratios": PRO_COMPRESS, + }, + "flash": { + "num_layers": 43, + "hidden_size": 4096, + "num_attention_heads": 64, + "kv_channels": 512, + "num_experts": 256, + "moe_router_topk": 6, + "moe_ffn_hidden_size": 2048, + "moe_shared_expert_intermediate_size": 2048, + "index_topk": 512, + "vocab_size": 129280, + "mtp_num_layers": 1, + "mtp_compress_ratios": [4], + "pipeline_layout": "Et*10|t*11|t*11|t*11mL", + "compress_ratios": FLASH_COMPRESS, + }, +} + +# MI355X: BF16 matrix 2.5 PFLOPS, HBM3E 8 TB/s (AMD product page). MI455X +# (MI400): HBM4 19.6 TB/s; BF16 dense not officially published — estimated +# ~10 PFLOPS (half of the 20 PFLOPS FP8 spec). The site can override these. +DEFAULT_HARDWARE = { + "MI355X": {"peak_tflops_bf16": 2500.0, "hbm_bandwidth_gbps": 8000.0}, + "MI455X": {"peak_tflops_bf16": 10000.0, "hbm_bandwidth_gbps": 19600.0}, +} + +# Measured multi-node anchors used to set/validate the site's calibFactor. The +# projection, configured to the anchor's parallel layout, must reproduce these +# (see design/06-calibration.md). flash: real 8-node MI355X run (image pr-768, +# commit 2c9b). pro: production multi-node anchor still TODO (needs >=8 idle +# nodes); until then pro reuses flash's cross-model calibFactor. +MODEL_ANCHORS: dict[str, dict[str, Any]] = { + "flash": { + "measured_iter_time_ms": 4076.0, + "measured_anchor": { + "config": "PP8/VPP1/EP8/TP1, world 64, MBS1/GBS128, seq4096, full recompute, " + "layout Et*4|t*5|(t*6|)*5,t*4mL (8-node MI355X)", + "iter_ms": 4076.0, + "tflops_gpu": 722, + "tok_s_gpu": 2010, + "calib_factor": 0.87, + "tolerance": "<0.1%", + }, + }, +} + +ALL_MODULES = ( + "attn.proj", + "attn.core", + "attn.indexer", + "attn.norm", + "moe.router", + "moe.dispatch", + "moe.grouped_gemm", + "moe.shared_expert", + "moe.combine", + "embedding", + "output", + "loss", + "other", +) + + +def _arg(ev: dict, *keys: str) -> Any: + args = ev.get("args") or {} + for k in keys: + if k in args: + return args[k] + return None + + +def _dims_key(dims: Any) -> str: + if dims is None: + return "" + try: + return json.dumps(dims, separators=(",", ":")) + except TypeError: + return str(dims) + + +def _merge_intervals(intervals: list[tuple[float, float]], gap: float = 0.0) -> list[tuple[float, float]]: + """Merge [start, end] intervals; bridge neighbours separated by <= gap.""" + out: list[tuple[float, float]] = [] + for s, e in sorted(intervals): + if out and s <= out[-1][1] + gap: + out[-1] = (out[-1][0], max(out[-1][1], e)) + else: + out.append((s, e)) + return out + + +def _make_membership(merged: list[tuple[float, float]]): + """Return a fn(ts)->bool: is ts inside one of the merged intervals.""" + starts = [s for s, _ in merged] + + def _in(ts: float | None) -> bool: + if ts is None or not merged: + return False + i = bisect.bisect_right(starts, ts) - 1 + return i >= 0 and merged[i][0] <= ts <= merged[i][1] + + return _in + + +def _is_opt_or_comm_kernel(name: str) -> bool: + low = name.lower() + return "multi_tensor_apply" in name or "fusedadam" in low or "adamw" in low or "nccl" in low + + +def _gemm_flops(dims_key: str) -> float | None: + if not dims_key: + return None + try: + dims = json.loads(dims_key) + except json.JSONDecodeError: + return None + shapes = [d for d in dims if isinstance(d, list) and len(d) >= 2 and all(isinstance(x, int) for x in d)] + if len(shapes) >= 2: + a, b = shapes[0], shapes[1] + m, k = a[-2], a[-1] + k2, n = b[-2], b[-1] + if k == k2: + batch = 1 + for x in a[:-2]: + batch *= x + return 2.0 * batch * m * n * k + return None + + +def _resolve_module(kname: str, cpu_name: str, anc_classes: set[str], flop_class: str | None) -> str: + """Logical module for a kernel given its name, CPU op name, and enclosing + nn.Module classes. Returns '__optimizer__' / '__dpcomm__' for non-layer + buckets handled separately. Priority: kernel name > cpu-op name > enclosing + nn.Module (forward only; backward ops aren't inside module forward ranges).""" + n = kname + low = n.lower() + cn = cpu_name or "" + + def has(*xs: str) -> bool: + return any(any(x in a for a in anc_classes) for x in xs) + + def coarse(module: str) -> str: + if module in ("attn.qkv_proj", "attn.o_proj"): + return "attn.proj" + if module in ("attn.rope",): + return "attn.norm" + if module == "moe.act": + return "moe.grouped_gemm" + return module + + # 1) kernel-name rules (most reliable; present for fwd and bwd) + if "multi_tensor_apply" in n or "fusedadam" in low or "adamw" in low: + return "__optimizer__" + if "nccl" in low: + return "__dpcomm__" + if "cross_entropy" in low or "online_softmax" in low: + return "loss" + if "deep_ep" in n or "deepep" in low: + return "moe.combine" if "combine" in low else "moe.dispatch" + if "_v4_csa" in n or "_v4_attention" in n or "_hc_" in n: + return "attn.core" + if "_sinkhorn" in n or "_v4_router" in n: + return "moe.router" + if ( + "GroupedGemm" in n + or "_grouped" in n + or "group_gemm" in low + or "grouped_gemm" in low + or "grouped_variable" in n + ): + return "moe.grouped_gemm" + + # 2) cpu-op (autograd Function / aten) name rules — needed for backward + if "Attention" in cn or "CSAPool" in cn or "MLA" in cn: + return "attn.core" + if "Indexer" in cn or "Compressor" in cn: + return "attn.indexer" + if "Sinkhorn" in cn or "Router" in cn: + return "moe.router" + if "RMSNorm" in cn or "LayerNorm" in cn or "layer_norm" in cn.lower(): + return "attn.norm" + if "crossentropy" in cn.lower() or "cross_entropy" in cn.lower() or "nll_loss" in cn.lower(): + return "loss" + if "embedding" in cn.lower(): + return "output" if flop_class == "gemm" else "embedding" + if "LinearWithGradAccumulation" in cn or cn in ("aten::mm", "aten::addmm", "aten::matmul", "aten::bmm"): + if has("Embedding") or ( + has("DeepseekV4Model") + and not has( + "DeepseekV4Attention", "DeepseekV4HybridLayer", "Compressor", "Indexer", "MLP", "Expert" + ) + ): + return "output" + if has("Compressor", "Indexer"): + return "attn.indexer" + if has("MLP", "Expert"): + return "moe.grouped_gemm" + return "attn.proj" + + # 3) enclosing nn.Module (forward only) + if has("Compressor", "Indexer"): + return "attn.indexer" + if has("SharedExpert"): + return "moe.shared_expert" + if has("GroupedMLP", "SequentialMLP", "GroupedExperts", "Experts"): + return "moe.grouped_gemm" + if has("Router"): + return "moe.router" + if has("DeepseekV4Attention", "MLASelfAttention", "SelfAttention"): + return "attn.proj" if flop_class == "gemm" else "attn.norm" + if has("Embedding"): + return "output" if flop_class == "gemm" else "embedding" + fallback = coarse(module_from_kernel(kname)) + return fallback if fallback != "other" else "attn.misc" + + +def parse_trace(path: Path, ga: int = 2): + payload = json.loads(path.read_text()) + events = payload.get("traceEvents", []) + + # index cpu ops by External id + cpu_by_extid: dict[Any, dict] = {} + # interesting python_function intervals per tid: (ts, end, name) + pf_by_tid: dict[Any, list[tuple[float, float, str]]] = defaultdict(list) + # `autograd::engine::evaluate_function` CPU intervals precisely bracket the + # backward pass; a kernel whose launching CPU op falls inside one is bwd. + eval_intervals: list[tuple[float, float]] = [] + kernels: list[dict] = [] + num_steps = 0 # real captured training steps (ProfilerStep events, dur>10ms) + + for ev in events: + cat = (ev.get("cat") or "").lower() + ph = ev.get("ph") + if ph == "X" and ev.get("name", "").startswith("ProfilerStep") and (ev.get("dur") or 0) > 10000: + num_steps += 1 + if cat == "cpu_op" and ph == "X": + ext = _arg(ev, "External id") + if ext is not None and ext not in cpu_by_extid: + cpu_by_extid[ext] = ev + if ev.get("name", "").startswith("autograd::engine::evaluate_function"): + ets = ev.get("ts") + if ets is not None: + eval_intervals.append((ets, ets + (ev.get("dur") or 0))) + elif cat == "python_function" and ph == "X": + name = ev.get("name", "") + if name.startswith("nn.Module:") or "ackward" in name or "autograd" in name: + ts = ev.get("ts") + dur = ev.get("dur") or 0 + if ts is not None: + pf_by_tid[ev.get("tid")].append((ts, ts + dur, name)) + elif cat == "kernel" and ph == "X" and ev.get("dur") is not None: + kernels.append(ev) + + for tid in pf_by_tid: + pf_by_tid[tid].sort(key=lambda t: t[0]) + + def enclosing(cpu: dict) -> set[str]: + """Return enclosing nn.Module class names for a cpu op (forward only; + backward ops live under the autograd engine, not module forward ranges).""" + tid = cpu.get("tid") + ts = cpu.get("ts") + end = ts + (cpu.get("dur") or 0) + classes: set[str] = set() + for pts, pend, name in pf_by_tid.get(tid, ()): + if pts > ts: + break + if pend >= end and name.startswith("nn.Module:"): + classes.add(name.split(":", 1)[1].strip().rsplit("_", 1)[0]) + return classes + + # Per-microbatch time = (sum of all launches over the whole profiler window) + # / num_mb, where num_mb = num_steps * GA. We keep the SUM (not a min over + # launches): the single-layer capture serializes each layer's work, and the + # measured flash-16L anchor (design/06) shows this serial per-layer time is + # the right estimate (calibFactor ~0.93 against 6665 ms). A min-over-launches + # rule would assume the scalar control-flow stalls (e.g. the Indexer top-k + # device syncs) are fully hidden in the full model; the anchor refutes that + # (it would need calibFactor ~1.3), so they are kept as real per-layer cost. + # + # Subgroups are keyed by (kernel, input-dims) only so we can (a) classify a + # row as compute_bound iff FLOP-classed kernels dominate its time (>50%) -- + # a stray flop-classed kernel can no longer flip a memory-bound aggregate -- + # and (b) sum dim-derived GEMM FLOPs correctly. + num_mb = max(1, num_steps * ga) + + # ---- phase classification (forward vs backward) ---------------------- + # The backward pass is identified by the autograd engine: a kernel is + # backward iff its launching CPU op was issued inside an + # `autograd::engine::evaluate_function` interval (these precisely bracket + # the backward). A `_fwd_`/`_bwd_` tag in the kernel name (V4 Triton kernels + # encode it) wins. Unlinked kernels (no External id -> no CPU op; common for + # fused/elementwise launches Kineto fails to flow-link) are assigned by + # whether their GPU timestamp lands inside the backward GPU-time window + # rebuilt from the linked backward kernels. This replaces the old "default + # to forward" rule, which systematically leaked backward compute -- incl. + # the MoE dgrad/wgrad grouped GEMMs -- into the forward breakdown. + bwd_eval = _merge_intervals(eval_intervals) + in_bwd_eval = _make_membership(bwd_eval) + + phase_by_idx: list[str | None] = [None] * len(kernels) + _bwd_windows: list[tuple[float, float]] = [] + for i, ev in enumerate(kernels): + name = ev.get("name", "") + ln = name.lower() + if "_fwd" in ln and "_bwd" not in ln: + phase_by_idx[i] = "forward" + continue + if "_bwd" in ln: + phase_by_idx[i] = "backward" + if not _is_opt_or_comm_kernel(name) and ev["dur"] <= _MAX_PLAUSIBLE_LAUNCH_US: + _bwd_windows.append((ev["ts"], ev["ts"] + ev["dur"])) + continue + ext = _arg(ev, "External id") + cpu = cpu_by_extid.get(ext) if ext is not None else None + if cpu is not None: + ph = "backward" if in_bwd_eval(cpu.get("ts")) else "forward" + phase_by_idx[i] = ph + if ( + ph == "backward" + and not _is_opt_or_comm_kernel(name) + and ev["dur"] <= _MAX_PLAUSIBLE_LAUNCH_US + ): + _bwd_windows.append((ev["ts"], ev["ts"] + ev["dur"])) + # else: unlinked -> decided below from the GPU-side backward window + + in_bwd_gpu = _make_membership(_merge_intervals(_bwd_windows, _BWD_WINDOW_GAP_US)) + for i, ev in enumerate(kernels): + if phase_by_idx[i] is None: + phase_by_idx[i] = "backward" if in_bwd_gpu(ev.get("ts")) else "forward" + + # (phase, module) -> { (kernel_name, dims_key): {"durs": [...], "flop_class", "flop_per_launch"} } + groups: dict[tuple[str, str], dict] = defaultdict(dict) + optimizer_us = 0.0 + dpcomm_us = 0.0 + dropped_stall_us = 0.0 + + for i, ev in enumerate(kernels): + name = ev.get("name", "") + dur = float(ev["dur"]) + ext = _arg(ev, "External id") + cpu = cpu_by_extid.get(ext) if ext is not None else None + cpu_name = cpu.get("name", "") if cpu else "" + anc = enclosing(cpu) if cpu else set() + flop_class = flop_class_from_kernel(name) + module = _resolve_module(name, cpu_name, anc, flop_class) + if module == "__optimizer__": + optimizer_us += dur + continue + if module == "__dpcomm__": + dpcomm_us += dur + continue + # Drop one-off device-side stalls billed to a layer-compute kernel (see + # _MAX_PLAUSIBLE_LAUNCH_US); they are capture artifacts, not per-layer cost. + if dur > _MAX_PLAUSIBLE_LAUNCH_US: + dropped_stall_us += dur + continue + phase = phase_by_idx[i] + dims_key = _dims_key(_arg(cpu, "Input Dims")) if cpu else "" + flop_per_launch = _gemm_flops(dims_key) if (cpu and flop_class == "gemm") else None + sub = groups[(phase, module)].setdefault( + (name, dims_key), + {"durs": [], "flop_class": flop_class, "flop_per_launch": flop_per_launch}, + ) + sub["durs"].append(dur) + + optimizer_us /= max(1, num_steps) # optimizer runs once per training iteration + dpcomm_us /= num_mb + dropped_stall_us /= num_mb # report per-microbatch, like the breakdown rows + + out = {b: {"forward": {}, "backward": {}} for b in ("attention", "moe", "embedding", "output", "loss")} + for (phase, module), subs in groups.items(): + time_us = 0.0 + compute_us = 0.0 + flops = 0.0 + has_flops = False + kern: dict[str, float] = defaultdict(float) + class_time: dict[str, float] = defaultdict(float) + for (name, _dims), sub in subs.items(): + n = len(sub["durs"]) + per_mb = sum(sub["durs"]) / num_mb + time_us += per_mb + kern[name[:80]] += per_mb + if sub["flop_class"]: + compute_us += per_mb + class_time[sub["flop_class"]] += per_mb + if sub["flop_per_launch"]: + flops += sub["flop_per_launch"] * n / num_mb + has_flops = True + compute = compute_us > 0.5 * time_us if time_us > 0 else False + flop_class = max(class_time, key=class_time.get) if (compute and class_time) else None + flops_out = flops if (compute and has_flops) else None + time_s = time_us * 1e-6 + tflops = (flops_out / time_s / 1e12) if (flops_out and time_s > 0) else None + kern_list = sorted( + ({"name": n, "time_us": round(t, 3)} for n, t in kern.items()), + key=lambda k: -k["time_us"], + )[:6] + entry = { + "module": module, + "time_us": round(time_us, 3), + "class": "compute_bound" if compute else "memory_bound", + "flop_class": flop_class, + "flops": flops_out, + "tflops": round(tflops, 1) if tflops else None, + "kernels": kern_list, + } + # Logical bucket. Unattributed scalar kernels are labelled `attn.misc` + # by _resolve_module because the V4 traces show they are dominated by + # attention-side hyper-connection / Indexer control-flow work; this keeps + # the MoE bucket strictly the cr-independent router/dispatch/grouped_gemm + # /combine/shared_expert set (A10). + if module.startswith("attn."): + bucket = "attention" + elif module.startswith("moe."): + bucket = "moe" + elif module in ("embedding", "output", "loss"): + bucket = module + else: + bucket = "moe" + out[bucket][phase][module] = entry + return out, optimizer_us, dpcomm_us, dropped_stall_us + + +def _lists(bd: dict) -> dict: + return {p: sorted(bd[p].values(), key=lambda r: -r["time_us"]) for p in ("forward", "backward")} + + +def cr_layer_counts(compress: list[int]) -> dict[str, int]: + counts = {"0": 0, "4": 0, "128": 0} + for c in compress: + counts[str(c)] = counts.get(str(c), 0) + 1 + return counts + + +def _fwd_total(buckets: dict) -> float: + return sum(r["time_us"] for r in buckets["attention"]["forward"].values()) + sum( + r["time_us"] for r in buckets["moe"]["forward"].values() + ) + + +def build(model: str, traces: dict[str, Path], ga: int = 2) -> dict[str, Any]: + cfg = MODEL_CONFIGS[model] + per_cr, opt_us = {}, [] + dropped_stall = {} + for cr, path in traces.items(): + buckets, o, _dp, stall = parse_trace(path, ga) + per_cr[cr] = buckets + opt_us.append(o) + dropped_stall[cr] = round(stall, 1) + + # Some cr layers (pure dense cr=0 / HCA cr=128) get CUDA-graph / stream- + # captured, so their compute kernels are not individually visible in the + # trace (only optimizer/comm/elementwise appear). Fall back to the eager + # cr=4 sample for those so the full-model projection isn't zeroed; flag it. + ref = ( + "4" + if "4" in per_cr and _fwd_total(per_cr["4"]) >= 1000 + else max(per_cr, key=lambda c: _fwd_total(per_cr[c])) + ) + graphed = [] + for cr in list(per_cr): + if cr != ref and _fwd_total(per_cr[cr]) < 1000: + graphed.append(cr) + per_cr[cr] = per_cr[ref] + + src = per_cr[ref] + layers = {cr: {"attention": _lists(b["attention"]), "moe": _lists(b["moe"])} for cr, b in per_cr.items()} + optimizer_us = round(sum(opt_us) / len(opt_us), 1) if opt_us else None + + # Analytic V4 closed-form FLOPs (ported from Megatron's flops patch) so the + # site's TFLOP/s matches a real run. Per layer, per microbatch (B=1), at the + # capture seq; Megatron-convention (fwd+bwd, FMA -> x6). + cap_seq = 4096 + mtp_num_layers = int(cfg.get("mtp_num_layers", 0) or 0) + mtp_cr = int((cfg.get("mtp_compress_ratios") or [0])[0]) + mtp = mtp_flops(model, cap_seq, mtp_num_layers, mtp_cr) + analytic_flops = { + "per_cr_layer_flops": { + cr: sum(layer_fmac(model, int(cr), cap_seq).values()) * FB_FMA for cr in ("0", "4", "128") + }, + "output_flops": nonlayer_fmac(model, cap_seq)["logits"] * FB_FMA, + "mtp": { + "num_layers": mtp_num_layers, + "compress_ratio": mtp_cr, + "inner_layer_flops": mtp["inner_layer"], + "eh_proj_flops": mtp["eh_proj"], + "extra_logits_flops": mtp["extra_logits"], + "hc_head_flops": mtp["hc_head"], + }, + "seq": cap_seq, + "note": "per-layer and MTP (B=1) Megatron-convention FLOPs at capture seq; site multiplies by GA x DP", + } + + return { + "schema_version": 1, + "model": model, + "analytic_flops": analytic_flops, + "generated_at": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "provenance": { + "traces": {cr: str(p) for cr, p in traces.items()}, + "graphed_crs_estimated_from_cr4": graphed, + "dropped_stall_us_per_mb": dropped_stall, + "note": ( + "cr in graphed_crs_estimated_from_cr4 were CUDA-graph/stream-captured " + "(compute not visible in trace); their breakdown is copied from cr=4 as an " + "estimate. Re-run those cr with graph capture disabled for exact numbers. " + "dropped_stall_us_per_mb: per-mb layer-compute kernel time dropped as " + "implausible one-off device stalls (> _MAX_PLAUSIBLE_LAUNCH_US)." + ), + }, + "capture": { + "gpu": "MI355X", + "seq_length": 4096, + "micro_batch_size": 1, + "tokens_per_microbatch": 4096, + "ep": 8, + "ga_for_capture": 2, + "optimizer": "adam", + "distributed_optimizer": False, + "recompute": "off", + "measured_iter_time_ms": MODEL_ANCHORS.get(model, {}).get("measured_iter_time_ms"), + "measured_anchor": MODEL_ANCHORS.get(model, {}).get("measured_anchor"), + }, + "model_config": { + **cfg, + "cr_layer_counts": cr_layer_counts(cfg["compress_ratios"]), + "total_params": model_total_params(model, cfg["num_layers"], mtp_num_layers), + }, + "hardware": DEFAULT_HARDWARE, + "layers": layers, + "non_layer": {k: _lists(src[k]) for k in ("embedding", "output", "loss")}, + "optimizer": { + "type": "adam", + "measured_params": None, + "time_us": optimizer_us, + "bytes_per_param": 30, + "class": "memory_bound", + "note": "Adam mixed-precision step traffic in bytes/param; measured one-layer optimizer-step kernel time is a sanity reference", + }, + "comm": { + "ep_dispatch_us": None, + "ep_combine_us": None, + "note": "EP dispatch/combine are memory_bound rows inside moe; informational", + }, + } + + +def parse_args() -> argparse.Namespace: + p = argparse.ArgumentParser(description="Build projection breakdown JSON from per-cr traces.") + p.add_argument("--model", required=True, choices=sorted(MODEL_CONFIGS)) + p.add_argument("--trace", action="append", default=[], metavar="cr=PATH") + p.add_argument( + "--ga", type=int, default=2, help="gradient-accumulation at capture (GBS/(DP*MBS)); default 2" + ) + p.add_argument("--out", required=True, type=Path) + return p.parse_args() + + +def main() -> int: + args = parse_args() + traces: dict[str, Path] = {} + for spec in args.trace: + if "=" not in spec: + raise SystemExit(f"--trace must be cr=PATH, got: {spec}") + cr, path = spec.split("=", 1) + traces[cr.replace("cr", "")] = Path(path) + if not traces: + raise SystemExit("at least one --trace cr=PATH is required") + for cr, path in traces.items(): + if not path.exists(): + raise SystemExit(f"trace not found for cr={cr}: {path}") + + doc = build(args.model, traces, args.ga) + args.out.parent.mkdir(parents=True, exist_ok=True) + args.out.write_text(json.dumps(doc, indent=2) + "\n") + print(f"[parse_trace] wrote {args.out} (model={args.model}, crs={sorted(traces)})") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/examples/deepseek-v4/projection/tools/v4_flops.py b/examples/deepseek-v4/projection/tools/v4_flops.py new file mode 100644 index 000000000..34329866e --- /dev/null +++ b/examples/deepseek-v4/projection/tools/v4_flops.py @@ -0,0 +1,250 @@ +#!/usr/bin/env python3 +"""DeepSeek-V4 closed-form analytic FLOPs, ported from +``primus/backends/megatron/patches/deepseek_v4_flops_patches.py`` so the +projection's TFLOP/s matches what Megatron reports on a real run. + +Returns per-component FMAC (multiply-only, pre fwd+bwd/FMA expansion) for ONE +layer of a given cr at batch_size=1. Multiply by FB_FMA (=6) for Megatron- +convention FLOPs (fwd 1 + bwd 2, times FMA 2). + +Validated against the measured flash 16-layer run (TOTAL 34093 TFLOP/global- +batch; per-component within rounding) — see __main__ self-test. +""" + +from __future__ import annotations + +FB_FMA = 6 # _FORWARD_BACKWARD_FACTOR(3) * _FMA_FACTOR(2) +SWIGLU = 3 # gate+up+down collapsed expansion factor +DEFAULT_MTP_LAYERS = {"pro": 1, "flash": 1} + +# Per-model architecture params (from primus/configs/models/megatron/*.yaml + +# deepseek_v4_base.yaml). Shared: index_head_dim=128, index_n_heads=64, +# attn_sliding_window=128, hc_mult=4. +MODEL_PARAMS = { + "pro": dict( + hidden=7168, + heads=128, + head_dim=512, + q_lora=1536, + o_lora=1024, + o_groups=16, + moe_ffn=3072, + shared_ffn=3072, + topk=6, + experts=384, + index_topk=1024, + vocab=129280, + ), + "flash": dict( + hidden=4096, + heads=64, + head_dim=512, + q_lora=1024, + o_lora=1024, + o_groups=8, + moe_ffn=2048, + shared_ffn=2048, + topk=6, + experts=256, + index_topk=512, + vocab=129280, + ), +} +SHARED = dict(index_head_dim=128, index_n_heads=64, swa_window=128, hc_mult=4) + + +def _local_visible_pairs(swa, s): + if swa <= 0 or swa >= s: + return s * (s + 1) // 2 + return swa * s - swa * (swa - 1) // 2 + + +def _pool_visible_pairs(cr, s): + if cr <= 0 or s <= 0: + return 0 + n = s // cr + if n == 0: + return 0 + return cr * n * (n - 1) // 2 + n * (s - cr * n + 1) + + +def _visible_pairs(swa, cr, index_topk, s): + local = _local_visible_pairs(swa, s) + if cr == 0: + return local + pool = max(1, s // cr) + if cr == 128: + return local + _pool_visible_pairs(cr, s) + if cr == 4: + sparse = min(index_topk if index_topk else pool, pool) + return local + sparse * s + return local + pool * s + + +def _attn_qkv_o(s_eff, p): + n_d = p["heads"] * p["head_dim"] + qkv = p["hidden"] * p["q_lora"] + p["q_lora"] * n_d + p["hidden"] * p["head_dim"] + if p["o_lora"] > 0: + o_proj = n_d * p["o_lora"] + (p["o_groups"] * p["o_lora"]) * p["hidden"] + else: + o_proj = n_d * p["hidden"] + return s_eff * (qkv + o_proj) + + +def _attn_scores(s_eff, cr, p): + pairs = _visible_pairs(SHARED["swa_window"], cr, p["index_topk"], s_eff) + return 2 * p["heads"] * p["head_dim"] * pairs + + +def _compressor(s_eff, cr, p): + if cr == 0: + return 0 + coff = 2 if cr == 4 else 1 + return 2 * s_eff * p["hidden"] * (coff * p["head_dim"]) + + +def _indexer(s_eff, cr, p): + if cr != 4: + return 0 + ihd, inh = SHARED["index_head_dim"], SHARED["index_n_heads"] + pool = max(1, s_eff // cr) + dq_rank = ihd + proj = p["hidden"] * dq_rank + dq_rank * (inh * ihd) + p["hidden"] * inh + proj += 2 * p["hidden"] * (2 * ihd) # mini-compressor + return s_eff * proj + s_eff * inh * pool * ihd + + +def _moe(s_eff, p): + router = p["hidden"] * p["experts"] + routed = p["topk"] * SWIGLU * p["hidden"] * p["moe_ffn"] + shared = SWIGLU * p["hidden"] * p["shared_ffn"] if p["shared_ffn"] > 0 else 0 + return s_eff * (router + routed + shared) + + +def _hc_mixer(s, p): + hc = SHARED["hc_mult"] + n_d = hc * p["hidden"] + return 2 * s * n_d * ((2 + hc) * hc) + + +def _hc_head(s, p, mtp_num_layers): + hc = SHARED["hc_mult"] + n_d = hc * p["hidden"] + return (1 + mtp_num_layers) * s * n_d * hc + + +def _mtp_eh_proj(s, p, mtp_num_layers): + return mtp_num_layers * s * (2 * p["hidden"]) * p["hidden"] + + +def layer_fmac(model: str, cr: int, seq: int) -> dict[str, float]: + """Per-layer FMAC components (batch_size=1) for one cr layer.""" + p = MODEL_PARAMS[model] + s_eff = seq * SHARED["hc_mult"] + return { + "attn_qkv_o": _attn_qkv_o(s_eff, p), + "attn_scores": _attn_scores(s_eff, cr, p), + "compressor": _compressor(s_eff, cr, p), + "indexer": _indexer(s_eff, cr, p), + "moe": _moe(s_eff, p), + "hc": _hc_mixer(seq, p), + } + + +def nonlayer_fmac(model: str, seq: int, mtp_num_layers: int = 0) -> dict[str, float]: + p = MODEL_PARAMS[model] + return {"logits": (1 + mtp_num_layers) * seq * p["hidden"] * p["vocab"]} + + +def mtp_fmac(model: str, seq: int, mtp_num_layers: int = 1, mtp_cr: int = 4) -> dict[str, float]: + """Extra FMAC components for MTP depths, batch_size=1. + + V4 MTP reuses a full V4 inner layer per depth; the current Flash Megatron + FLOPs anchor reports a CSA-style MTP inner layer (cr=4). + """ + if mtp_num_layers <= 0: + return { + "inner_layer": 0, + "eh_proj": 0, + "extra_logits": 0, + "hc_head": _hc_head(seq, MODEL_PARAMS[model], 0), + } + p = MODEL_PARAMS[model] + inner = sum(layer_fmac(model, mtp_cr, seq).values()) * mtp_num_layers + main_logits = seq * p["hidden"] * p["vocab"] + return { + "inner_layer": inner, + "eh_proj": _mtp_eh_proj(seq, p, mtp_num_layers), + "extra_logits": main_logits * mtp_num_layers, + "hc_head": _hc_head(seq, p, mtp_num_layers), + } + + +def model_total_params(model: str, num_layers: int, mtp_num_layers: int = 0) -> int: + """Approximate total parameter count (for optimizer-step sizing). Uses the + same V4 MLA low-rank attention shapes as the FLOPs formula (q/o LoRA + single + latent KV) instead of the crude 4*h^2, plus MoE experts + shared + router and + the tied-free embedding/output. MTP adds one full V4 inner layer plus the + 2H->H eh_proj per depth; logits reuse the output layer weights.""" + p = MODEL_PARAMS[model] + n_d = p["heads"] * p["head_dim"] + attn = ( + p["hidden"] * p["q_lora"] + + p["q_lora"] * n_d + + p["hidden"] * p["head_dim"] + + n_d * p["o_lora"] + + p["o_groups"] * p["o_lora"] * p["hidden"] + ) + moe = ( + p["experts"] * SWIGLU * p["hidden"] * p["moe_ffn"] + + SWIGLU * p["hidden"] * p["shared_ffn"] + + p["hidden"] * p["experts"] + ) + return int( + (num_layers + mtp_num_layers) * (attn + moe) + + mtp_num_layers * 2 * p["hidden"] * p["hidden"] + + 2 * p["vocab"] * p["hidden"] + ) + + +# Map analytic components to projection module names (per layer). +def module_flops(model: str, cr: int, seq: int) -> dict[str, float]: + """Megatron-convention FLOPs (×FB_FMA) per module, per layer, batch_size=1.""" + f = layer_fmac(model, cr, seq) + return { + "attn.proj": f["attn_qkv_o"] * FB_FMA, + "attn.core": f["attn_scores"] * FB_FMA, + "attn.indexer": (f["compressor"] + f["indexer"]) * FB_FMA, + "attn.norm": f["hc"] * FB_FMA, + "moe.grouped_gemm": f["moe"] * FB_FMA, + } + + +def output_flops(model: str, seq: int) -> float: + return nonlayer_fmac(model, seq)["logits"] * FB_FMA + + +def mtp_flops(model: str, seq: int, mtp_num_layers: int = 1, mtp_cr: int = 4) -> dict[str, float]: + f = mtp_fmac(model, seq, mtp_num_layers, mtp_cr) + return {k: v * FB_FMA for k, v in f.items()} + + +def _self_test() -> None: + """Self-test against measured flash 16L (GBS64): cr [0x3,4x6,128x7].""" + sched = [0, 0, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 0] + seq_t, batch = 4096, 64 + comp = {k: 0.0 for k in ("attn_qkv_o", "attn_scores", "compressor", "indexer", "moe", "hc")} + for cr_t in sched: + for k, v in layer_fmac("flash", cr_t, seq_t).items(): + comp[k] += v + logits = nonlayer_fmac("flash", seq_t)["logits"] + tot = (sum(comp.values()) + logits) * FB_FMA * batch / 1e12 + print("flash 16L analytic vs measured (TFLOP/global-batch):") + for k, v in comp.items(): + print(f" {k:12s} = {v*FB_FMA*batch/1e12:9.1f}") + print(f" {'logits':12s} = {logits*FB_FMA*batch/1e12:9.1f}") + print(f" TOTAL = {tot:9.1f} (measured 34093.4)") + + +if __name__ == "__main__": + _self_test() diff --git a/examples/deepseek-v4/rccl_avg_workaround/.gitignore b/examples/deepseek-v4/rccl_avg_workaround/.gitignore new file mode 100644 index 000000000..7a60b85e1 --- /dev/null +++ b/examples/deepseek-v4/rccl_avg_workaround/.gitignore @@ -0,0 +1,2 @@ +__pycache__/ +*.pyc diff --git a/examples/deepseek-v4/rccl_avg_workaround/sitecustomize.py b/examples/deepseek-v4/rccl_avg_workaround/sitecustomize.py new file mode 100644 index 000000000..bf10ac87e --- /dev/null +++ b/examples/deepseek-v4/rccl_avg_workaround/sitecustomize.py @@ -0,0 +1,143 @@ +"""gfx1250 single-GPU bring-up workarounds, auto-imported in every Python +worker via sitecustomize (this dir is on PYTHONPATH). Two independent fixes: + +1. RCCL AVG hang: torch.distributed.all_reduce(op=AVG) HANGS on this build for + (at least) single-rank process groups, while SUM works fine (verified by + collective microbench). Megatron's MoE aux-loss metric reduction + (moe_utils.reduce_aux_losses_tracker_across_ranks) uses op=AVG and + deadlocks. Replace AVG with SUM + divide-by-world-size, which is + mathematically identical for any world size. + +2. primus_turbo import shim: the MI355X production containers bundle the + `primus_turbo` package; the gfx1250 therock container does not. Most Primus + call-sites guard the import (try/except -> HAVE_TURBO=False), but the V4 + model path imports it unconditionally: + deepseek_v4_layer_specs.py -> transformer_engine_spec_provider.py + (DeepSeekV4SpecProvider subclasses PrimusTurboSpecProvider) + -> extensions/primus_turbo.py -> `import primus_turbo.pytorch` + and backends/megatron/core/utils.py -> primus_turbo...attention_utils. + With every use_turbo_* flag False the turbo classes are never SELECTED + (the spec provider returns the TE classes), so a pure import-shim is safe: + install a meta-path finder that fabricates stub modules for primus_turbo.* + whose attributes are auto-generated dummy classes. Attribute chains + evaluated at class-definition time (e.g. the ScalingGranularity.TENSORWISE + default arg in PrimusTurboQuantConfig) resolve fine; actually CALLING or + instantiating any stub raises RuntimeError, so a misrouted turbo path fails + loudly instead of computing garbage. The shim only installs when the real + package is absent, so it can never shadow a real primus_turbo install. +""" + +import sys + + +def _install_rccl_avg_workaround(): + import torch.distributed as dist + + orig_all_reduce = dist.all_reduce + avg_op = dist.ReduceOp.AVG + + def all_reduce_avg_safe(tensor, op=dist.ReduceOp.SUM, group=None, async_op=False): + is_avg = False + try: + is_avg = op == avg_op + except Exception: + is_avg = str(op) == str(avg_op) + if is_avg: + work = orig_all_reduce(tensor, op=dist.ReduceOp.SUM, group=group, async_op=async_op) + try: + ws = dist.get_world_size(group) + except Exception: + ws = 1 + if ws and ws > 1: + # Enqueued on the same stream after the all_reduce, so ordering holds. + tensor.div_(ws) + return work + return orig_all_reduce(tensor, op=op, group=group, async_op=async_op) + + dist.all_reduce = all_reduce_avg_safe + print( + "[rccl_avg_workaround] patched torch.distributed.all_reduce (AVG -> SUM/ws)", + file=sys.stderr, + flush=True, + ) + + +def _install_primus_turbo_stub(): + import importlib.abc + import importlib.machinery + import importlib.util + import types + + # Never shadow a real install. + if importlib.util.find_spec("primus_turbo") is not None: + return + + class _StubMeta(type): + """Dummy-class metaclass: any attribute access mints another dummy + class (covers enum-style chains like ScalingGranularity.TENSORWISE).""" + + def __getattr__(cls, name): + if name.startswith("__"): + raise AttributeError(name) + dummy = _make_dummy(f"{cls._stub_qual}.{name}") + setattr(cls, name, dummy) + return dummy + + def _make_dummy(qual): + def _raise(self, *args, **kwargs): + raise RuntimeError( + f"primus_turbo stub: {qual} was invoked, but primus_turbo is NOT " + "installed in this container. A turbo code path ran despite all " + "use_turbo_*/enable_primus_turbo flags being False — fix the flags " + "instead of installing primus_turbo." + ) + + return _StubMeta(qual.rsplit(".", 1)[-1], (), {"__init__": _raise, "_stub_qual": qual}) + + class _StubModule(types.ModuleType): + def __getattr__(self, name): + if name.startswith("__"): + raise AttributeError(name) + dummy = _make_dummy(f"{self.__name__}.{name}") + setattr(self, name, dummy) + return dummy + + class _Finder(importlib.abc.MetaPathFinder, importlib.abc.Loader): + def find_spec(self, fullname, path=None, target=None): + if fullname == "primus_turbo" or fullname.startswith("primus_turbo."): + return importlib.machinery.ModuleSpec(fullname, self, is_package=True) + return None + + def create_module(self, spec): + return _StubModule(spec.name) + + def exec_module(self, module): + module.__path__ = [] + module._primus_turbo_stub = True + + sys.meta_path.append(_Finder()) + print( + "[primus_turbo_stub] primus_turbo not installed -> import shim active " + "(turbo classes import as raising stubs; all use_turbo_* must stay False)", + file=sys.stderr, + flush=True, + ) + + +# Only patch the actual training worker. Importing torch here is heavy, so we +# must NOT do it for pip/offload-arch/build helper invocations (they stall and +# block setup). Gate on the training entrypoint appearing in argv. +def _is_training_worker(): + argv = " ".join(sys.argv) + return ("primus/cli/main.py" in argv) or ("run_pretrain" in argv) or ("pretrain" in argv) + + +if _is_training_worker(): + try: + _install_rccl_avg_workaround() + except Exception as e: # noqa: BLE001 + print(f"[rccl_avg_workaround] install FAILED: {e}", file=sys.stderr, flush=True) + try: + _install_primus_turbo_stub() + except Exception as e: # noqa: BLE001 + print(f"[primus_turbo_stub] install FAILED: {e}", file=sys.stderr, flush=True) diff --git a/examples/deepseek-v4/run_deepseek_v4.sh b/examples/deepseek-v4/run_deepseek_v4.sh new file mode 100755 index 000000000..0cd3797ec --- /dev/null +++ b/examples/deepseek-v4/run_deepseek_v4.sh @@ -0,0 +1,393 @@ +#!/bin/bash +set -euo pipefail +set -x + +_RUN_START_SEC=$(date +%s) +_RUN_START_TS=$(date '+%Y-%m-%d %H:%M:%S') +_print_run_elapsed() { + local _end_sec _end_ts _elapsed _exit=$1 + _end_sec=$(date +%s) + _end_ts=$(date '+%Y-%m-%d %H:%M:%S') + _elapsed=$((_end_sec - _RUN_START_SEC)) + echo "----------------------------------------" + echo "run_deepseek_v4.sh wall time" + echo " start: ${_RUN_START_TS}" + echo " end: ${_end_ts}" + echo " elapsed: ${_elapsed}s ($((_elapsed / 60))m $((_elapsed % 60))s)" + echo " exit: ${_exit}" +} +trap '_print_run_elapsed $?' EXIT + +export HF_TOKEN="${HF_TOKEN:-}" +export WANDB_API_KEY="${WANDB_API_KEY:-your_wandb_api_key}" + +export NNODES=${NNODES:-1} +export TRAIN_ITERS=${TRAIN_ITERS:-20} + +export DOCKER_IMAGE=${DOCKER_IMAGE:?set DOCKER_IMAGE to a Primus container image} +export SLURM_PARTITION=${SLURM_PARTITION:-} +export SLURM_NODELIST=${SLURM_NODELIST:-} +export MASTER_PORT=${MASTER_PORT:-29500} + +export USING_AINIC=${USING_AINIC:-1} +export NCCL_IB_HCA="ionic_0:1,ionic_1:1,ionic_2:1,ionic_3:1,ionic_4:1,ionic_5:1,ionic_6:1,ionic_7:1" +# Default socket interface to loopback (single-node fallback). Override with +# GLOO_SOCKET_IFNAME / NCCL_SOCKET_IFNAME for multi-node (e.g. ens3). +export GLOO_SOCKET_IFNAME=${GLOO_SOCKET_IFNAME:-lo} +export NCCL_SOCKET_IFNAME=${NCCL_SOCKET_IFNAME:-lo} +export NCCL_IB_GID_INDEX=1 +export HSA_NO_SCRATCH_RECLAIM=${HSA_NO_SCRATCH_RECLAIM:-1} +export NVTE_CK_USES_BWD_V3=${NVTE_CK_USES_BWD_V3:-1} + +# Phase-7 fixed knobs for single-node bring-up. +export MBS=${MBS:-1} +export GBS=${GBS:-$((16 * NNODES * MBS))} +export PRIMUS_TP=${PRIMUS_TP:-1} +export PRIMUS_PP=${PRIMUS_PP:-1} +export PRIMUS_EP=${PRIMUS_EP:-8} + +# Keep this smoke config lightweight for quick bring-up. +export PRIMUS_TOTAL_LAYERS=${PRIMUS_TOTAL_LAYERS:-8} +export PRIMUS_SEQ_LENGTH=${PRIMUS_SEQ_LENGTH:-128} +export PRIMUS_MAX_POSITION_EMBEDDINGS=${PRIMUS_MAX_POSITION_EMBEDDINGS:-128} +export PRIMUS_NUM_EXPERTS=${PRIMUS_NUM_EXPERTS:-8} +export PRIMUS_MOE_TOPK=${PRIMUS_MOE_TOPK:-2} +export PRIMUS_MOE_FFN_HIDDEN_SIZE=${PRIMUS_MOE_FFN_HIDDEN_SIZE:-512} +export PRIMUS_INDEX_TOPK=${PRIMUS_INDEX_TOPK:-8} +export PRIMUS_COMPRESS_RATIOS=${PRIMUS_COMPRESS_RATIOS:-"[0,0,4,4,4,4,4,0]"} +export PRIMUS_MOE_ENABLE_EXPERT_BIAS=${PRIMUS_MOE_ENABLE_EXPERT_BIAS:-False} +export PRIMUS_V4_GROUPED_EXPERTS_SUPPORT_CLAMPED_SWIGLU=${PRIMUS_V4_GROUPED_EXPERTS_SUPPORT_CLAMPED_SWIGLU:-True} +export PROFILE=${PROFILE:-False} +export USE_TURBO_ATTENTION=${USE_TURBO_ATTENTION:-False} +export TURBO_USE_GROUPED_MLP=${TURBO_USE_GROUPED_MLP:-False} +export LEGACY_GG=${LEGACY_GG:-False} +# MegaMoE: FlyDSL-based fused MoE layer replacing Megatron's MoELayer (see +# docs/04-technical-guides/mega-moe.md). It owns the whole expert path -- +# dispatch/combine all-to-all is fused into the grouped GEMMs -- so it is +# mutually exclusive with turbo DeepEP; force DeepEP off rather than letting +# both patch the MoE layer. Requires EP-only (TP=1) + bf16 + EP>1. +export USE_TURBO_MEGA_MOE=${USE_TURBO_MEGA_MOE:-False} +if [ "$USE_TURBO_MEGA_MOE" = "True" ]; then + export USE_TURBO_DEEPEP=False +fi +# Plan-3 P22 / P23: PrimusTurbo gate (must be on for turbo attention / +# turbo deepep to take effect; enable_primus_turbo gates the +# `before_train` patches that re-bind the spec provider). +export ENABLE_PRIMUS_TURBO=${ENABLE_PRIMUS_TURBO:-False} +if [ "$USE_TURBO_ATTENTION" = "True" ] || [ "${USE_TURBO_DEEPEP:-False}" = "True" ] || + [ "$USE_TURBO_MEGA_MOE" = "True" ]; then + ENABLE_PRIMUS_TURBO=True +fi +export USE_TURBO_DEEPEP=${USE_TURBO_DEEPEP:-False} + +if [ "$TURBO_USE_GROUPED_MLP" = "True" ]; then + export PRIMUS_BIAS_SWIGLU_FUSION=True +fi + +# Plan-3 P23: Turbo DeepEP-related knobs. Only emit these CLI flags +# when USE_TURBO_DEEPEP=True so non-deepep runs don't carry unrelated +# overrides. Best-practice CU count: 64 (or 80) for EP=8, 32 for +# EP>=16 — the EP>=16 cap is asserted by +# `primus/modules/trainer/megatron/utils.py:527`. DeepEP itself +# requires `moe_router_dtype=fp32` and forbids +# `moe_shared_expert_overlap=True` (both are already V4-Flash YAML +# defaults; we pin them via CLI defensively so a stray YAML override +# or future config edit cannot flip them out from under the Turbo +# path mid-run). +TURBO_DEEPEP_CLI_ARGS=() +if [ "$USE_TURBO_DEEPEP" = "True" ]; then + if [ "${PRIMUS_EP:-1}" -ge 16 ]; then + _DEFAULT_TURBO_DEEPEP_NUM_CU=32 + else + _DEFAULT_TURBO_DEEPEP_NUM_CU=80 + fi + export TURBO_DEEPEP_NUM_CU=${TURBO_DEEPEP_NUM_CU:-$_DEFAULT_TURBO_DEEPEP_NUM_CU} + export TURBO_DEEPEP_USE_COMM_STREAM=${TURBO_DEEPEP_USE_COMM_STREAM:-False} + export MOE_ROUTER_DTYPE=${MOE_ROUTER_DTYPE:-fp32} + export MOE_SHARED_EXPERT_OVERLAP=${MOE_SHARED_EXPERT_OVERLAP:-False} + TURBO_DEEPEP_CLI_ARGS=( + --turbo_deepep_num_cu "$TURBO_DEEPEP_NUM_CU" + --turbo_deepep_use_comm_stream "$TURBO_DEEPEP_USE_COMM_STREAM" + --moe_router_dtype "$MOE_ROUTER_DTYPE" + --moe_shared_expert_overlap "$MOE_SHARED_EXPERT_OVERLAP" + ) +fi + +# TransformerEngine full-scope CUDA graph capture. `--external_cuda_graph True` +# maps to cuda_graph_impl="transformer_engine"; scope `full` is normalized to [] +# by Megatron, i.e. capture the whole layer. Pairs well with MegaMoE, which is +# sync-free (no device-to-host sync in the expert path) and captures cleanly. +export ENABLE_CUDA_GRAPH=${ENABLE_CUDA_GRAPH:-False} +CUDA_GRAPH_CLI_ARGS=() +if [ "$ENABLE_CUDA_GRAPH" = "True" ]; then + CUDA_GRAPH_CLI_ARGS=( + --external_cuda_graph True + --cuda_graph_scope "${CUDA_GRAPH_SCOPE:-full}" + --cuda_graph_warmup_steps "${CUDA_GRAPH_WARMUP_STEPS:-3}" + ) +fi + +export PRECISION_TYPE=${PRECISION_TYPE:-BF16} +# Honor an incoming FP8 / FP8_RECIPE env (e.g. FP8_RECIPE=mxfp8); default null +# so non-FP8 runs are unchanged. (Previously these were hard-set to null, +# which silently clobbered a caller-provided recipe.) +export FP8=${FP8:-null} +export FP8_RECIPE=${FP8_RECIPE:-null} + +# ---------- Optimizer selection (adam default; muon = DeepSeek-V4 recipe) ---- +# OPTIMIZER=adam (default): unchanged behaviour (BF16 precision-aware AdamW +# from the EXP yaml); overlap_grad_reduce / overlap_param_gather stay ON. +# OPTIMIZER=muon: Primus distributed-Muon path (primus .../optimizer/moun.py). +# Megatron asserts plain `muon` is incompatible with distributed optimizer + +# grad/param overlap, so we force them OFF and switch optimizer states to +# fp32 (Muon does not support the precision-aware optimizer). The +# Newton-Schulz coefficient set auto-selects 'deepseekv4' (8 aggressive + 2 +# stable) for V4 configs inside get_megatron_muon_optimizer. Requires the +# emerging_optimizers package -> we set PRIMUS_INSTALL_EMERGING_OPTIMIZERS so +# the in-container install hook (runner/.../01_install_emerging_optimizers.sh) +# provisions it. +export OPTIMIZER=${OPTIMIZER:-adam} +export PRIMUS_OVERLAP_GRAD_REDUCE=${PRIMUS_OVERLAP_GRAD_REDUCE:-True} +export PRIMUS_OVERLAP_PARAM_GATHER=${PRIMUS_OVERLAP_PARAM_GATHER:-True} +OPTIMIZER_CLI_ARGS=() +if [ "$OPTIMIZER" = "muon" ] || [ "$OPTIMIZER" = "dist_muon" ]; then + export PRIMUS_INSTALL_EMERGING_OPTIMIZERS=${PRIMUS_INSTALL_EMERGING_OPTIMIZERS:-1} + export MUON_MOMENTUM=${MUON_MOMENTUM:-0.95} + export MUON_EXTRA_SCALE_FACTOR=${MUON_EXTRA_SCALE_FACTOR:-0.18} + # Both plain muon (Megatron asserts) and dist_muon (LayerWiseDistributed- + # Optimizer docstring: "keep all megatron distributed-optimizer related + # options OFF"; it manages its own param all-gather, so DDP + # overlap_param_gather double-drives start_param_sync -> crash) need the + # DDP grad/param overlap OFF. + PRIMUS_OVERLAP_GRAD_REDUCE=False + PRIMUS_OVERLAP_PARAM_GATHER=False + OPTIMIZER_CLI_ARGS=( + --optimizer "$OPTIMIZER" + --muon_momentum "$MUON_MOMENTUM" + --muon_extra_scale_factor "$MUON_EXTRA_SCALE_FACTOR" + --use_distributed_optimizer False + --use_precision_aware_optimizer False + --main_grads_dtype fp32 + --exp_avg_dtype fp32 + --exp_avg_sq_dtype fp32 + ) +fi + +# DeepSeek-V4 attention backend selection (unified string selectors). Default +# triton_v2 (production default; fastest V4 sparse-MLA path). These are +# V4-only; no effect on other model types. +# USE_V4_ATTENTION_BACKEND (dense cr=0 / HCA cr=128): eager|triton_v1|triton_v2|gluon +# USE_V4_CSA_ATTENTION_BACKEND (CSA cr=4): eager|triton_v0|triton_v1|triton_v2|gluon|flydsl_v0 +# gluon is gfx950/CDNA4-only (lazily imported; asserts arch when selected). +# use_turbo_attention (when core_attention is built) still wins for the dense path. +export USE_V4_ATTENTION_BACKEND=${USE_V4_ATTENTION_BACKEND:-turbo} +export USE_V4_CSA_ATTENTION_BACKEND=${USE_V4_CSA_ATTENTION_BACKEND:-turbo} + +# Plan-9: FP8 (E4M3) Indexer QK path (CSA selector). Default OFF; flip with +# USE_V4_FP8_INDEXER=True. Passed as a CLI override so it reliably reaches the +# in-container config regardless of env propagation. +export USE_V4_FP8_INDEXER=${USE_V4_FP8_INDEXER:-False} + +# Indexer distillation loss coefficient (CSA selector training). 0 keeps the +# loss off and the indexer frozen -- correct when loading an already-trained +# indexer. A from-scratch pretrain needs it ON (1e-2 is a reasonable starting +# value), which also unfreezes the indexer params. +export PRIMUS_V4_INDEXER_DISTILL_LOSS_COEFF=${PRIMUS_V4_INDEXER_DISTILL_LOSS_COEFF:-0.0} + +# Plan-5 P29 (RESCOPED): wrap sinkhorn_normalize in HyperMixer with a +# cached torch.compile build. Default OFF here; the proxy script +# (run_deepseek_v4_flash_proxy.sh) flips it ON. After G32 + G33b are +# green, the default flips to True for the V4-Flash configs. +export USE_V4_COMPILED_SINKHORN=${USE_V4_COMPILED_SINKHORN:-False} + +# Plan-4 P27: TP-side guard for the V4 Triton kernels. +# The dense / HCA / CSA kernels operate on the local head slice (each +# rank only sees H/TP query heads) so TP-sharded execution is correct +# by construction (no in-kernel collective comm needed). Plan-4 unit +# tests / smoke gates exercise TP=1 only; emit a soft warning when a +# user enables the kernels at TP>1 so any TP-related regression is +# easy to attribute. TP=1 is the V4-Flash / V4-Pro release default +# (release configs use PP+EP for parallelism, never TP). +if echo "$USE_V4_ATTENTION_BACKEND $USE_V4_CSA_ATTENTION_BACKEND" | grep -q "triton" && [ "${PRIMUS_TP:-1}" -gt 1 ]; then + echo "[WARN] Plan-4 V4 Triton kernels enabled at PRIMUS_TP=${PRIMUS_TP}>1; this combination is not covered by Plan-4 unit tests / smoke gates (G28..G30 ran TP=1 only). Functionally the kernels operate per-rank on the local H/TP head slice, so this should work, but treat any TP>1 regression as a Plan-4 follow-up." +fi + +if [ "$PRECISION_TYPE" = "FP8" ]; then + # Default to the paper's ue8m0 microscaling (e4m3 + mxfp8); honor explicit + # FP8 / FP8_RECIPE overrides. Sentinel-aware because "null" is non-empty, so + # a plain ${FP8:-...} would keep the off-sentinel instead of defaulting. + [ "$FP8" = "null" ] && export FP8=e4m3 + [ "$FP8_RECIPE" = "null" ] && export FP8_RECIPE=mxfp8 +fi + +# ---------- MXFP8 + FP8 param-gather (Muon path; Megatron #4987 analogue) ---- +# Plan-9: combine the distributed-Muon (LayerWise) path with an MXFP8 forward +# recipe + FP8 parameter all-gather. Enable with FP8_PARAM_GATHER=True (best +# paired with OPTIMIZER=dist_muon + PRECISION_TYPE=FP8 FP8_RECIPE=mxfp8). +# MXFP8 on ROCm/TE requires NVTE_ROCM_ENABLE_MXFP8=1; the mxfp8 param-AG path +# is most memory-efficient with --reuse-grad-buf-for-mxfp8-param-ag. NOTE: +# Megatron auto-disables --fp8-param-gather on TE>=2.0.0 (falls back to a +# bf16/all_gather), so on such containers this exercises the MXFP8 forward + +# dist-Muon path with param-gather requested-but-possibly-downgraded. +export FP8_PARAM_GATHER=${FP8_PARAM_GATHER:-False} +FP8_PARAM_GATHER_CLI_ARGS=() +if [ "$FP8_PARAM_GATHER" = "True" ]; then + export NVTE_ROCM_ENABLE_MXFP8=${NVTE_ROCM_ENABLE_MXFP8:-1} + export REUSE_GRAD_BUF_FOR_MXFP8_PARAM_AG=${REUSE_GRAD_BUF_FOR_MXFP8_PARAM_AG:-True} + FP8_PARAM_GATHER_CLI_ARGS=(--fp8_param_gather True) + if [ "$REUSE_GRAD_BUF_FOR_MXFP8_PARAM_AG" = "True" ] && [ "$FP8_RECIPE" = "mxfp8" ]; then + FP8_PARAM_GATHER_CLI_ARGS+=(--reuse_grad_buf_for_mxfp8_param_ag True) + fi +fi + +# Force load balancing discards the real router decision so every expert receives +# a similar number of tokens, removing run-to-run imbalance noise. This selects +# *how*: `even` (Primus default) gives exactly equal, step-invariant per-expert +# counts, so grouped-GEMM shapes never change; `uniform` balances only +# statistically and keeps the step-to-step shape variation of real routing. +# docs/04-technical-guides/mega-moe.md recommends `uniform` for benchmarking, +# because `even` disproportionately favours the non-fused grouped-GEMM path. +# Empty (default) leaves the config value alone. +export MOE_FORCE_LB_TYPE=${MOE_FORCE_LB_TYPE:-} +MOE_FORCE_LB_ARGS=() +if [ -n "$MOE_FORCE_LB_TYPE" ]; then + MOE_FORCE_LB_ARGS=(--moe_router_force_load_balancing_type "$MOE_FORCE_LB_TYPE") +fi + +PP_LAYOUT_ARGS=() +if [ -n "${PRIMUS_PP_LAYOUT:-}" ]; then + PP_LAYOUT_ARGS=(--pipeline_model_parallel_layout "$PRIMUS_PP_LAYOUT") +fi + +PRIMUS_RECOMPUTE_LAYERS=${PRIMUS_RECOMPUTE_LAYERS:-0} + +export EXP=${EXP:-examples/megatron/configs/MI355X/deepseek_v4_flash-BF16-pretrain.yaml} +export BACKEND_PATH=${BACKEND_PATH:-"$(pwd)/third_party/Megatron-LM"} +export PRIMUS_TEAM=${PRIMUS_TEAM:-amd} +export PRIMUS_USER=${PRIMUS_USER:-tas-mi355x-$(date +%Y%m%d)} +export PRIMUS_EXP_NAME=${PRIMUS_EXP_NAME:-deepseek_v4_smoke_${PRECISION_TYPE}_MBS${MBS}_GBS${GBS}_PP${PRIMUS_PP}_EP${PRIMUS_EP}} +# Host-side directory for the launcher's aggregated log. Defaults to the +# canonical "output" tree; override when that tree is not writable by the +# invoking user (e.g. it was created by an earlier root/sudo run). +export PRIMUS_OUTPUT_ROOT=${PRIMUS_OUTPUT_ROOT:-output} + +if [ ! -d "$BACKEND_PATH" ] || [ -z "$(ls -A "$BACKEND_PATH" 2>/dev/null)" ]; then + echo "[ERROR] BACKEND_PATH does not exist or is empty: $BACKEND_PATH" + echo "Run: git submodule update --init --recursive" + exit 1 +fi + +mkdir -p "$PRIMUS_OUTPUT_ROOT/$PRIMUS_TEAM/$PRIMUS_USER/$PRIMUS_EXP_NAME" + +# On spur, direct the sbatch job's aggregated stdout+stderr (the actual per-node +# training log) into the experiment output dir. sbatch returns right after +# submission, so we can't `tee` it here; spur's sbatch reads SBATCH_OUTPUT/ERROR. +if command -v spur >/dev/null 2>&1; then + export SBATCH_OUTPUT="$PRIMUS_OUTPUT_ROOT/$PRIMUS_TEAM/$PRIMUS_USER/$PRIMUS_EXP_NAME/train_sbatch_output.log" + export SBATCH_ERROR="$PRIMUS_OUTPUT_ROOT/$PRIMUS_TEAM/$PRIMUS_USER/$PRIMUS_EXP_NAME/train_sbatch_error.log" +fi + +export PRIMUS_EXIT_FAST=1 + +# Launcher: slurm (default, multi-node cluster) or direct (single-node, already +# inside the container — e.g. local smoke on one box). PRIMUS_LAUNCHER=direct +# drops the SLURM/srun + docker-image wrap that 'direct' doesn't use. +export PRIMUS_LAUNCHER=${PRIMUS_LAUNCHER:-slurm} +if [ "$PRIMUS_LAUNCHER" = "direct" ]; then + LAUNCHER_ARGS=(direct) + if [ "${PRIMUS_NUMA_BIND:-1}" = "1" ]; then + LAUNCHER_ARGS+=(--numa) + fi +else + LAUNCHER_ARGS=(slurm "${SLURM_LAUNCH_CMD:-srun}" -N "$NNODES") + if [ -n "${SLURM_ATTACH_JOBID:-}" ]; then + # Run as a step inside an allocation that is already held (e.g. a long-lived + # `sbatch --exclusive --wrap "sleep ..."` holder), so a sweep of runs lands on + # the same nodes without re-queueing per run. partition / qos / account / + # nodelist / exclusive belong to the holder job -- passing them again on an + # attached step is redundant and spur rejects some of them. + LAUNCHER_ARGS+=(--jobid="${SLURM_ATTACH_JOBID}" --overlap) + else + [ -n "${SLURM_PARTITION:-}" ] && LAUNCHER_ARGS+=(--partition="${SLURM_PARTITION}") + [ -n "${SLURM_NODELIST:-}" ] && LAUNCHER_ARGS+=(--nodelist="${SLURM_NODELIST}") + [ -n "${SLURM_QOS:-}" ] && LAUNCHER_ARGS+=(--qos="${SLURM_QOS}") + [ -n "${SLURM_ACCOUNT:-}" ] && LAUNCHER_ARGS+=(--account="${SLURM_ACCOUNT}") + # --exclusive = whole-node allocation. On spur only sbatch accepts it (srun does + # not), so add it only in sbatch mode. Set SLURM_EXCLUSIVE=0 to disable. + # Spelled as an `if` rather than an `&&` chain because this is the last command + # of the else branch: under `set -e` a false `&&` chain there would make the + # whole compound command fail and abort the script. + if [ "${SLURM_LAUNCH_CMD:-srun}" = "sbatch" ] && [ "${SLURM_EXCLUSIVE:-1}" != "0" ]; then + LAUNCHER_ARGS+=(--exclusive) + fi + fi + # Each patch self-skips (exit 2) when its PRIMUS_* env gate is unset, so both + # can be passed unconditionally. + LAUNCHER_ARGS+=(-- --image "${DOCKER_IMAGE}" --clean -- + --numa + --patch runner/helpers/patches/10_fix_libionic_abi4.sh + --patch runner/helpers/patches/11_fix_lld_stub.sh) +fi + +./primus-cli "${LAUNCHER_ARGS[@]}" \ + -- train pretrain --config "$EXP" \ + --manual_gc True \ + --manual_gc_interval 100 \ + --pp_warmup "${PP_WARMUP:-True}" \ + "${PP_LAYOUT_ARGS[@]}" \ + --moe_router_force_load_balancing True \ + "${MOE_FORCE_LB_ARGS[@]}" \ + --log_avg_skip_iterations 3 \ + --backend_path "$BACKEND_PATH" \ + --num_layers "$PRIMUS_TOTAL_LAYERS" \ + --train_iters "$TRAIN_ITERS" \ + --lr_warmup_iters 0 \ + --lr_decay_iters "$TRAIN_ITERS" \ + --micro_batch_size "$MBS" \ + --global_batch_size "$GBS" \ + --seq_length "$PRIMUS_SEQ_LENGTH" \ + --max_position_embeddings "$PRIMUS_MAX_POSITION_EMBEDDINGS" \ + --rope_type rope \ + --tensor_model_parallel_size "$PRIMUS_TP" \ + --pipeline_model_parallel_size "$PRIMUS_PP" \ + --expert_model_parallel_size "$PRIMUS_EP" \ + --num_experts "$PRIMUS_NUM_EXPERTS" \ + --moe_router_topk "$PRIMUS_MOE_TOPK" \ + --moe_router_enable_expert_bias "$PRIMUS_MOE_ENABLE_EXPERT_BIAS" \ + --moe_ffn_hidden_size "$PRIMUS_MOE_FFN_HIDDEN_SIZE" \ + --index_topk "$PRIMUS_INDEX_TOPK" \ + --v4_grouped_experts_support_clamped_swiglu "$PRIMUS_V4_GROUPED_EXPERTS_SUPPORT_CLAMPED_SWIGLU" \ + --compress_ratios "$PRIMUS_COMPRESS_RATIOS" \ + --mtp_num_layers "${MTP_NUM_LAYERS:-0}" \ + --mock_data True \ + --enable_primus_turbo "$ENABLE_PRIMUS_TURBO" \ + --use_turbo_attention "$USE_TURBO_ATTENTION" \ + --use_v4_attention_backend "$USE_V4_ATTENTION_BACKEND" \ + --use_v4_csa_attention_backend "$USE_V4_CSA_ATTENTION_BACKEND" \ + --use_v4_fp8_indexer "$USE_V4_FP8_INDEXER" \ + --v4_indexer_distill_loss_coeff "$PRIMUS_V4_INDEXER_DISTILL_LOSS_COEFF" \ + --use_v4_compiled_sinkhorn "$USE_V4_COMPILED_SINKHORN" \ + --use_turbo_deepep "$USE_TURBO_DEEPEP" \ + "${TURBO_DEEPEP_CLI_ARGS[@]}" \ + --use_turbo_mega_moe "$USE_TURBO_MEGA_MOE" \ + "${CUDA_GRAPH_CLI_ARGS[@]}" \ + --use_turbo_grouped_gemm "$TURBO_USE_GROUPED_MLP" \ + --moe_use_legacy_grouped_gemm "$LEGACY_GG" \ + "${OPTIMIZER_CLI_ARGS[@]}" \ + --fp8 "$FP8" \ + --fp8_recipe "$FP8_RECIPE" \ + "${FP8_PARAM_GATHER_CLI_ARGS[@]}" \ + --recompute_num_layers "$PRIMUS_RECOMPUTE_LAYERS" \ + --recompute_granularity full \ + --recompute_method block \ + --overlap_grad_reduce "$PRIMUS_OVERLAP_GRAD_REDUCE" \ + --overlap_param_gather "$PRIMUS_OVERLAP_PARAM_GATHER" \ + --disable_last_saving True \ + --disable_wandb True \ + --disable_tensorboard True \ + --profile "$PROFILE" \ + --use_pytorch_profiler "$PROFILE" \ + --profile_step_end 7 \ + --profile_step_start 6 \ + --bias_swiglu_fusion "$PRIMUS_BIAS_SWIGLU_FUSION" \ + 2>&1 | tee "$PRIMUS_OUTPUT_ROOT/$PRIMUS_TEAM/$PRIMUS_USER/$PRIMUS_EXP_NAME/log_node_${NODE_RANK:-0}.txt" diff --git a/examples/deepseek-v4/run_deepseek_v4_flash.sh b/examples/deepseek-v4/run_deepseek_v4_flash.sh new file mode 100755 index 000000000..4194086a8 --- /dev/null +++ b/examples/deepseek-v4/run_deepseek_v4_flash.sh @@ -0,0 +1,408 @@ +#!/bin/bash +# +# DeepSeek-V4-Flash pretraining on AMD Instinct MI355X. +# +# Six families of optimization, each behind one switch, all on by default, so +# running this script with nothing but an image reproduces the best measured +# configuration: +# +# PRIMUS_OPT_FUSION (1) small kernel fusions (RMSNorm, RoPE, Sinkhorn, +# hyper-connections, compressor pooling, indexer +# tail, router tail, grouped-weight stack, plus the +# Megatron permute / CE / grad-accum fusions) +# PRIMUS_OPT_ATTENTION (2) attention backend: FlyDSL sparse-MLA ("turbo") +# PRIMUS_OPT_DEEPEP (3) DeepEP dispatch/combine + Turbo grouped GEMM +# PRIMUS_OPT_SYNC_FREE (4) sync-free MoE stages +# PRIMUS_OPT_MEGA_MOE (5) MegaMoE -- a REPLACEMENT for (3) and (4) +# PRIMUS_OPT_LAYOUT (6) pipeline layout + recompute depth +# +# Set any of them to 0 to turn that family off. Individual knobs inside a family +# can still be overridden one at a time: every export below is +# `${VAR:-}`, so the environment always wins. +# +# ----------------------------------------------------------------------------- +# Reproducing the speedup curve +# ----------------------------------------------------------------------------- +# Each step keeps everything from the steps above it and adds one change. Set +# these once, then run any step below unchanged: +# +# export DOCKER_IMAGE= +# export SLURM_ALLOC_JOB_ID= # optional: join an allocation you hold +# export TRAIN_ITERS=10 +# cd +# +# The throughput after each command is what a 4-node MI355X run measured at +# 10 iterations with router load balancing forced to uniform (TFLOP/s per GPU). +# +# step 0 -- baseline, every optimization off +# PRIMUS_OPT_FUSION=0 PRIMUS_OPT_ATTENTION=0 PRIMUS_OPT_DEEPEP=0 \ +# PRIMUS_OPT_SYNC_FREE=0 PRIMUS_OPT_MEGA_MOE=0 PRIMUS_OPT_LAYOUT=0 \ +# bash examples/deepseek-v4/run_deepseek_v4_flash.sh +# +# step 1 -- + small kernel fusions +# PRIMUS_OPT_FUSION=1 PRIMUS_OPT_ATTENTION=0 PRIMUS_OPT_DEEPEP=0 \ +# PRIMUS_OPT_SYNC_FREE=0 PRIMUS_OPT_MEGA_MOE=0 PRIMUS_OPT_LAYOUT=0 \ +# bash examples/deepseek-v4/run_deepseek_v4_flash.sh +# +# step 2 -- attention to Gluon. The ATTENTION switch means FlyDSL, so the +# Gluon backend is named explicitly on both paths. +# PRIMUS_OPT_FUSION=1 PRIMUS_OPT_ATTENTION=0 PRIMUS_OPT_DEEPEP=0 \ +# PRIMUS_OPT_SYNC_FREE=0 PRIMUS_OPT_MEGA_MOE=0 PRIMUS_OPT_LAYOUT=0 \ +# USE_V4_ATTENTION_BACKEND=gluon_v3 USE_V4_CSA_ATTENTION_BACKEND=gluon_v3 \ +# bash examples/deepseek-v4/run_deepseek_v4_flash.sh +# +# step 3 -- attention to FlyDSL sparse-MLA +# PRIMUS_OPT_FUSION=1 PRIMUS_OPT_ATTENTION=1 PRIMUS_OPT_DEEPEP=0 \ +# PRIMUS_OPT_SYNC_FREE=0 PRIMUS_OPT_MEGA_MOE=0 PRIMUS_OPT_LAYOUT=0 \ +# bash examples/deepseek-v4/run_deepseek_v4_flash.sh +# +# step 4 -- + DeepEP alone. The DEEPEP switch also turns on the Turbo grouped +# GEMM, so the GEMM is held off to measure the two apart. +# PRIMUS_OPT_FUSION=1 PRIMUS_OPT_ATTENTION=1 PRIMUS_OPT_DEEPEP=1 \ +# PRIMUS_OPT_SYNC_FREE=0 PRIMUS_OPT_MEGA_MOE=0 PRIMUS_OPT_LAYOUT=0 \ +# TURBO_USE_GROUPED_MLP=False YAML_TURBO_GROUPED_GEMM=false \ +# bash examples/deepseek-v4/run_deepseek_v4_flash.sh +# +# step 5 -- + Turbo grouped GEMM +# PRIMUS_OPT_FUSION=1 PRIMUS_OPT_ATTENTION=1 PRIMUS_OPT_DEEPEP=1 \ +# PRIMUS_OPT_SYNC_FREE=0 PRIMUS_OPT_MEGA_MOE=0 PRIMUS_OPT_LAYOUT=0 \ +# bash examples/deepseek-v4/run_deepseek_v4_flash.sh +# +# step 6 -- MegaMoE, which replaces DeepEP and the grouped GEMM +# PRIMUS_OPT_MEGA_MOE=1 PRIMUS_OPT_LAYOUT=0 \ +# bash examples/deepseek-v4/run_deepseek_v4_flash.sh +# +# step 7 -- + tuned pipeline layout and no recompute. This is the default, +# so it needs no switches at all. +# bash examples/deepseek-v4/run_deepseek_v4_flash.sh +# +# Steps 6 and 7 need no explicit PRIMUS_OPT_DEEPEP / PRIMUS_OPT_SYNC_FREE: +# MegaMoE turns both off itself, since it replaces rather than stacks on them. + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PRIMUS_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" + +# ============================================================================= +# Optimization switches +# ============================================================================= +PRIMUS_OPT_FUSION=${PRIMUS_OPT_FUSION:-1} +PRIMUS_OPT_ATTENTION=${PRIMUS_OPT_ATTENTION:-1} +PRIMUS_OPT_DEEPEP=${PRIMUS_OPT_DEEPEP:-1} +PRIMUS_OPT_SYNC_FREE=${PRIMUS_OPT_SYNC_FREE:-1} +PRIMUS_OPT_MEGA_MOE=${PRIMUS_OPT_MEGA_MOE:-1} +PRIMUS_OPT_LAYOUT=${PRIMUS_OPT_LAYOUT:-1} + +# (5) replaces (3) and (4) rather than stacking on them: MegaMoE builds its own +# experts, brings its own all-to-all, and never reaches token_dispatcher or +# grouped_experts, so DeepEP, the Turbo grouped GEMM and the sync-free stages +# have nothing left to accelerate. run_deepseek_v4.sh already forces +# USE_TURBO_DEEPEP=False when MegaMoE is on; the rest is turned off here so the +# summary printed at the end matches what actually runs. +if [ "$PRIMUS_OPT_MEGA_MOE" = 1 ]; then + PRIMUS_OPT_DEEPEP=0 + PRIMUS_OPT_SYNC_FREE=0 +fi + +# ============================================================================= +# Cluster wiring -- site-specific, not an optimization +# ============================================================================= +if command -v spur >/dev/null 2>&1; then + export PRIMUS_LAUNCHER=slurm + export SLURM_LAUNCH_CMD="${SLURM_LAUNCH_CMD:-srun}" + # Partition / QOS / account are site-specific and only forwarded when + # non-empty, so set them in the environment for your cluster. + export SLURM_PARTITION="${SLURM_PARTITION:-}" + export SLURM_QOS="${SLURM_QOS:-}" + export SLURM_ACCOUNT="${SLURM_ACCOUNT:-}" + # Empty = let the scheduler allocate nodes. An incoming SLURM_NODELIST is + # honored so callers can pin to specific known-good nodes. + export SLURM_NODELIST="${SLURM_NODELIST:-}" + # Path to an ABI-4 libionic provider .so to swap into the container at launch + # (fixes ionic RDMA on images whose bundled libionic only advertises uverbs + # ABI 1). tools/patches/fix_libionic_abi4.sh reads it; empty disables it. + export PRIMUS_LIBIONIC_SRC_ABI4_SO="${PRIMUS_LIBIONIC_SRC_ABI4_SO:-}" + export NCCL_DEBUG="${NCCL_DEBUG:-}" + export GLOO_SOCKET_IFNAME="${GLOO_SOCKET_IFNAME:-ens3}" + export NCCL_SOCKET_IFNAME="${NCCL_SOCKET_IFNAME:-ens3}" + # Registry login, needed only when the image is not public. Each node runs + # `docker login` before the pull, and sbatch --export=ALL propagates these. + # Empty means no login is attempted, which keeps credentials out of this + # git-tracked file. + export DOCKER_LOGIN_USER="${DOCKER_LOGIN_USER:-}" + export DOCKER_LOGIN_KEY="${DOCKER_LOGIN_KEY:-}" +else + # dccs cluster. Partition / nodelist are not pinned here; export + # SLURM_PARTITION / SLURM_NODELIST to target specific hardware. + # + # Socket interface: run_deepseek_v4.sh falls back to `lo`, which leaves + # multi-node rendezvous hanging. The dccs front-end NIC is `fenic` (the RDMA + # devices are benic1p1..benic8p1 / ionic_0..7). runner/helpers/hooks/ + # 10_auto_nccl_net.sh would auto-detect it, but only when these are unset, and + # the `lo` fallback sets them -- so pin them here. + export GLOO_SOCKET_IFNAME="${GLOO_SOCKET_IFNAME:-fenic}" + export NCCL_SOCKET_IFNAME="${NCCL_SOCKET_IFNAME:-fenic}" + + # Optionally reuse one held allocation instead of queueing per run, so a sweep + # keeps landing on the same (known-good) nodes: + # salloc --no-shell -N 4 --exclusive --partition= --mem=0 + # SLURM_ALLOC_JOB_ID= bash examples/deepseek-v4/run_deepseek_v4_flash.sh + # srun then joins that allocation rather than asking for new nodes; + # --no-shell keeps it alive independently of any terminal, and + # `scancel ` releases it. Left unset by default: a job id is only valid + # for the life of its allocation. + SLURM_ALLOC_JOB_ID="${SLURM_ALLOC_JOB_ID:-}" + if [ -n "$SLURM_ALLOC_JOB_ID" ]; then + export SLURM_JOB_ID="${SLURM_JOB_ID:-$SLURM_ALLOC_JOB_ID}" + export SLURM_JOBID="${SLURM_JOBID:-$SLURM_ALLOC_JOB_ID}" + # Let the step share nodes the allocation already holds. + export SLURM_OVERLAP="${SLURM_OVERLAP:-1}" + # srun rejects a --nodelist that is not inside the allocation, so take the + # allocation's own list. Empty (allocation still pending) is fine; srun + # then just uses every node it was given. + export SLURM_NODELIST="${SLURM_NODELIST:-$(squeue -h -j "$SLURM_ALLOC_JOB_ID" -o '%N' 2>/dev/null || true)}" + fi + export GLOO_SOCKET_IFNAME="${GLOO_SOCKET_IFNAME:-fenic}" + export NCCL_SOCKET_IFNAME="${NCCL_SOCKET_IFNAME:-fenic}" +fi + +# ============================================================================= +# Model shape and parallelism -- must stay identical across configurations +# being compared +# ============================================================================= +export PRIMUS_TOTAL_LAYERS=${PRIMUS_TOTAL_LAYERS:-43} +export PRIMUS_NUM_EXPERTS=${PRIMUS_NUM_EXPERTS:-256} +export PRIMUS_MOE_TOPK=${PRIMUS_MOE_TOPK:-6} +export PRIMUS_MOE_FFN_HIDDEN_SIZE=${PRIMUS_MOE_FFN_HIDDEN_SIZE:-2048} +export PRIMUS_INDEX_TOPK=${PRIMUS_INDEX_TOPK:-512} +export PRIMUS_COMPRESS_RATIOS=${PRIMUS_COMPRESS_RATIOS:-'[0, 0, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 128, 4, 0]'} +export MTP_NUM_LAYERS=${MTP_NUM_LAYERS:-1} +export NNODES=${NNODES:-4} +export PRIMUS_TP=${PRIMUS_TP:-1} +export PRIMUS_PP=${PRIMUS_PP:-4} +export PRIMUS_EP=${PRIMUS_EP:-8} + +export MBS=${MBS:-1} +export GBS=${GBS:-$((64 * NNODES * MBS))} +export TRAIN_ITERS=${TRAIN_ITERS:-10} +export PRIMUS_SEQ_LENGTH=${PRIMUS_SEQ_LENGTH:-4096} +export PRIMUS_MAX_POSITION_EMBEDDINGS=${PRIMUS_MAX_POSITION_EMBEDDINGS:-${PRIMUS_SEQ_LENGTH}} + +# Force load balancing so the expert GEMM shapes are step-invariant. This keeps +# a comparison between two configurations from being polluted by routing jitter; +# `uniform` is closer to real routing than `even`, which hands every expert an +# identical token count and flatters the unfused grouped-GEMM path. +export MOE_FORCE_LB_TYPE=${MOE_FORCE_LB_TYPE:-uniform} + +# ============================================================================= +# (1) Small kernel fusions +# ============================================================================= +# Each of these replaces a chain of small elementwise ops -- and the HBM round +# trip per op -- with a single Triton kernel. Individually none is dramatic; +# together they are the largest single step in the speedup curve. +if [ "$PRIMUS_OPT_FUSION" = 1 ]; then + _F=1; _FY=true; _FB=True +else + _F=0; _FY=false; _FB=False +fi + +export PRIMUS_RMSNORM_TRITON=${PRIMUS_RMSNORM_TRITON:-$_F} # RMSNorm +export PRIMUS_ROPE_TRITON=${PRIMUS_ROPE_TRITON:-$_F} # interleaved partial RoPE +export PRIMUS_SINKHORN_TRITON=${PRIMUS_SINKHORN_TRITON:-$_F} # Sinkhorn-Knopp +export PRIMUS_HC_TRITON=${PRIMUS_HC_TRITON:-$_F} # hyper-connection glue +export PRIMUS_HC_COLLAPSE_TRITON=${PRIMUS_HC_COLLAPSE_TRITON:-$_F} # hyper-connection collapse +export PRIMUS_HC_EXPAND_TRITON=${PRIMUS_HC_EXPAND_TRITON:-$_F} # hyper-connection expand +export PRIMUS_COMPRESS_POOL_TRITON=${PRIMUS_COMPRESS_POOL_TRITON:-$_F} # compressor pooling +export PRIMUS_INDEXER_TRITON=${PRIMUS_INDEXER_TRITON:-$_F} # indexer scoring tail +export PRIMUS_V4_ROUTER_TRITON=${PRIMUS_V4_ROUTER_TRITON:-$_F} # MoE router tail +export PRIMUS_STACK_GROUPED_WEIGHT_TRITON=${PRIMUS_STACK_GROUPED_WEIGHT_TRITON:-$_F} # grouped weight stack +export PRIMUS_INDEXER_FUSE_PROJ=${PRIMUS_INDEXER_FUSE_PROJ:-$_F} # indexer q/k projection fuse +export PRIMUS_INDEXER_MASK_CACHE=${PRIMUS_INDEXER_MASK_CACHE:-$_F} # indexer causal-mask cache +export PRIMUS_COMPRESS_ROPE_CACHE=${PRIMUS_COMPRESS_ROPE_CACHE:-$_F} # compressed RoPE cache +# Backward-side fusions inside the Triton/FlyDSL attention kernels. +export PRIMUS_V4_ATTN_BWD_USE_SPLIT=${PRIMUS_V4_ATTN_BWD_USE_SPLIT:-$_F} +export PRIMUS_V4_CSA_BWD_SEGREDUCE=${PRIMUS_V4_CSA_BWD_SEGREDUCE:-$_F} +# An alternative, wider indexer fusion. Off by default even with (1) on: it +# supersedes PRIMUS_INDEXER_TRITON and has not been the faster of the two here. +export PRIMUS_INDEXER_TRITON_FULL=${PRIMUS_INDEXER_TRITON_FULL:-0} +# torch.compile path for Sinkhorn, an alternative to the Triton kernel above. +export USE_V4_COMPILED_SINKHORN=${USE_V4_COMPILED_SINKHORN:-$_FB} + +# Fusions that live in the experiment yaml (see the derivation at the end). +# use_turbo_rms_norm additionally needs the primus_turbo master gate, which only +# comes on with (3) or (5). +YAML_MOE_PERMUTE_FUSION=${YAML_MOE_PERMUTE_FUSION:-$_FY} +YAML_CE_LOSS_FUSION=${YAML_CE_LOSS_FUSION:-$_FY} +YAML_GRAD_ACC_FUSION=${YAML_GRAD_ACC_FUSION:-$_FY} +YAML_TURBO_RMS_NORM=${YAML_TURBO_RMS_NORM:-$_FY} + +# ============================================================================= +# (2) Attention backend +# ============================================================================= +# The dense/HCA path and the CSA path are selected separately because CSA's +# indexer and top-k selection make it a different kernel problem. Backends, in +# increasing order of speed: eager, triton_v1, triton_v2, gluon_v2, gluon_v3, +# turbo (Primus-Turbo native FlyDSL sparse-MLA). +# +# `eager` cannot run at this scale: it materialises a [B, H, S, S] tensor, about +# 16 GiB per microbatch per layer at V4-Flash dimensions, and fails to allocate +# at any recompute setting. The runnable zero point is triton_v1. +if [ "$PRIMUS_OPT_ATTENTION" = 1 ]; then _ATTN=turbo; else _ATTN=triton_v1; fi +export USE_V4_ATTENTION_BACKEND=${USE_V4_ATTENTION_BACKEND:-$_ATTN} +export USE_V4_CSA_ATTENTION_BACKEND=${USE_V4_CSA_ATTENTION_BACKEND:-$_ATTN} +# A separate flash-attention path that takes dispatch precedence over the V4 +# backend on dense layers; measured far slower here because it cannot do SWA. +export USE_TURBO_ATTENTION=${USE_TURBO_ATTENTION:-False} +# Keep the indexer QK in high precision. The indexer decides which compressed KV +# entries each query attends to, so quantization error there changes the +# selection itself rather than merely perturbing a value. It is also a fake +# quant (quantize/dequantize around a BF16 GEMM), so leaving it off removes pure +# overhead. Set True to opt back in for QAT experiments. +export USE_V4_FP8_INDEXER=${USE_V4_FP8_INDEXER:-False} + +# ============================================================================= +# (3) DeepEP + Turbo grouped GEMM +# ============================================================================= +# DeepEP moves the expert-parallel dispatch and combine into dedicated kernels +# instead of PyTorch permutation around two all-to-alls. The Turbo grouped GEMM +# issues the local experts' GEMMs as one ragged-batch kernel rather than a loop +# of small ones -- measured separately, the grouped GEMM is worth several times +# what DeepEP is. +if [ "$PRIMUS_OPT_DEEPEP" = 1 ]; then _EP=True; _EPY=true; else _EP=False; _EPY=false; fi +export USE_TURBO_DEEPEP=${USE_TURBO_DEEPEP:-$_EP} +export TURBO_USE_GROUPED_MLP=${TURBO_USE_GROUPED_MLP:-$_EP} +YAML_TURBO_GROUPED_GEMM=${YAML_TURBO_GROUPED_GEMM:-$_EPY} +export LEGACY_GG=${LEGACY_GG:-False} +# run_deepseek_v4.sh only sets this when TURBO_USE_GROUPED_MLP=True but then +# dereferences it unconditionally under `set -u`, so it must exist either way. +export PRIMUS_BIAS_SWIGLU_FUSION=${PRIMUS_BIAS_SWIGLU_FUSION:-False} + +# ============================================================================= +# (4) Sync-free MoE +# ============================================================================= +# Stage 1 fuses the router with the aux score and enables permutation fusion; +# stage 2 additionally implies DeepEP and the grouped GEMM. Note that the fused +# router drives Megatron's TopKRouter, while V4 uses its own learned router, so +# on this model stage 1 reduces to permutation fusion alone. +# +# Stage > 1 with the grouped GEMM off raises ValueError before Megatron can +# auto-enable it, which is why (4) implies (3) below. +if [ "$PRIMUS_OPT_SYNC_FREE" = 1 ]; then _SF=1; _SFY=true; else _SF=0; _SFY=false; fi +YAML_SYNC_FREE_STAGE=${YAML_SYNC_FREE_STAGE:-$_SF} +YAML_FUSED_ROUTER=${YAML_FUSED_ROUTER:-$_SFY} + +# ============================================================================= +# (5) MegaMoE +# ============================================================================= +# Fuses the expert-parallel all-to-all into the grouped GEMM itself, so the +# ideal cost becomes max(comm, gemm) rather than their sum. EP-only: needs TP=1, +# BF16 and an EP process group. Mutually exclusive with (3), which +# run_deepseek_v4.sh enforces by forcing USE_TURBO_DEEPEP=False. +if [ "$PRIMUS_OPT_MEGA_MOE" = 1 ]; then _MM=True; else _MM=False; fi +export USE_TURBO_MEGA_MOE=${USE_TURBO_MEGA_MOE:-$_MM} + +# Master gate for every primus_turbo patch (DeepEP dispatcher, Turbo RMSNorm, +# MegaMoE, Turbo grouped GEMM, sync-free auto-enable). run_deepseek_v4.sh turns +# it on automatically when any turbo feature is requested; pinned here so the +# state is explicit when every MoE optimization is off. +if [ "$PRIMUS_OPT_DEEPEP" = 1 ] || [ "$PRIMUS_OPT_MEGA_MOE" = 1 ]; then + export ENABLE_PRIMUS_TURBO=${ENABLE_PRIMUS_TURBO:-True} +else + export ENABLE_PRIMUS_TURBO=${ENABLE_PRIMUS_TURBO:-False} +fi + +# ============================================================================= +# (6) Pipeline layout + recompute +# ============================================================================= +# An even split is not balanced: the last stage also carries the MTP module and +# the loss, while 1F1B leaves stage 0 holding the most microbatches in flight. +# The tuned layout moves layers off the last stage onto the middle ones and +# keeps stage 0 small. Recompute can then go to zero, but only because the +# optimizations above have freed the memory for it -- dropping it first will +# run out of memory. +if [ "$PRIMUS_OPT_LAYOUT" = 1 ]; then + export PRIMUS_PP_LAYOUT="${PRIMUS_PP_LAYOUT:-Et*10|t*12|t*12|t*9mL}" + export PRIMUS_RECOMPUTE_LAYERS=${PRIMUS_RECOMPUTE_LAYERS:-0} +else + export PRIMUS_PP_LAYOUT="${PRIMUS_PP_LAYOUT:-Et*10|t*11|t*11|t*11mL}" + export PRIMUS_RECOMPUTE_LAYERS=${PRIMUS_RECOMPUTE_LAYERS:-3} +fi +export RECOMPUTE_GRANULARITY=${RECOMPUTE_GRANULARITY:-full} +export RECOMPUTE_METHOD=${RECOMPUTE_METHOD:-block} + +# ============================================================================= +# Experiment config: derived yaml +# ============================================================================= +# Seven of the switches above land on fields the shipped experiment yaml +# hardcodes, and run_deepseek_v4.sh passes no CLI override for them, so no +# environment variable can reach them. Derive a yaml rather than editing the +# original, tagged so concurrent configurations never share a file. +YAML_TAG=${YAML_TAG:-flash} +SRC_YAML="$PRIMUS_ROOT/examples/megatron/configs/MI355X/deepseek_v4_flash-BF16-pretrain.yaml" +GEN_YAML="$PRIMUS_ROOT/examples/megatron/configs/MI355X/deepseek_v4_flash-BF16-${YAML_TAG}.yaml" + +if [ ! -f "$SRC_YAML" ]; then + echo "[flash] ERROR: source config not found: $SRC_YAML" >&2 + exit 1 +fi + +# Always regenerate: the same tag must never carry values from a previous run. +{ + echo "# GENERATED by examples/deepseek-v4/run_deepseek_v4_flash.sh -- do not edit." + echo "# switches: fusion=$PRIMUS_OPT_FUSION attention=$PRIMUS_OPT_ATTENTION deepep=$PRIMUS_OPT_DEEPEP" + echo "# sync_free=$PRIMUS_OPT_SYNC_FREE mega_moe=$PRIMUS_OPT_MEGA_MOE layout=$PRIMUS_OPT_LAYOUT" + sed \ + -e "s/^\( *\)turbo_sync_free_moe_stage: *[0-9]*/\1turbo_sync_free_moe_stage: $YAML_SYNC_FREE_STAGE/" \ + -e "s/^\( *\)use_turbo_rms_norm: *\(true\|false\)/\1use_turbo_rms_norm: $YAML_TURBO_RMS_NORM/" \ + -e "s/^\( *\)use_turbo_grouped_gemm: *\(true\|false\)/\1use_turbo_grouped_gemm: $YAML_TURBO_GROUPED_GEMM/" \ + -e "s/^\( *\)moe_use_fused_router_with_aux_score: *\(true\|false\)/\1moe_use_fused_router_with_aux_score: $YAML_FUSED_ROUTER/" \ + -e "s/^\( *\)moe_permute_fusion: *\(true\|false\)/\1moe_permute_fusion: $YAML_MOE_PERMUTE_FUSION/" \ + -e "s/^\( *\)cross_entropy_loss_fusion: *\(true\|false\)/\1cross_entropy_loss_fusion: $YAML_CE_LOSS_FUSION/" \ + -e "s/^\( *\)gradient_accumulation_fusion: *\(true\|false\)/\1gradient_accumulation_fusion: $YAML_GRAD_ACC_FUSION/" \ + "$SRC_YAML" +} > "$GEN_YAML" + +# Fail loudly rather than silently training a config that is not the one asked +# for: a rename upstream would otherwise pass through unnoticed. +for kv in "turbo_sync_free_moe_stage: $YAML_SYNC_FREE_STAGE" \ + "use_turbo_rms_norm: $YAML_TURBO_RMS_NORM" \ + "use_turbo_grouped_gemm: $YAML_TURBO_GROUPED_GEMM" \ + "moe_use_fused_router_with_aux_score: $YAML_FUSED_ROUTER" \ + "moe_permute_fusion: $YAML_MOE_PERMUTE_FUSION" \ + "cross_entropy_loss_fusion: $YAML_CE_LOSS_FUSION" \ + "gradient_accumulation_fusion: $YAML_GRAD_ACC_FUSION"; do + if ! grep -q "$kv" "$GEN_YAML"; then + echo "[flash] ERROR: '$kv' missing from $GEN_YAML -- did the source yaml change shape?" >&2 + exit 1 + fi +done + +# Sync-free stage > 1 needs the grouped GEMM already on, or Megatron raises +# before it gets a chance to auto-enable it. +if [ "$YAML_SYNC_FREE_STAGE" -gt 1 ] && [ "$YAML_TURBO_GROUPED_GEMM" != "true" ] \ + && [ "$TURBO_USE_GROUPED_MLP" != "True" ]; then + echo "[flash] ERROR: sync-free stage $YAML_SYNC_FREE_STAGE requires the grouped GEMM" >&2 + exit 1 +fi + +export EXP="${EXP:-$GEN_YAML}" + +export PROFILE=${PROFILE:-False} +export PRIMUS_EXP_NAME=${PRIMUS_EXP_NAME:-deepseek_v4_flash_nodes${NNODES}_pp${PRIMUS_PP}_ep${PRIMUS_EP}_seq${PRIMUS_SEQ_LENGTH}} + +echo "[flash] (1) fusion=$PRIMUS_OPT_FUSION (2) attention=$PRIMUS_OPT_ATTENTION (3) deepep=$PRIMUS_OPT_DEEPEP (4) sync_free=$PRIMUS_OPT_SYNC_FREE (5) mega_moe=$PRIMUS_OPT_MEGA_MOE (6) layout=$PRIMUS_OPT_LAYOUT" +echo "[flash] attention=$USE_V4_ATTENTION_BACKEND/$USE_V4_CSA_ATTENTION_BACKEND turbo_gate=$ENABLE_PRIMUS_TURBO" +echo "[flash] deepep=$USE_TURBO_DEEPEP grouped_gemm=$TURBO_USE_GROUPED_MLP mega_moe=$USE_TURBO_MEGA_MOE sync_free_stage=$YAML_SYNC_FREE_STAGE" +echo "[flash] layout=${PRIMUS_PP_LAYOUT:-} recompute=$PRIMUS_RECOMPUTE_LAYERS" +echo "[flash] nodes=$NNODES tp=$PRIMUS_TP pp=$PRIMUS_PP ep=$PRIMUS_EP gbs=$GBS seq=$PRIMUS_SEQ_LENGTH iters=$TRAIN_ITERS" +echo "[flash] exp=$EXP" + +# Resolve the configuration and print it without launching anything, so a switch +# combination can be checked before an allocation is spent on it. +if [ "${PRIMUS_DRY_RUN:-0}" = 1 ]; then + echo "[flash] dry run: configuration resolved, not launching" + exit 0 +fi + +exec "${SCRIPT_DIR}/run_deepseek_v4.sh" 2>&1 | tee "train_flash_${YAML_TAG}.log" diff --git a/examples/deepseek-v4/run_deepseek_v4_flash_proxy.sh b/examples/deepseek-v4/run_deepseek_v4_flash_proxy.sh new file mode 100755 index 000000000..1552fd6ff --- /dev/null +++ b/examples/deepseek-v4/run_deepseek_v4_flash_proxy.sh @@ -0,0 +1,280 @@ +#!/bin/bash +############################################################################### +# DeepSeek-V4 Flash perf PROXY runner (latest config: plan-5 P32 final). +# +# Wraps `run_deepseek_v4.sh` with a V4-Flash production-shape proxy: +# +# - num_layers 8 (vs production 43; PROXY) +# - hidden_size 4096 (full V4-Flash; from yaml) +# - num_heads 64 (full V4-Flash; from yaml) +# - head_dim 512 (full V4-Flash; from yaml) +# - num_experts 256 (full V4-Flash; PROXY-friendly +# 32 experts/rank at EP=8) +# - moe_router_topk 6 (full V4-Flash) +# - moe_ffn_hidden 2048 (full V4-Flash) +# - index_topk 512 (full V4-Flash CSA top-K) +# - compress_ratios [0,0,4,128,4,128,4,0] (8-layer slice exercising +# every layer kind: 3 cr=0, +# 3 cr=4, 2 cr=128) +# - parallel TP=1 PP=1 EP=8 (single-node 8 GPU) +# - seq_length 4096 (default) (V4 pretrain target; +# set PRIMUS_SEQ_LENGTH=2048 +# / 1024 / 512 on the +# command line if OOM) +# +# Plan-5 perf knobs default ON: +# - USE_V4_ATTENTION_BACKEND (cr ∈ {0, 128} dense/HCA backend; default triton_v2) +# - USE_V4_CSA_ATTENTION_BACKEND (cr == 4 CSA backend; default triton_v2) +# - USE_TURBO_DEEPEP (PrimusTurboDeepEPTokenDispatcher) +# - TURBO_USE_GROUPED_MLP (Turbo grouped-GEMM MoE expert path) +# - USE_V4_COMPILED_SINKHORN (P29: torch.compile-fused Sinkhorn, +# kills the 7.6 s aten::sum fp32 reduce +# that dominated the P28 baseline) +# +# Plan-5 P32 final attention-kernel knobs (also default ON in code; surfaced +# here for visibility / easy A/B): +# - PRIMUS_V4_ATTN_BWD_USE_SPLIT (atomic-free split V4 attention BWD: +# dQ kernel + dK/dV kernel, each writes +# its own disjoint tiles via tl.store +# instead of atomic_add on a shared buf) +# - PRIMUS_V4_CSA_BWD_SEGREDUCE (atomic-free CSA pool BWD via per-visit +# partial buffer + sorted inverse-index +# segmented reduction into dpool) +# +# These two relied on the **plan-5 P32 dual-RoPE bf16 cast fix** in +# `apply_interleaved_partial_rope` (`primus/backends/megatron/core/transformer/ +# dual_rope.py`) to actually win in the proxy: pre-fix, cos/sin from +# `position_ids.float() * inv_freq` was fp32, so `bf16 * fp32 = fp32` +# silently upcast Q / K leaving RoPE — every V4 attention kernel paid 2x +# HBM traffic and ran the slow fp32-specialised Triton binary, inflating +# kernel times 1.8-7x in the proxy and masking the split / segreduce wins. +# The one-line cast of cos/sin to `x.dtype` after the unsqueeze lets the +# microbench-optimal kernels also win end-to-end. See +# `deepseek-v4/develop/progress/p32/p32-summary.md` for the full +# diagnostic walk-through. +# +# USE_TURBO_ATTENTION stays OFF — Turbo would take precedence over the V4 +# Triton dense path in `DeepseekV4Attention.forward` (plan-4 P27 dispatch +# precedence: turbo > v4_triton > eager for cr ∈ {0, 128}). +# +# Steady-state perf (P32 final, mi355-gpu-8 / dev_primus_wenx_693, +# iter 10 of 10, ${VAR:-DEFAULT} only): +# +# iter time : 603 ms / iter (vs P28 baseline 8837 ms; 14.64x) +# TFLOP/s/GPU : 1134 (vs P28 baseline 77.5) +# HBM peak / rank : ~170 GiB +# +# Every override is `${VAR:-DEFAULT}`-guarded, so the caller can flip any +# knob via `PRIMUS_SEQ_LENGTH=2048 ./run_deepseek_v4_flash_proxy.sh` etc. +# without editing the script. +# +# Usage: +# ./run_deepseek_v4_flash_proxy.sh # 10-iter smoke +# TRAIN_ITERS=20 ./run_deepseek_v4_flash_proxy.sh # longer warmup pass +# PRIMUS_V4_ATTN_BWD_USE_SPLIT=0 ./run_deepseek_v4_flash_proxy.sh # fall back to +# # monolithic V4 BWD +# PRIMUS_V4_CSA_BWD_SEGREDUCE=0 ./run_deepseek_v4_flash_proxy.sh # fall back to +# # gather+atomic CSA BWD +# PRIMUS_V4_DIAG_TIME=1 ./run_deepseek_v4_flash_proxy.sh # dump per-call +# # cuda.Event timings for +# # v4_attention (rank 0) +# +# Profile is intentionally OFF in this script (this is the SMOKE / perf +# runner, not the trace capture). For chrome-trace capture use +# `deepseek-v4/develop/progress/p32/run_baseline_trace_ep8_p32_final.sh` +# (mirrors the plan-4 P25 / plan-3 P23 profile-script pattern). +############################################################################### +set -euo pipefail + +# ---------- V4-Flash production widths (8-layer proxy slice) ---------------- +export PRIMUS_TOTAL_LAYERS=${PRIMUS_TOTAL_LAYERS:-8} +export PRIMUS_NUM_EXPERTS=${PRIMUS_NUM_EXPERTS:-256} +export PRIMUS_MOE_TOPK=${PRIMUS_MOE_TOPK:-6} +export PRIMUS_MOE_FFN_HIDDEN_SIZE=${PRIMUS_MOE_FFN_HIDDEN_SIZE:-2048} +export PRIMUS_INDEX_TOPK=${PRIMUS_INDEX_TOPK:-512} +# 8-layer slice — every V4 attention layer kind exercised: +# layer 0 / 1 : cr=0 (dense + SWA + sink) +# layer 2 : cr=4 (CSA) +# layer 3 : cr=128 (HCA) +# layer 4 : cr=4 (CSA) +# layer 5 : cr=128 (HCA) +# layer 6 : cr=4 (CSA) +# layer 7 : cr=0 (dense + SWA + sink) -- V4-Flash production has +# cr=0 first/last layer +export PRIMUS_COMPRESS_RATIOS=${PRIMUS_COMPRESS_RATIOS:-"[0,0,4,128,4,128,4,0]"} + +# ---------- Single-node EP=8 ------------------------------------------------ +export PRIMUS_TP=${PRIMUS_TP:-1} +export PRIMUS_PP=${PRIMUS_PP:-1} +export PRIMUS_EP=${PRIMUS_EP:-8} + +# DP=8 with TP=1 PP=1 EP=8 on 8 GPUs (EP shards experts within DP group). +# GBS=8, MBS=1 -> 1 microbatch / DP rank / iter. Profiling-friendly: +# minimises iter-to-iter variance + keeps activation memory bounded. +export MBS=${MBS:-1} +export GBS=${GBS:-8} + +# ---------- Production seq length target ------------------------------------ +# The CSA wrapper-side gather (plan-4 P26) materialises +# [B, H, Sq, K_topk, D] = [1, 64, Sq, 512, 512] * 2 bytes per microbatch +# in HBM. At Sq=4096 that is 64 GiB / microbatch on top of the 256-expert +# MoE state (~12 GiB / rank for 8 layers) + KV cache + activations + +# optimizer state — likely OOMs at MI355X (192 GiB HBM). The plan-5 P28 +# task is to CALIBRATE this value (try 4096 -> 2048 -> 1024 -> 512) and +# document the chosen value in `develop/profile/profile-baseline-ep8-*`. +# Plan-5 P31 (in-kernel `topk_idxs` gather) is the structural fix that +# eventually lets this default reach 4096. +export PRIMUS_SEQ_LENGTH=${PRIMUS_SEQ_LENGTH:-4096} +export PRIMUS_MAX_POSITION_EMBEDDINGS=${PRIMUS_MAX_POSITION_EMBEDDINGS:-${PRIMUS_SEQ_LENGTH}} + +# ---------- Plan-5 perf knobs (all five ON) --------------------------------- +export USE_V4_ATTENTION_BACKEND=${USE_V4_ATTENTION_BACKEND:-triton_v2} +export USE_V4_CSA_ATTENTION_BACKEND=${USE_V4_CSA_ATTENTION_BACKEND:-triton_v2} +export USE_TURBO_DEEPEP=${USE_TURBO_DEEPEP:-True} +export TURBO_USE_GROUPED_MLP=${TURBO_USE_GROUPED_MLP:-True} +# Plan-5 P29 (RESCOPED): torch.compile-fused HyperMixer Sinkhorn. Kills +# the 7.6 s aten::sum fp32 reduce (87.3 % of step time in the P28 +# baseline trace). Default ON in the proxy after G32 + G33b are green. +export USE_V4_COMPILED_SINKHORN=${USE_V4_COMPILED_SINKHORN:-True} + +# Turbo attention OFF — would take precedence over V4 Triton dense path +# in DeepseekV4Attention.forward (plan-4 P27 dispatch precedence: +# turbo > v4_triton > eager for cr ∈ {0, 128} +# v4_triton_csa > eager for cr == 4 ). +export USE_TURBO_ATTENTION=${USE_TURBO_ATTENTION:-False} + +# ---------- Plan-5 P32 final attention-kernel knobs (split + segreduce) ----- +# Both default ON in the kernel code post-RoPE-fix; surface them here so +# the proxy script self-documents the P32 final perf recipe and so a quick +# A/B fallback is a single env-var flip. See header for the full root-cause +# write-up. +export PRIMUS_V4_ATTN_BWD_USE_SPLIT=${PRIMUS_V4_ATTN_BWD_USE_SPLIT:-1} +export PRIMUS_V4_CSA_BWD_SEGREDUCE=${PRIMUS_V4_CSA_BWD_SEGREDUCE:-1} + +# ---------- Plan-6 elemwise-fusion knobs (default ON; A/B with =0) ---------- +# Plan-6 P40 close-out (2026-05-15): each plan-6 phase that wins the EP=8 +# proxy A/B adds its env knob here as default ON, mirroring the plan-5 P32 +# final precedent above. Phases that microbench-win but proxy-noise-lose +# (P38, P39) ship as default OFF with the kernel checked in for future +# tuning. Cumulative plan-6 win at this composition (P34..P37 ON, P38/P39 +# OFF): **-92.7 ms / iter (-15.4 %) vs plan-5 P32 final**; steady-state +# iter time 510.6 ms / 524.9 TFLOP/s/GPU (peak HBM 172.3 GiB / rank). +# See `deepseek-v4/develop/perf/proxy_ep8.md` row `P40 final` and +# `progress/p40/p40-summary.md` for the full close-out write-up. +# +# The kernel code already defaults each to "1" / "0" appropriately; this +# block makes the runner script self-document the recipe and lets users +# flip individual fusions for A/B without editing source. +# +# P34 — stack_grouped_weight Triton FWD/BWD fusion in +# PrimusTurboGroupedMLP._stack_grouped_linear_weight. +# EP=8 proxy A/B win: 580.65 -> 530.85 ms / iter, -49.8 ms (-8.6%); +# TFLOP/s/GPU 463.2 -> 507.2, +9.5%; lm_loss bit-identical (pure +# layout transform). Default ON since 29baf151 (2026-05-14). +export PRIMUS_STACK_GROUPED_WEIGHT_TRITON=${PRIMUS_STACK_GROUPED_WEIGHT_TRITON:-1} + +# P35 — apply_interleaved_partial_rope Triton FWD/BWD fusion in +# dual_rope.py. Collapses the 9-op eager chain (slice + reshape + +# four broadcast muls + stack + reshape + cat) into one Triton +# kernel that does a single contiguous write with the rotation +# baked in. +# EP=8 proxy A/B win: 531.7 -> 526.7 ms / iter, -5.0 ms (-0.94%); +# TFLOP/s/GPU 507.1 -> 513.3, +1.2%; lm_loss bit-identical (pure +# analytic rotation). Default ON since landing (2026-05-14). +export PRIMUS_ROPE_TRITON=${PRIMUS_ROPE_TRITON:-1} + +# P36 — sinkhorn_normalize Triton FWD/BWD fusion in +# hyper_connection.py. Replaces the plan-5 P29 ``torch.compile`` +# cached Sinkhorn body with a hand-rolled Triton kernel that runs +# the 1 + 2*(n_iters - 1) alternating row/col normalize trajectory +# in registers per row of the leading axis (V4-Flash uses K=4). +# Microbench at V4-Flash K=4 (B=1, S=4096): +# FWD 0.045 ms (vs eager 0.600 ms = 13.4x; vs P29 compiled 0.270 +# ms = 6.0x) +# BWD 0.105 ms (vs eager 1.520 ms = 14.5x; vs P29 compiled 0.628 +# ms = 6.0x) +# The compiled-region overhead (`Torch-Compiled Region` ~21 ms / 16 +# calls + `CompiledFunctionBackward` ~41 ms / 16 calls) is removed +# entirely. Default ON since landing (2026-05-14). +export PRIMUS_SINKHORN_TRITON=${PRIMUS_SINKHORN_TRITON:-1} + +# P37 — HyperConnection compute_weights elemwise tail Triton fusion in +# hyper_connection.HyperMixer.compute_weights. Fuses the 3 slices + +# 3 fused-multiply-adds + 2 sigmoid + 1 softmax + 2 eps adds (the +# post-_packed_logits, pre-Sinkhorn chain) into one FWD + one BWD +# Triton kernel. Microbench at V4-Flash K=4 (B=1, S=4096): +# FWD 0.044 ms (vs eager 0.102 ms = 2.34x) +# BWD 0.276 ms (vs eager 0.405 ms = 1.47x) +# The matmul inside _packed_logits stays as F.linear; collapse / expand +# (matmul-adjacent) stay eager too -- they are not net wins as +# separate Triton kernels. Default ON since landing (2026-05-14). +export PRIMUS_HC_TRITON=${PRIMUS_HC_TRITON:-1} + +# P41 — Indexer.forward post-einsum tail Triton fusion. Re-attempt +# of P38 that keeps the cuBLAS / hipBLASLt einsum eager and fuses +# only the bandwidth-bound tail (`relu + mul(w_i) + sum(H) + +# causal_mask`). +# +# Plan-8 P57 close-out 2 (2026-05-15): default flipped to ON. +# Microbench at V4-Flash widths is a clear positive +# (FWD 4.30x / BWD 1.63x); the EP=8 proxy A/B (10-iter smoke) +# showed ~0.2 ms / iter aggregate gain within the ±1 ms noise band +# (small but consistently positive). We default ON so the +# bandwidth-bound tail is fused by default; set +# PRIMUS_INDEXER_TRITON=0 to revert to the eager body. +# +# The env knob was re-purposed at P41: it now controls the +# post-einsum tail path. Legacy P38 full-fuse path lives behind +# PRIMUS_INDEXER_TRITON_FULL (default OFF, kept in tree for small- +# shape paths and future tuning). +export PRIMUS_INDEXER_TRITON=${PRIMUS_INDEXER_TRITON:-1} +export PRIMUS_INDEXER_TRITON_FULL=${PRIMUS_INDEXER_TRITON_FULL:-0} + +# P39 — V4 Router post-logits Triton FWD/BWD fusion (shared by topk + +# hash router). +# +# Plan-8 P57 close-out 2 (2026-05-15): default flipped to ON. +# Microbench at V4-Flash widths (N=4096, E=256, K=8) wins on V4's +# production `sqrtsoftplus` score function (1.56x FWD / 1.22x BWD). +# P39 / P43 EP=8 proxy A/B was inside the proxy noise band, so the +# conservative landing posture left it default-OFF; for P57 R2 we +# default ON to keep the microbench-positive kernel on the production +# path. Set PRIMUS_V4_ROUTER_TRITON=0 to revert to the eager body. +export PRIMUS_V4_ROUTER_TRITON=${PRIMUS_V4_ROUTER_TRITON:-1} + +# ---------- Precision: FP8 training (paper ue8m0 microscaling) -------------- +# V4-Flash trains in FP8 by default. PRECISION_TYPE=FP8 makes run_deepseek_v4.sh +# emit --fp8 e4m3 --fp8_recipe mxfp8 (mxfp8 = the paper's ue8m0 microscaling, +# E8M0 block scale, native on MI355X/CDNA4). The FP4 expert / FP4-Indexer path +# is not yet wired in the Primus V4 integration ("Phase 2"), so experts run FP8 +# here (FP8-everywhere) rather than FP4 — the closest supported step. FP8 is +# outlier-sensitive, hence the clamped SwiGLU (swiglu_limit) in the EXP yaml. +# A/B back to BF16 with PRECISION_TYPE=BF16; override the recipe via FP8_RECIPE. +export PRECISION_TYPE=${PRECISION_TYPE:-FP8} +export EXP=${EXP:-examples/megatron/configs/MI355X/deepseek_v4_flash-FP8-pretrain.yaml} + +# ---------- Profile OFF in the proxy smoke runner --------------------------- +# This script is the steady-state perf / smoke runner — kineto profiling +# stays OFF to avoid contaminating the iter timer with profiler-collection +# overhead. For chrome-trace capture use +# `deepseek-v4/develop/progress/p32/run_baseline_trace_ep8_p32_final.sh`. +export PROFILE=${PROFILE:-False} + +# ---------- Bookkeeping ----------------------------------------------------- +# Distinguish the proxy run output dir from the smoke run output dir so the +# trace-capture script + the smoke run land side-by-side without clobbering +# each other's logs. +export PRIMUS_EXP_NAME=${PRIMUS_EXP_NAME:-deepseek_v4_flash_proxy_pp${PRIMUS_PP}_ep${PRIMUS_EP}_seq${PRIMUS_SEQ_LENGTH}} + +# ---------- Launcher: single-node in-container by default ------------------- +# This proxy is the single-node 8-GPU smoke/perf runner, normally invoked from +# INSIDE the training container. run_deepseek_v4.sh defaults PRIMUS_LAUNCHER to +# `slurm` (which needs `srun` from a SLURM allocation and would fail in a bare +# container). Default to `direct` here so the proxy just torchruns locally; +# override with PRIMUS_LAUNCHER=slurm when launching from a cluster login node. +export PRIMUS_LAUNCHER=${PRIMUS_LAUNCHER:-direct} + +# Defer to run_deepseek_v4.sh for the actual training launch — every +# CLI flag and the primus-cli invocation lives there. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +exec "${SCRIPT_DIR}/run_deepseek_v4.sh" diff --git a/examples/deepseek-v4/run_deepseek_v4_pro_muon.sh b/examples/deepseek-v4/run_deepseek_v4_pro_muon.sh new file mode 100755 index 000000000..cea44a134 --- /dev/null +++ b/examples/deepseek-v4/run_deepseek_v4_pro_muon.sh @@ -0,0 +1,361 @@ +#!/bin/bash +############################################################################### +# DeepSeek-V4 *Pro* single-node bring-up with the Muon optimizer. +# +# Follows the DeepSeek-V4 paper Pro recipe (§4.2.1 architecture + §4.2.2 +# training setup) as closely as a single 8x288GB node allows: +# 1. Model : deepseek_v4_pro (61L / d7168 / 384 experts — paper §4.2.1). +# Selected via PRIMUS_MODEL, consumed by the +# `model: ${PRIMUS_MODEL:...}.yaml` line in the EXP yaml. +# Widths come from primus/configs/models/megatron/ +# deepseek_v4_pro.yaml; we only override the shape knobs the +# runner exposes (layers/experts/topk/ffn/index_topk/ +# compress_ratios) so the runner's smoke defaults don't win. +# 2. Reduced : 61 layers + seq 4096 do NOT fit one node, so cut depth +# to fit (PRIMUS_TOTAL_LAYERS) and seq (PRIMUS_SEQ_LENGTH); full/ +# uniform recompute on. (CSA-layer gather scales with seq.) +# 3. Optimizer : Muon (paper §4.2.2: Muon for matrices, AdamW for embedding/ +# pred-head/RMSNorm — in-tree ChainedOptimizer). Plain `muon` +# requires overlap_{grad_reduce,param_gather}=False and +# use_distributed_optimizer=False (Megatron asserts). +# 4. Training : paper §4.2.2 hyperparameters — momentum 0.95, update-RMS +# setup 0.18 (muon_extra_scale_factor), AdamW eps 1e-20, LR +# 2.0e-4→2.0e-5, balance-loss 1e-4, and (crucially) a LARGE +# batch via gradient accumulation toward the paper's 94.4M +# tokens/step. The batch is what amortizes the fixed Muon +# Newton-Schulz cost: at GBS=8 (accum 1) NS looks like ~97% +# of GEMM (a starved-batch artifact, NOT a Muon bug); at the +# paper batch it falls to the reported ~1-3%. +# +# Single-node integration gaps vs the paper (not config — Primus V4 TODO): +# - MTP depth 1 (MultiTokenPredictionLayer unsupported) -> MTP_NUM_LAYERS=0 +# - expert-bias/noaux_tc (Megatron needs sigmoid; V4 uses sqrtsoftplus) -> off +# - muon_weight_decay (0.01 vs paper 0.1) / mtp_loss_scaling are yaml-only. +# +# Usage: +# # paper-faithful single-node run (validated: ~890 TFLOP/s/GPU, Muon ~1% GPU): +# PRIMUS_TOTAL_LAYERS=2 PRIMUS_COMPRESS_RATIOS="[128,128]" \ +# PRIMUS_SEQ_LENGTH=4096 GBS=256 ./run_deepseek_v4_pro_muon.sh +# PRIMUS_TOTAL_LAYERS=4 PRIMUS_SEQ_LENGTH=512 GBS=8 \ +# ./run_deepseek_v4_pro_muon.sh # cheap validation +# OPTIMIZER=adam ./run_deepseek_v4_pro_muon.sh # A/B vs AdamW +# PRECISION_TYPE=BF16 ./run_deepseek_v4_pro_muon.sh # A/B vs BF16 (fp8 is default-on) +# PROFILE=True DISABLE_TENSORBOARD=False ... # capture 1-step trace +# +# Precision: FP8 training is ON by default (FP8=e4m3, FP8_RECIPE=tensorwise). +# Paper recipe is ue8m0/mxfp8 but it's not runnable on this gfx950 build (see the +# "FP8 training" block below); tensorwise gives the paper's fp8 layout on the +# weight GEMMs. PRECISION_TYPE=BF16 to A/B back to bf16. +############################################################################### +set -euo pipefail +set -x + +export HF_TOKEN="${HF_TOKEN:-}" + +export NNODES=${PET_NNODES:-1} +export TRAIN_ITERS=${TRAIN_ITERS:-10} +export HSA_NO_SCRATCH_RECLAIM=${HSA_NO_SCRATCH_RECLAIM:-1} + +# ---------- Model: DeepSeek-V4 Pro ----------------------------------------- +export PRIMUS_MODEL=${PRIMUS_MODEL:-deepseek_v4_pro} +export EXP=${EXP:-examples/megatron/configs/MI355X/deepseek_v4_flash-FP8-pretrain.yaml} + +# ---------- Pro production widths (paper §4.2.1) ---------------------------- +export PRIMUS_NUM_EXPERTS=${PRIMUS_NUM_EXPERTS:-384} +export PRIMUS_MOE_TOPK=${PRIMUS_MOE_TOPK:-6} +# Megatron only supports aux-loss-free expert bias with the sigmoid score +# function; V4 uses sqrtsoftplus, so disable expert bias (matches the working +# run_deepseek_v4.sh smoke; balancing falls back to seq_aux_loss). +export PRIMUS_MOE_ENABLE_EXPERT_BIAS=${PRIMUS_MOE_ENABLE_EXPERT_BIAS:-False} +export PRIMUS_MOE_FFN_HIDDEN_SIZE=${PRIMUS_MOE_FFN_HIDDEN_SIZE:-3072} +export PRIMUS_INDEX_TOPK=${PRIMUS_INDEX_TOPK:-1024} + +# ---------- Reduced depth + seq to fit single node ------------------------- +# MEASURED CEILING (chi2774, 8x288GB, 2026-05-20): with full Pro width +# (384 experts) + Muon, *4 layers @ seq 512 = 268.8 GB/rank (93%)* is about +# the single-node max. The binding cost is weights + Muon's fp32 optimizer +# states (Muon forces use_precision_aware_optimizer=False, so states cannot +# be bf16) — NOT activations, so lowering seq does not buy more layers. +# 5+ layers OOMs; for more depth use fewer experts or multi-node. +export PRIMUS_TOTAL_LAYERS=${PRIMUS_TOTAL_LAYERS:-4} +export PRIMUS_SEQ_LENGTH=${PRIMUS_SEQ_LENGTH:-512} +export PRIMUS_MAX_POSITION_EMBEDDINGS=${PRIMUS_MAX_POSITION_EMBEDDINGS:-${PRIMUS_SEQ_LENGTH}} + +# Per-layer compression schedule, length == PRIMUS_TOTAL_LAYERS, mirroring the +# Pro pattern: first two HCA(128), then CSA(4)/HCA(128) interleaved, last +# dense+SWA(0). (Pro full yaml: idx0,1=128; idx>=2 even=4 / odd=128; last=0.) +export PRIMUS_COMPRESS_RATIOS=${PRIMUS_COMPRESS_RATIOS:-$(python3 - "$PRIMUS_TOTAL_LAYERS" <<'PY' +import sys +n=int(sys.argv[1]) +r=[] +for i in range(n): + if i<2: r.append(128) + elif i==n-1: r.append(0) + else: r.append(4 if i%2==0 else 128) +print("["+",".join(map(str,r))+"]") +PY +)} + +# ---------- Single-node EP=8 ----------------------------------------------- +export PRIMUS_TP=${PRIMUS_TP:-1} +export PRIMUS_PP=${PRIMUS_PP:-1} +export PRIMUS_EP=${PRIMUS_EP:-8} +export MBS=${MBS:-1} +# Paper Pro batch = 94.4M tokens/step (batch-size schedule). On one node we +# approach that regime via GRADIENT ACCUMULATION: grad_accum = GBS/(MBS*DP), +# DP=8 here. Large GBS is what amortizes the fixed Muon Newton-Schulz cost over +# many fwd/bwd microbatches (GBS=8 ⇒ accum=1 ⇒ NS runs every step ⇒ NS looks +# like ~97% of GEMM; that was a starved-batch artifact, NOT a Muon bug). At +# GBS=512 (accum 64) × seq 4096 = ~2.1M tokens/step the optimizer share drops +# toward the paper's reported 1-3%. +export GBS=${GBS:-512} + +# ---------- Optimizer: Muon (paper §4.2.2, values for BOTH Flash & Pro) ------ +export OPTIMIZER=${OPTIMIZER:-muon} +# The Primus Muon path (primus/backends/megatron/core/optimizer/moun.py) needs +# the emerging_optimizers package, which is NOT bundled in the container. Set +# PRIMUS_INSTALL_EMERGING_OPTIMIZERS so the in-container install hook +# (runner/.../01_install_emerging_optimizers.sh) provisions the pinned commit. +# Gated on a Muon optimizer so an OPTIMIZER=adam A/B run pays nothing. +if [ "$OPTIMIZER" = "muon" ] || [ "$OPTIMIZER" = "dist_muon" ]; then + export PRIMUS_INSTALL_EMERGING_OPTIMIZERS=${PRIMUS_INSTALL_EMERGING_OPTIMIZERS:-1} +fi +export MUON_MOMENTUM=${MUON_MOMENTUM:-0.95} # paper momentum 0.95 +# Paper: "rescale the RMS of each update matrix to 0.18 for reutilization of the +# AdamW learning rate." With scale_mode=spectral the realized update RMS ≈ +# extra_scale_factor (orth_grad RMS≈1/√max(m,n), times spectral scale √max(m,n)), +# so 0.18 maps directly here. Megatron default is 1.0 (≈5.5× too large vs paper). +export MUON_EXTRA_SCALE_FACTOR=${MUON_EXTRA_SCALE_FACTOR:-0.18} +# Newton-Schulz hardening knobs (matter for mxfp8, where NS can diverge on the +# quant-noised gradient). num_ns_steps = NS iterations (more = better convergence +# on ill-conditioned input); fp32_matmul_prec = precision of the NS matmuls +# ("medium" = tf32-ish, "high" = full fp32 — full precision keeps a near-σ=1 +# input from being pushed past the quintic's stable region by rounding error). +export MUON_NUM_NS_STEPS=${MUON_NUM_NS_STEPS:-5} +export MUON_FP32_MATMUL_PREC=${MUON_FP32_MATMUL_PREC:-medium} +# NOTE: muon_weight_decay is yaml-only (trainer_base.yaml=0.01); paper=0.1. +# No CLI flag, so it stays 0.01 here unless overridden in an EXP yaml. +# Muon hard requirements (Megatron arguments.py:1422): +export USE_DISTRIBUTED_OPTIMIZER=${USE_DISTRIBUTED_OPTIMIZER:-False} +export USE_PRECISION_AWARE_OPTIMIZER=${USE_PRECISION_AWARE_OPTIMIZER:-False} + +# ---------- Paper §4.2.2 Pro training hyperparameters ----------------------- +export LR=${LR:-2.0e-4} # Pro peak LR (Flash exp yaml had 1e-5) +export MIN_LR=${MIN_LR:-2.0e-5} # Pro end LR +export ADAM_EPS=${ADAM_EPS:-1.0e-20} # paper AdamW eps (NOTE: needs decimal point — Primus parses "1e-20" as a string) +export MOE_AUX_LOSS_COEFF=${MOE_AUX_LOSS_COEFF:-0.0001} # paper balance-loss weight +# Paper MTP depth = 1, but the Primus V4 integration does NOT yet support the +# MTP layer ("Unsupported mtp_model_layer submodules type ... when instantiating +# MultiTokenPredictionLayer"), so default 0 here. Set =1 once V4 MTP lands. +export MTP_NUM_LAYERS=${MTP_NUM_LAYERS:-0} + +# ---------- Perf knobs (V4 Triton attn + Turbo MoE; same family as proxy) -- +export ENABLE_PRIMUS_TURBO=${ENABLE_PRIMUS_TURBO:-True} +export USE_TURBO_ATTENTION=${USE_TURBO_ATTENTION:-False} +export USE_TURBO_DEEPEP=${USE_TURBO_DEEPEP:-True} +export TURBO_USE_GROUPED_MLP=${TURBO_USE_GROUPED_MLP:-True} +# Primus Sync-Free MoE (eliminates the DeepEP host busy-wait on the variable +# per-expert token counts): 0=off, 1=fused router/permute, 2=+no CPU busy-wait +# (turbo deepep + grouped mlp), 3=fully sync-free (+fused act). Stage >=2 needs +# use_turbo_grouped_gemm=True. Auto-enables the required sub-flags. Default 0. +export TURBO_SYNC_FREE_MOE_STAGE=${TURBO_SYNC_FREE_MOE_STAGE:-0} +# Phase 1b: route the dense/attention projections (q_down/kv/o_a etc.) through +# Primus-Turbo linears so they pick up the mxfp8 (CK) path under the fp8 context. +# Default OFF (attention stays bf16, the validated baseline). Set True to enable +# fp8 attention/dense projections. Requires TP=1 and fp8_recipe in {tensorwise, +# blockwise,mxfp8}; the MLA monkey-patch is auto-skipped for V4 (see mla_patches). +export USE_TURBO_PARALLEL_LINEAR=${USE_TURBO_PARALLEL_LINEAR:-False} +# Per-module recipe (paper): routed experts in MXFP4 while the rest of the layer +# stays FP8. Works under the global FP8 recipe (no --fp4/--fp8 conflict): the +# PrimusTurbo grouped MLP routes expert GEMMs through native FP4 (hipBLASLt). +# Default OFF. When on, force the hipBLASLt FP4 backend (no AITER). +export MOE_EXPERTS_FP4=${MOE_EXPERTS_FP4:-False} +if [ "$MOE_EXPERTS_FP4" = "True" ]; then + export PRIMUS_TURBO_GEMM_BACKEND=${PRIMUS_TURBO_GEMM_BACKEND:-FP4:HIPBLASLT} +fi +# Phase 5 (paper): CSA-indexer QK score in FP4. Rounds q_i and K^{IComp} to +# MXFP4 before the QK product (STE backward); w_i + ReLU/sum tail stay BF16. +# Read directly by the Indexer via PRIMUS_INDEXER_FP4. Default OFF. +export INDEXER_FP4=${INDEXER_FP4:-False} +if [ "$INDEXER_FP4" = "True" ]; then + export PRIMUS_INDEXER_FP4=1 + # The indexer QK now runs a REAL MXFP4 gemm (pt.ops.gemm_fp4) — force the + # hipBLASLt FP4 backend (no AITER), same as the MXFP4 expert path. + export PRIMUS_TURBO_GEMM_BACKEND=${PRIMUS_TURBO_GEMM_BACKEND:-FP4:HIPBLASLT} +fi +# MXFP8 expert-weight caching: expert weights are constant within an optimizer +# step, so re-quantizing them every microbatch + recompute forward (the large +# _mxfp8_quant_weight_fwd kernel) is redundant. When on, PrimusTurboGroupedMLP +# prequantizes once per step and reuses the fp8 buffers (loss-neutral, faster). +# Costs extra bytes/param resident — watch HBM at depth. Only affects the +# mxfp8 (MX_BLOCKWISE) grouped path. Default OFF. +export CACHE_MXFP8_WEIGHT=${CACHE_MXFP8_WEIGHT:-False} +if [ "$CACHE_MXFP8_WEIGHT" = "True" ]; then + export PRIMUS_TURBO_CACHE_MXFP8_WEIGHT=1 +fi +# FP8 attention projections (paper recipe): route q-up / o-proj through the fp8 +# turbo linear instead of the bf16 gather/scatter native path. Only valid at +# TP=1 (gather/scatter are no-ops there); for TP>1 the turbo linear rejects +# gather_output/scatter-input and it stays bf16. Default OFF. +export V4_FP8_ATTN_PROJ=${V4_FP8_ATTN_PROJ:-False} +if [ "$V4_FP8_ATTN_PROJ" = "True" ]; then + export PRIMUS_V4_FP8_ATTN_PROJ=1 +fi +export USE_V4_ATTENTION_BACKEND=${USE_V4_ATTENTION_BACKEND:-triton_v2} +export USE_V4_CSA_ATTENTION_BACKEND=${USE_V4_CSA_ATTENTION_BACKEND:-triton_v2} +export USE_V4_COMPILED_SINKHORN=${USE_V4_COMPILED_SINKHORN:-False} +export PRIMUS_V4_GROUPED_EXPERTS_SUPPORT_CLAMPED_SWIGLU=${PRIMUS_V4_GROUPED_EXPERTS_SUPPORT_CLAMPED_SWIGLU:-True} + +# ---------- FP8 training (paper §4.x quantization / techblog §9.6) ---------- +# Paper recipe: "FP4 + FP8 Mixed" — MoE experts + CSA-Indexer QK in FP4 (MXFP4), +# EVERYTHING ELSE in FP8, all with the **ue8m0** microscaling scale format. +# On this stack the ue8m0 path is `fp8_recipe=mxfp8` → TE MXFP8BlockScaling → +# Primus-Turbo MX_BLOCKWISE granularity with scale_dtype=E8M0 (fp8_utils.py:148), +# i.e. the paper's exact scaling format, native on MI355X/CDNA4. +# +# Integration gap vs the paper (NOT config — Primus V4 TODO, develop techblog +# item 10 "Phase 2 FP4/FP8 Mixed"): the FP4 expert / FP4-Indexer path is not yet +# wired in V4, so experts run at FP8 here (FP8 everywhere) rather than FP4. This +# is the closest supported step toward the paper recipe. FP8 is highly outlier- +# sensitive, which is why the paper pairs it with clamped SwiGLU (swiglu_limit, +# already on above via PRIMUS_V4_GROUPED_EXPERTS_SUPPORT_CLAMPED_SWIGLU=True). +# Precision toggle — shared interface with run_deepseek_v4.sh / the flash proxy: +# PRECISION_TYPE=FP8 (default) -> e4m3 + tensorwise; BF16 -> fp8 off. +# FP8 / FP8_RECIPE still override directly (e.g. FP8_RECIPE=blockwise, or FP8=null). +export PRECISION_TYPE=${PRECISION_TYPE:-FP8} +if [ "$PRECISION_TYPE" = "FP8" ]; then + export FP8=${FP8:-e4m3} # forward fp8 format (paper E4M3); "hybrid" = E4M3 fwd / E5M2 bwd + # Paper recipe is ue8m0 microscaling (mxfp8), but mxfp8 is NOT runnable on this + # gfx950 build (turbo grouped-GEMM has no MX path; TE ROCm MXFP8 needs K%128==0, + # V4 has a K=224 proj). `tensorwise` is the working recipe — paper fp8 layout, + # non-ue8m0 scale. (other: blockwise [TE-ROCm unsupported] / delayed) + export FP8_RECIPE=${FP8_RECIPE:-tensorwise} + # mxfp8 (paper ue8m0) + Muon: TRAINS, but shows a transient early-training + # grad-norm spike. Earlier 8-iter runs only caught the spike and mislabelled it + # a divergence; a 40-iter run shows it SELF-HEALS and the loss descends cleanly. + # What's happening: mxfp8 quant noise ill-conditions the gradient early (random + # init), so Muon's Newton-Schulz update norm spikes for ~10-20 iters, then + # settles as the model organizes. RAW grads stay ~0.99 throughout; only the + # NS-orthogonalized UPDATE norm spikes (Muon-specific: Adam@same-config is flat). + # It is NOT a kernel bug (both MX GEMMs ~4% correct on REAL E2E inputs via + # capture-replay; error zero-mean). + # FIX (verified, L4/64-expert/GBS512/seq128, 40 iters): + # no warmup -> loss 12->0.80, grad-norm peak ~2.4e5 (settles to ~35) + # warmup=10 -> loss 12->0.44, grad-norm peak ~2.1e4 (12x lower), no NaN + # So LR warmup tames the transient AND improves the loss — and it is what the + # paper does. We therefore AUTO-ENABLE warmup for mxfp8 (default 10; override + # LR_WARMUP_ITERS). Two more NS-hardening knobs are exposed if needed: + # MUON_NUM_NS_STEPS (more iters) and MUON_FP32_MATMUL_PREC=high. tensorwise + # stays the conservative default (smooth per-tensor scale, no transient). + # NOTE: validated at reduced depth/width; full 61-layer/384-expert is a + # separate multi-GPU confirmation. + if [ "$FP8_RECIPE" = "mxfp8" ]; then + export LR_WARMUP_ITERS=${LR_WARMUP_ITERS:-10} + echo "[INFO] FP8_RECIPE=mxfp8 + Muon: expect a transient early grad-norm spike" >&2 + echo " (self-heals; LR warmup auto-set to ${LR_WARMUP_ITERS} to damp it)." >&2 + fi +else + export FP8=${FP8:-null} # PRECISION_TYPE=BF16 -> disable fp8 + export FP8_RECIPE=${FP8_RECIPE:-null} +fi + +TURBO_DEEPEP_CLI_ARGS=() +if [ "$USE_TURBO_DEEPEP" = "True" ]; then + export TURBO_DEEPEP_NUM_CU=${TURBO_DEEPEP_NUM_CU:-80} + export TURBO_DEEPEP_USE_COMM_STREAM=${TURBO_DEEPEP_USE_COMM_STREAM:-False} + export MOE_ROUTER_DTYPE=${MOE_ROUTER_DTYPE:-fp32} + export MOE_SHARED_EXPERT_OVERLAP=${MOE_SHARED_EXPERT_OVERLAP:-False} + TURBO_DEEPEP_CLI_ARGS=( + --turbo_deepep_num_cu "$TURBO_DEEPEP_NUM_CU" + --turbo_deepep_use_comm_stream "$TURBO_DEEPEP_USE_COMM_STREAM" + --moe_router_dtype "$MOE_ROUTER_DTYPE" + --moe_shared_expert_overlap "$MOE_SHARED_EXPERT_OVERLAP" + ) +fi + +export PROFILE=${PROFILE:-False} +# Profiler writes the chrome trace via tensorboard_trace_handler(args.tensorboard_dir), +# so tensorboard must be enabled for a trace run. Default True (smoke); set +# DISABLE_TENSORBOARD=False together with PROFILE=True to capture a trace. +export DISABLE_TENSORBOARD=${DISABLE_TENSORBOARD:-True} +export BACKEND_PATH=${BACKEND_PATH:-"$(pwd)/third_party/Megatron-LM"} +export PRIMUS_TEAM=${PRIMUS_TEAM:-amd} +export PRIMUS_USER=${PRIMUS_USER:-tas-mi355x-$(date +%Y%m%d)} +export PRIMUS_EXP_NAME=${PRIMUS_EXP_NAME:-deepseek_v4_pro_muon_L${PRIMUS_TOTAL_LAYERS}_seq${PRIMUS_SEQ_LENGTH}_ep${PRIMUS_EP}} + +if [ ! -d "$BACKEND_PATH" ] || [ -z "$(ls -A "$BACKEND_PATH" 2>/dev/null)" ]; then + echo "[ERROR] BACKEND_PATH does not exist or is empty: $BACKEND_PATH" + echo "Run: git submodule update --init --recursive" + exit 1 +fi + +mkdir -p "output/$PRIMUS_TEAM/$PRIMUS_USER/$PRIMUS_EXP_NAME" + +./primus-cli direct \ + -- train pretrain --config "$EXP" \ + --backend_path "$BACKEND_PATH" \ + --manual_gc True \ + --manual_gc_interval 100 \ + --num_layers "$PRIMUS_TOTAL_LAYERS" \ + --train_iters "$TRAIN_ITERS" \ + --lr_warmup_iters "${LR_WARMUP_ITERS:-0}" \ + --lr_decay_iters "$TRAIN_ITERS" \ + --micro_batch_size "$MBS" \ + --global_batch_size "$GBS" \ + --lr "$LR" \ + --min_lr "$MIN_LR" \ + --adam_eps "$ADAM_EPS" \ + --moe_aux_loss_coeff "$MOE_AUX_LOSS_COEFF" \ + --seq_length "$PRIMUS_SEQ_LENGTH" \ + --max_position_embeddings "$PRIMUS_MAX_POSITION_EMBEDDINGS" \ + --rope_type rope \ + --tensor_model_parallel_size "$PRIMUS_TP" \ + --pipeline_model_parallel_size "$PRIMUS_PP" \ + --expert_model_parallel_size "$PRIMUS_EP" \ + --num_experts "$PRIMUS_NUM_EXPERTS" \ + --moe_router_topk "$PRIMUS_MOE_TOPK" \ + --moe_router_force_load_balancing "${MOE_FORCE_LOAD_BALANCE:-False}" \ + --moe_router_enable_expert_bias "$PRIMUS_MOE_ENABLE_EXPERT_BIAS" \ + --moe_ffn_hidden_size "$PRIMUS_MOE_FFN_HIDDEN_SIZE" \ + --index_topk "$PRIMUS_INDEX_TOPK" \ + --v4_grouped_experts_support_clamped_swiglu "$PRIMUS_V4_GROUPED_EXPERTS_SUPPORT_CLAMPED_SWIGLU" \ + --compress_ratios "$PRIMUS_COMPRESS_RATIOS" \ + --mtp_num_layers "$MTP_NUM_LAYERS" \ + --mock_data True \ + --optimizer "$OPTIMIZER" \ + --muon_momentum "$MUON_MOMENTUM" \ + --muon_extra_scale_factor "$MUON_EXTRA_SCALE_FACTOR" \ + --muon_num_ns_steps "$MUON_NUM_NS_STEPS" \ + --muon_fp32_matmul_prec "$MUON_FP32_MATMUL_PREC" \ + --use_distributed_optimizer "$USE_DISTRIBUTED_OPTIMIZER" \ + --use_precision_aware_optimizer "$USE_PRECISION_AWARE_OPTIMIZER" \ + --main_grads_dtype fp32 \ + --exp_avg_dtype fp32 \ + --exp_avg_sq_dtype fp32 \ + --enable_primus_turbo "$ENABLE_PRIMUS_TURBO" \ + --use_turbo_attention "$USE_TURBO_ATTENTION" \ + --use_v4_attention_backend "$USE_V4_ATTENTION_BACKEND" \ + --use_v4_csa_attention_backend "$USE_V4_CSA_ATTENTION_BACKEND" \ + --use_v4_compiled_sinkhorn "$USE_V4_COMPILED_SINKHORN" \ + --use_turbo_deepep "$USE_TURBO_DEEPEP" \ + --turbo_sync_free_moe_stage "$TURBO_SYNC_FREE_MOE_STAGE" \ + "${TURBO_DEEPEP_CLI_ARGS[@]}" \ + --use_turbo_grouped_gemm "$TURBO_USE_GROUPED_MLP" \ + --use_turbo_gemm "$USE_TURBO_PARALLEL_LINEAR" \ + --moe_experts_fp4 "$MOE_EXPERTS_FP4" \ + --moe_use_legacy_grouped_gemm False \ + --fp8 "$FP8" \ + --fp8_recipe "$FP8_RECIPE" \ + --recompute_num_layers 1 \ + --recompute_granularity full \ + --recompute_method uniform \ + --overlap_grad_reduce False \ + --overlap_param_gather False \ + --disable_last_saving True \ + --disable_wandb True \ + --disable_tensorboard "$DISABLE_TENSORBOARD" \ + --profile "$PROFILE" \ + --use_pytorch_profiler "$PROFILE" \ + --profile_step_end 7 \ + --profile_step_start 6 \ + 2>&1 | tee "output/$PRIMUS_TEAM/$PRIMUS_USER/$PRIMUS_EXP_NAME/log_node_${NODE_RANK:-0}.txt" diff --git a/examples/deepseek-v4/run_deepseek_v4_pro_muon_1gpu.sh b/examples/deepseek-v4/run_deepseek_v4_pro_muon_1gpu.sh new file mode 100755 index 000000000..d30f05069 --- /dev/null +++ b/examples/deepseek-v4/run_deepseek_v4_pro_muon_1gpu.sh @@ -0,0 +1,497 @@ +#!/bin/bash +############################################################################### +# DeepSeek-V4 *Pro* + Muon single-GPU bring-up on mi455 / gfx1250 (1 GPU). +# +# Single-GPU sibling of run_deepseek_v4_flash_proxy_1gpu.sh, for the Pro model. +# The upstream run_deepseek_v4_pro_muon.sh uses `primus-cli direct`, which +# assumes it is ALREADY inside the 8x288GB MI355X container at EP=8. This host +# is one gfx1250 box with no SLURM and one GPU, so this script instead wraps +# the SAME examples/run_pretrain.sh entrypoint in a local `docker run` (the +# validated gfx1250 docker + TransformerEngine recipe from +# ../../mi450/Primus/run_dsv3_proxy_4L.sh), selects the Pro model via +# PRIMUS_MODEL, and scales it down to a MINIMUM single-GPU proxy: +# +# - model deepseek_v4_pro hidden 7168 / 128 heads / head_dim +# 512 / o_groups 16 (full Pro widths +# from deepseek_v4_pro.yaml) +# - parallel TP=1 PP=1 EP=1 (single GPU; no SLURM, no DeepEP) +# - num_layers 4 (vs production 61; MINIMUM slice +# that still exercises every V4 +# attention layer kind) +# - compress_ratios [128,128,4,0] Pro pattern (first two HCA, then +# CSA, last dense+SWA) -> covers +# HCA cr=128 / CSA cr=4 / dense cr=0 +# - num_experts 48 topk 1 (production 384/topk6 div by 8 = +# the per-rank shape of the EP=8 +# production run; topk ceil(6/8)=1. +# ~48 experts x 66M x 4L + Muon fp32 +# states + seq-4096 activations -> 273 +# GB peak (measured), fits the 432 GiB +# card; full 384 will NOT fit.) +# - moe_ffn_hidden 3072 (full Pro MoE width; from yaml) +# - seq_length 4096 (raised from 512: at GBS=8 the fixed +# Muon Newton-Schulz cost otherwise +# dominates GPU time; 4096 +# tokens/microbatch amortizes it to a +# representative profile. CSA gather +# scales w/ seq. Set 512 for a fast smoke.) +# - index_topk 64 (CSA top-K; <= cr=4 pool seq/4, i.e. +# 4096/4=1024 at the default seq) +# - precision FP8 e4m3 + tensorwise (paper fp8 LAYOUT, per-tensor scale; +# mxfp8/ue8m0 is GUARDED off — diverges +# on this build. CK-free TE path. +# PRECISION_TYPE=BF16 for a BF16 A/B.) +# +# Optimizer: Muon (paper §4.2.2), same recipe as the upstream pro_muon runner: +# momentum 0.95, update-RMS scale 0.18, AdamW eps 1e-20, LR 2.0e-4->2.0e-5, +# balance-loss 1e-4. Muon hard-requires use_distributed_optimizer=False + +# use_precision_aware_optimizer=False (so optimizer states stay fp32 — this +# is the binding memory cost, NOT activations). Set OPTIMIZER=adam to A/B. +# NOTE: at the tiny single-GPU GBS the Newton-Schulz cost looks huge as a % +# of GEMM (starved-batch artifact, not a Muon bug); raise GBS to amortize. +# +# Correctness-first defaults (this is a "does V4-Pro train at all on 1 gfx1250 +# GPU" bring-up, not a perf push). Eager attention; Turbo/DeepEP/tilelang/ +# compiled-Sinkhorn/plan-6 Triton fusions all OFF; stock hipBLASLt; profiler +# OFF. Every knob is ${VAR:-DEFAULT}-guarded for command-line A/B. +# +# REQUIRED gfx1250 fix kept ON (single-GPU-safe): RCCL all_reduce(op=AVG) hangs +# even at world_size=1 on this build and Megatron's MoE aux-loss reduce uses +# AVG; sitecustomize on PYTHONPATH rewrites AVG -> SUM/world_size. See +# rccl_avg_workaround/sitecustomize.py. +# +# Usage: +# ./run_deepseek_v4_pro_muon_1gpu.sh # 10-iter smoke +# OPTIMIZER=adam ./run_deepseek_v4_pro_muon_1gpu.sh # A/B vs AdamW +# PRIMUS_TOTAL_LAYERS=6 ./run_deepseek_v4_pro_muon_1gpu.sh # deeper slice (watch HBM) +# PRIMUS_NUM_EXPERTS=8 PRIMUS_MOE_TOPK=2 ./run_deepseek_v4_pro_muon_1gpu.sh # tiny MoE +# HIP_VISIBLE_DEVICES=3 ./run_deepseek_v4_pro_muon_1gpu.sh # pin a card +############################################################################### +set -eo pipefail + +export DOCKER_IMAGE=${DOCKER_IMAGE:-registry-sc-harbor.amd.com/framework/therock-npi@sha256:feba897e2a32a2465b8b296ed2662b2ad6136b5f1cf6f6c2716a3674aafc30f3} +# Repo root: this script lives under examples/deepseek-v4/, so resolve two levels +# up. All paths below (TE_DIR, rccl_avg_workaround, PRIMUS_PATH) are repo-root-relative. +SCRIPT_DIR=$(realpath -m "$(dirname "$0")/../..") +export TE_DIR=${TE_DIR:-$(realpath -m "$SCRIPT_DIR/../../mi450/TransformerEngine")} +export TE_WHEEL_DIR=${TE_WHEEL_DIR:-$(realpath -m "$SCRIPT_DIR/../../mi450/dist/feba897")} + +# ---------- Attention backend env (TE side) -------------------------------- +export NVTE_FUSED_ATTN=1 +export NVTE_FUSED_ATTN_CK=0 +export NVTE_FUSED_ATTN_AOTRITON=1 +export NVTE_USE_CK_GEMM=0 +export NVTE_FLASH_ATTN=0 + +# ---------- hipBLASLt: STOCK by default; opt-in TUNED (PRIMUS_TUNED_HIPBLASLT=1) - +# Stock is the safe default: the older feba897 tuned bundle DEADLOCKED on a +# backward-FP8 GSU split-K kernel on this host (GPU wedge -> node reboot, +# 2026-06-10). PRIMUS_TUNED_HIPBLASLT=1 opts into a freshly built GridBased +# gfx1250 tuned library (qwen3/dsv3 tuned; swept clean on dsv4 fwd shapes) via +# LD_PRELOAD + HIPBLASLT_TENSILE_LIBPATH. This is a GUARDED EXPERIMENT: run with +# a watchdog and expect a possible node reboot if the backward path still wedges. +# NOTE: never export an EMPTY HIPBLASLT_TENSILE_LIBPATH into the container — a +# missing path breaks even stock hipBLASLt ("Cannot read TensileLibrary..."). +export PRIMUS_TUNED_HIPBLASLT=${PRIMUS_TUNED_HIPBLASLT:-0} +export HBL_TUNED_RELEASE=${HBL_TUNED_RELEASE:-/home/yanyuqin/hipblaslt/rocm-libraries/projects/hipblaslt/build/release} +if [ "$PRIMUS_TUNED_HIPBLASLT" = "1" ]; then + if [ ! -f "$HBL_TUNED_RELEASE/library/libhipblaslt.so.1" ]; then + echo "[hipblaslt] ERROR: tuned lib not found at $HBL_TUNED_RELEASE/library/libhipblaslt.so.1" >&2 + exit 1 + fi + # LD_PRELOAD / LD_LIBRARY_PATH are injected INSIDE the container (below) so the + # image's own rocm/torch lib paths are preserved (prepend, not override). + export HIPBLASLT_TENSILE_LIBPATH="$HBL_TUNED_RELEASE/Tensile/library/gfx1250" + echo "[hipblaslt] TUNED (opt-in): libpath=$HIPBLASLT_TENSILE_LIBPATH, LD_PRELOAD=libhipblaslt.so.1 (GUARDED: watch for wedge)" +else + unset HIPBLASLT_DIR HIPBLASLT_LD_PRELOAD HIPBLASLT_TENSILE_LIBPATH + echo "[hipblaslt] STOCK (container built-in gfx1250 catalog)" +fi + +# ---------- REQUIRED gfx1250 RCCL AVG->SUM workaround ----------------------- +export PYTHONPATH="$SCRIPT_DIR/examples/deepseek-v4/rccl_avg_workaround:${PYTHONPATH:-}" +# Real primus_turbo imports flydsl at import time; put FLYDSL_PKG_DIR on PYTHONPATH. +export FLYDSL_PKG_DIR=${FLYDSL_PKG_DIR:-} +if [ -n "$FLYDSL_PKG_DIR" ] && [ -d "$FLYDSL_PKG_DIR/flydsl" ]; then + export PYTHONPATH="$FLYDSL_PKG_DIR:$PYTHONPATH" +fi + +# SDMA OFF on this host (run 3 debugging, 2026-06-10): an SDMA H2D copy +# intermittently never signals completion — py-spy --native showed the trainer +# pinned in rocr BusyWaitSignal under a trivial `torch.tensor(n, device=dev)` +# in Megatron get_batch, GPU 0%, dmesg clean. Killing the stuck proc then +# leaves MES unrecoverable (recovery disabled) -> node reboot. Blit-kernel +# copies are slower but don't use the flaky SDMA queues. This may ALSO be the +# true cause of the run-1 "permute autotune wedge" (same stuck-queue +# signature; permute fusion possibly innocent). +export HSA_ENABLE_SDMA=${HSA_ENABLE_SDMA:-0} + +# ---------- Distributed / NCCL: single GPU, loopback only ------------------- +export HSA_NO_SCRATCH_RECLAIM=1 +export NCCL_IB_DISABLE=1 +export NCCL_P2P_DISABLE=1 +export NCCL_IB_HCA= +export NCCL_SOCKET_IFNAME=lo +export GLOO_SOCKET_IFNAME=lo +export RCCL_DISABLE_AMDSMI=1 +export NCCL_AMDSMI_DISABLE=1 +export USING_AINIC=0 + +export GPUS_PER_NODE=1 +export NNODES=1 +export PYTHONUNBUFFERED=1 + +# REQUIRED gfx1250 workaround for V4-Pro (default ON). The Pro model build +# (hidden_size 7168, NOT a multiple of 4096) leaves a memory layout that wedges +# the process's first high-priority MES queue creation at iter-1 get_batch +# -> deadlock -> node reboot (debugged 2026-06-11; root cause = MES queue +# creation vs non-4096-aligned allocation layout). AMD_SERIALIZE_COPY=3 alone +# prevents it (kernels stay async; small iter-time cost on the eager +# proxy). Bisected: KERNEL serialize + LAUNCH_BLOCKING are NOT needed, so they +# default off. Set AMD_SERIALIZE_COPY=0 only to re-demonstrate the hang. +export AMD_SERIALIZE_COPY=${AMD_SERIALIZE_COPY:-3} +export AMD_SERIALIZE_KERNEL=${AMD_SERIALIZE_KERNEL:-0} +export HIP_LAUNCH_BLOCKING=${HIP_LAUNCH_BLOCKING:-0} + +# ---------- Model: DeepSeek-V4 Pro (selected via the EXP yaml model: line) -- +export PRIMUS_MODEL=${PRIMUS_MODEL:-deepseek_v4_pro} + +# ---------- Pro MINIMUM single-GPU proxy shape ------------------------------ +export PRIMUS_TP=${PRIMUS_TP:-1} +export PRIMUS_PP=${PRIMUS_PP:-1} +export PRIMUS_EP=${PRIMUS_EP:-1} +# 3 layers (down from 4): the 4-layer model sits near the 432GB gfx1250's +# capacity and OOMs at iter 2 (Muon keeps fp32 optimizer states). Dropping +# the last layer frees the headroom for a warm step. +export PRIMUS_TOTAL_LAYERS=${PRIMUS_TOTAL_LAYERS:-3} +export PRIMUS_NUM_EXPERTS=${PRIMUS_NUM_EXPERTS:-48} +export PRIMUS_MOE_TOPK=${PRIMUS_MOE_TOPK:-1} +export PRIMUS_MOE_FFN_HIDDEN_SIZE=${PRIMUS_MOE_FFN_HIDDEN_SIZE:-3072} +export PRIMUS_INDEX_TOPK=${PRIMUS_INDEX_TOPK:-64} +export PRIMUS_SEQ_LENGTH=${PRIMUS_SEQ_LENGTH:-4096} +export PRIMUS_MAX_POSITION_EMBEDDINGS=${PRIMUS_MAX_POSITION_EMBEDDINGS:-${PRIMUS_SEQ_LENGTH}} +export MBS=${MBS:-1} +export GBS=${GBS:-8} +export TRAIN_ITERS=${TRAIN_ITERS:-10} + +# Per-layer compression schedule, length == PRIMUS_TOTAL_LAYERS, mirroring the +# Pro pattern (first two HCA(128), then CSA(4)/HCA(128) interleaved, last +# dense+SWA(0)). Pure-bash generator (no host python needed). +gen_pro_compress_ratios() { + local n=$1 i r=() + for ((i = 0; i < n; i++)); do + if (( i < 2 )); then r+=(128) + elif (( i == n-1 )); then r+=(0) + elif (( i % 2 == 0)); then r+=(4) + else r+=(128) + fi + done + local IFS=, + echo "[${r[*]}]" +} +export PRIMUS_COMPRESS_RATIOS=${PRIMUS_COMPRESS_RATIOS:-$(gen_pro_compress_ratios "$PRIMUS_TOTAL_LAYERS")} + +# ---------- Optimizer: Muon (paper §4.2.2) --------------------------------- +export OPTIMIZER=${OPTIMIZER:-muon} +export MUON_MOMENTUM=${MUON_MOMENTUM:-0.95} +export MUON_EXTRA_SCALE_FACTOR=${MUON_EXTRA_SCALE_FACTOR:-0.18} +export USE_DISTRIBUTED_OPTIMIZER=${USE_DISTRIBUTED_OPTIMIZER:-False} +export USE_PRECISION_AWARE_OPTIMIZER=${USE_PRECISION_AWARE_OPTIMIZER:-False} +export LR=${LR:-2.0e-4} +export MIN_LR=${MIN_LR:-2.0e-5} +export ADAM_EPS=${ADAM_EPS:-1.0e-20} # needs a decimal point — Primus parses "1e-20" as a string +export MOE_AUX_LOSS_COEFF=${MOE_AUX_LOSS_COEFF:-0.0001} +export MTP_NUM_LAYERS=${MTP_NUM_LAYERS:-0} # V4 MTP layer not yet supported in-tree +# Pro uses sqrtsoftplus; Megatron only supports aux-loss-free expert bias with +# sigmoid, so disable expert bias (balancing falls back to seq_aux_loss). +export PRIMUS_MOE_ENABLE_EXPERT_BIAS=${PRIMUS_MOE_ENABLE_EXPERT_BIAS:-False} + +# Muon needs fp32 optimizer states (precision-aware off forces this anyway). +OPT_DTYPE_ARGS="--main_grads_dtype fp32 --exp_avg_dtype fp32 --exp_avg_sq_dtype fp32" + +# ---------- Perf knobs: V4 attention backends ON; turbo paths OFF ---------- +# V4 attention backend (replaces the unfused/eager path). Covers the dense + +# HCA layers (compress_ratio in {0, 128}) via USE_V4_ATTENTION_BACKEND and the +# CSA layers (compress_ratio == 4) via USE_V4_CSA_ATTENTION_BACKEND. +# Validated on gfx1250 after the WMMA tile-floor fix (06ae5214). +export USE_V4_ATTENTION_BACKEND=${USE_V4_ATTENTION_BACKEND:-triton_v2} +export USE_V4_CSA_ATTENTION_BACKEND=${USE_V4_CSA_ATTENTION_BACKEND:-triton_v2} +export USE_TURBO_ATTENTION=${USE_TURBO_ATTENTION:-False} +export USE_TURBO_DEEPEP=${USE_TURBO_DEEPEP:-False} +export TURBO_USE_GROUPED_MLP=${TURBO_USE_GROUPED_MLP:-False} +# Projections: the FP8 yaml sets use_turbo_gemm=true (PrimusTurboLinear); +# turbo-free here -> TELinear. Override off so no turbo GEMM is invoked. +export USE_TURBO_PARALLEL_LINEAR=${USE_TURBO_PARALLEL_LINEAR:-False} +export USE_V4_COMPILED_SINKHORN=${USE_V4_COMPILED_SINKHORN:-False} +export PRIMUS_STACK_GROUPED_WEIGHT_TRITON=${PRIMUS_STACK_GROUPED_WEIGHT_TRITON:-0} +# RoPE Triton: default ON. Trace (2026-06-25, L3) attributed 960 kernels / 513 tiny +# (<5us) / 38.6 ms to the eager rotary-embedding path — a launch-bound fusion target. +export PRIMUS_ROPE_TRITON=${PRIMUS_ROPE_TRITON:-1} +# Sinkhorn Triton fused FWD/BWD: default ON. The eager Sinkhorn-Knopp loop +# (n_iters=20) launches ~18,600 tiny sum/add/div kernels per step (5,616 on the +# fwd side alone); the Triton path emits exactly 1 fwd + 1 bwd kernel per call. +# Measured 2026-06-25 (0612, L3, FP8): total GPU events 80,962 -> 62,340, sinkhorn +# GPU kernels 5,616 -> 48, warm step ~2,890 -> ~2,797 ms (+3.2%), 0 NaN / loss +# bit-identical. Falls back to eager when the shape/device is unsupported. Set =0 to A/B. +export PRIMUS_SINKHORN_TRITON=${PRIMUS_SINKHORN_TRITON:-1} +# HyperConnection mHC Triton: default ON. The mHC HyperMixer glue (pre/post/comb +# projections + scales), separate from the already-fused HC-expand and sinkhorn. +# Trace (2026-06-25, L3): 1,320 kernels / 872 tiny (<5us) / 52 ms — top remaining +# launch-bound target after sinkhorn. +export PRIMUS_HC_TRITON=${PRIMUS_HC_TRITON:-1} +# CSA indexer Triton: kept OFF — inert at L3 (compress_ratios [128,128,0] has NO CSA +# layer, so the indexer never runs). Enable only with a CSA layer (>=4 layers). +export PRIMUS_INDEXER_TRITON=${PRIMUS_INDEXER_TRITON:-0} +export PRIMUS_INDEXER_TRITON_FULL=${PRIMUS_INDEXER_TRITON_FULL:-0} +# V4 MoE router Triton: default ON. Trace (2026-06-25, L3): 432 kernels / 208 tiny / +# 5.3 ms — marginal, but launch-bound and correctness-neutral. +export PRIMUS_V4_ROUTER_TRITON=${PRIMUS_V4_ROUTER_TRITON:-1} + +export ENABLE_PRIMUS_TURBO=False +if [ "$USE_TURBO_ATTENTION" = "True" ] || [ "$USE_TURBO_DEEPEP" = "True" ] || [ "$TURBO_USE_GROUPED_MLP" = "True" ]; then + ENABLE_PRIMUS_TURBO=True +fi + +# MoE permute fusion OFF for Pro on gfx1250: the Triton permute_with_mask_map +# BACKWARD autotune wedges the GPU stream at Pro shapes (48 experts / hidden +# 7168) — cuda.synchronize inside triton do_bench never returns (debugged +# 2026-06-10 via py-spy; flash shapes 32 experts / hidden 4096 autotune fine). +# Eager permute is the safe path; flip =True to retry after a triton fix. +export MOE_PERMUTE_FUSION=${MOE_PERMUTE_FUSION:-False} + +export PROFILE=${PROFILE:-False} +# PyTorch profiler writes the chrome trace via tensorboard_trace_handler( +# args.tensorboard_dir), so the tensorboard dir MUST be enabled to get a trace. +# Default tensorboard off, but auto-enable it whenever PROFILE=True so a +# profiled run actually produces a trace. profile window = steps [START,END); +# need TRAIN_ITERS > PROFILE_STEP_END. +export DISABLE_TENSORBOARD=${DISABLE_TENSORBOARD:-True} +if [ "$PROFILE" = "True" ]; then export DISABLE_TENSORBOARD=False; fi +export PROFILE_STEP_START=${PROFILE_STEP_START:-6} +export PROFILE_STEP_END=${PROFILE_STEP_END:-7} +export PRIMUS_TEAM=${PRIMUS_TEAM:-amd} +export PRIMUS_USER=${PRIMUS_USER:-gfx1250-1gpu} +export PRIMUS_EXP_NAME=${PRIMUS_EXP_NAME:-deepseek_v4_pro_muon_1gpu_L${PRIMUS_TOTAL_LAYERS}_E${PRIMUS_NUM_EXPERTS}_seq${PRIMUS_SEQ_LENGTH}} + +PRIMUS_PATH="$SCRIPT_DIR" +DATA_PATH="${PRIMUS_PATH}/data" +mkdir -p "$DATA_PATH" + +EXP=${EXP:-examples/megatron/configs/MI355X/deepseek_v4_flash-FP8-pretrain.yaml} +LOG=${LOG:-deepseek-v4-pro-muon-1gpu.log} + +# ---------- FP8 training (matches upstream run_deepseek_v4_pro_fp8_paper.sh) -- +# PRECISION_TYPE=FP8 (default) -> FP8=e4m3, FP8_RECIPE=tensorwise. This is the +# paper's fp8 LAYOUT (all weight GEMMs in fp8: MoE expert GEMMs via TEGroupedMLP, +# attention QKV/O + dense proj via TELinear; attention core QK^T/softmax*V stays +# BF16; mHC/Sinkhorn fp32; embedding/head/RMSNorm/router BF16; optimizer fp32) — +# but with a per-TENSOR scale instead of the paper's ue8m0 microscale. +# +# CK-free by construction: this 1gpu launcher already has TURBO_USE_GROUPED_MLP= +# False + USE_TURBO_DEEPEP=False, so experts route to TEGroupedMLP (hipBLASLt), +# not PrimusTurboGroupedMLP (ck_grouped_gemm). NVTE_ROCM_ENABLE_MXFP8=1 is set +# by examples/run_pretrain.sh. +# +# WHY NOT mxfp8 (paper ue8m0): two blockers on this build, both upstream- +# root-caused. (1) TE-ROCm MXFP8 asserts GEMM K % 128 == 0 (rocm_gemm.hip:1529) +# and V4 has non-128 K dims (e.g. K=224, K=32) -> errors out. (2) Even if it ran, +# mxfp8's e8m0 per-block quant noise AMPLIFIES MULTIPLICATIVELY through backward +# depth -> divergence. tensorwise's smooth per-tensor fp32 scale is stable +# (upstream: loss 12 -> 0.82 at full depth). So mxfp8 is GUARDED below. +# +# The earlier FP8 no-op (decoder skipped the fp8 context) is fixed upstream +# (commit b662c40b) and lives in the mounted repo, so FP8 now actually engages. +# A/B back to BF16 with PRECISION_TYPE=BF16 (or FP8=null). +export NVTE_ROCM_ENABLE_MXFP8=${NVTE_ROCM_ENABLE_MXFP8:-1} +# TURBO-FREE FP8: primus_turbo is only an import-shim in this gfx1250 container, +# so the turbo FP8 path (PrimusTurboQuantConfig / primus_turbo_fp8_autocast) +# can't run. Force the TE-native fp8_autocast branch (fp8_utils.py honors this). +export PRIMUS_FP8_DISABLE_TURBO=${PRIMUS_FP8_DISABLE_TURBO:-1} +export PRECISION_TYPE=${PRECISION_TYPE:-FP8} +if [ "$PRECISION_TYPE" = "FP8" ]; then + export FP8=${FP8:-e4m3} + export FP8_RECIPE=${FP8_RECIPE:-tensorwise} + # GUARD: mxfp8 (paper ue8m0) diverges for V4 on this build. Refuse it unless + # explicitly forced, matching upstream run_deepseek_v4_pro_muon.sh. + if [ "$FP8_RECIPE" = "mxfp8" ] && [ "${MXFP8_I_KNOW_ITS_BROKEN:-0}" != "1" ]; then + echo "[FATAL] FP8_RECIPE=mxfp8 diverges for V4 on this build (TE K%128 assert +" >&2 + echo " e8m0 depth-amplified instability). Use FP8_RECIPE=tensorwise," >&2 + echo " or set MXFP8_I_KNOW_ITS_BROKEN=1 to force it anyway." >&2 + exit 1 + fi +else + export FP8=${FP8:-null} + export FP8_RECIPE=${FP8_RECIPE:-null} +fi + +if [ "$TURBO_USE_GROUPED_MLP" = "True" ]; then + export PRIMUS_BIAS_SWIGLU_FUSION=True +fi + +if [ ! -d "$PRIMUS_PATH/third_party/Megatron-LM" ] || \ + [ -z "$(ls -A "$PRIMUS_PATH/third_party/Megatron-LM" 2>/dev/null)" ]; then + echo "[ERROR] third_party/Megatron-LM missing/empty -> run: git submodule update --init --recursive" >&2 + exit 1 +fi + +echo "[pro] model=$PRIMUS_MODEL layers=$PRIMUS_TOTAL_LAYERS experts=$PRIMUS_NUM_EXPERTS seq=$PRIMUS_SEQ_LENGTH optimizer=$OPTIMIZER compress_ratios=$PRIMUS_COMPRESS_RATIOS" + +# V4-Pro single-GPU overrides (trailing args -> run_pretrain.sh -> primus cli +# train pretrain --config $EXP ...). Mirrors run_deepseek_v4_pro_muon.sh's CLI +# set, minus the DeepEP wiring, scaled to one GPU / minimum layers. +# overlap_grad_reduce/param_gather stay OFF: upstream enabled them for multi- +# node DP scaling (needs the distributed optimizer + the indexer-param freeze), +# but at single-GPU DP=1 they are no-ops, and Muon requires them off anyway. +PROXY_OVERRIDES="\ + --backend_path $PRIMUS_PATH/third_party/Megatron-LM \ + --train_iters $TRAIN_ITERS \ + --lr_warmup_iters 0 \ + --lr_decay_iters $TRAIN_ITERS \ + --num_layers $PRIMUS_TOTAL_LAYERS \ + --compress_ratios $PRIMUS_COMPRESS_RATIOS \ + --micro_batch_size $MBS \ + --global_batch_size $GBS \ + --lr $LR \ + --min_lr $MIN_LR \ + --adam_eps $ADAM_EPS \ + --moe_aux_loss_coeff $MOE_AUX_LOSS_COEFF \ + --seq_length $PRIMUS_SEQ_LENGTH \ + --max_position_embeddings $PRIMUS_MAX_POSITION_EMBEDDINGS \ + --rope_type rope \ + --tensor_model_parallel_size $PRIMUS_TP \ + --pipeline_model_parallel_size $PRIMUS_PP \ + --expert_model_parallel_size $PRIMUS_EP \ + --num_experts $PRIMUS_NUM_EXPERTS \ + --moe_router_topk $PRIMUS_MOE_TOPK \ + --moe_router_enable_expert_bias $PRIMUS_MOE_ENABLE_EXPERT_BIAS \ + --moe_ffn_hidden_size $PRIMUS_MOE_FFN_HIDDEN_SIZE \ + --index_topk $PRIMUS_INDEX_TOPK \ + --v4_grouped_experts_support_clamped_swiglu True \ + --mtp_num_layers $MTP_NUM_LAYERS \ + --mock_data True \ + --moe_router_force_load_balancing True \ + --log_avg_skip_iterations 3 \ + --optimizer $OPTIMIZER \ + --muon_momentum $MUON_MOMENTUM \ + --muon_extra_scale_factor $MUON_EXTRA_SCALE_FACTOR \ + --use_distributed_optimizer $USE_DISTRIBUTED_OPTIMIZER \ + --use_precision_aware_optimizer $USE_PRECISION_AWARE_OPTIMIZER \ + $OPT_DTYPE_ARGS \ + --enable_primus_turbo $ENABLE_PRIMUS_TURBO \ + --use_turbo_attention $USE_TURBO_ATTENTION \ + --use_turbo_deepep $USE_TURBO_DEEPEP \ + --use_turbo_grouped_gemm $TURBO_USE_GROUPED_MLP \ + --use_turbo_gemm $USE_TURBO_PARALLEL_LINEAR \ + --use_v4_attention_backend $USE_V4_ATTENTION_BACKEND \ + --use_v4_csa_attention_backend $USE_V4_CSA_ATTENTION_BACKEND \ + --use_v4_compiled_sinkhorn $USE_V4_COMPILED_SINKHORN \ + --moe_use_legacy_grouped_gemm False \ + --moe_permute_fusion $MOE_PERMUTE_FUSION \ + --fp8 $FP8 \ + --fp8_recipe $FP8_RECIPE \ + --recompute_num_layers 0 \ + --recompute_granularity full \ + --recompute_method block \ + --gradient_accumulation_fusion False \ + --overlap_grad_reduce False \ + --overlap_param_gather False \ + --disable_last_saving True \ + --disable_wandb True \ + --disable_tensorboard $DISABLE_TENSORBOARD \ + --profile $PROFILE \ + --use_pytorch_profiler $PROFILE \ + --profile_step_start $PROFILE_STEP_START \ + --profile_step_end $PROFILE_STEP_END \ + --bias_swiglu_fusion $PRIMUS_BIAS_SWIGLU_FUSION \ + --torch_profiler_use_gzip True" + +ENV_ARGS=() +for v in DOCKER_IMAGE NVTE_FUSED_ATTN NVTE_FUSED_ATTN_CK NVTE_FUSED_ATTN_AOTRITON \ + PRIMUS_TURBO_GEMM_BACKEND PRIMUS_TURBO_GROUPED_GEMM_BACKEND TURBO_WHEEL_DIR FLYDSL_PKG_DIR \ + NVTE_FLASH_ATTN NVTE_USE_CK_GEMM NVTE_ROCM_ENABLE_MXFP8 PRIMUS_FP8_DISABLE_TURBO PYTHONPATH HSA_ENABLE_SDMA HSA_NO_SCRATCH_RECLAIM \ + TORCH_COMPILE_DISABLE TORCHINDUCTOR_COMPILE_THREADS TRITON_CACHE_DIR \ + HSA_SIGNAL_ABORT_TIMEOUT HSA_ENABLE_INTERRUPT \ + HIP_LAUNCH_BLOCKING AMD_SERIALIZE_KERNEL AMD_SERIALIZE_COPY \ + AMD_LOG_LEVEL AMD_LOG_MASK MASTER_PORT TORCH_NCCL_HIGH_PRIORITY \ + NCCL_IB_DISABLE NCCL_P2P_DISABLE NCCL_IB_HCA NCCL_SOCKET_IFNAME \ + GLOO_SOCKET_IFNAME RCCL_DISABLE_AMDSMI NCCL_AMDSMI_DISABLE USING_AINIC \ + GPUS_PER_NODE NNODES PYTHONUNBUFFERED TE_DIR TE_WHEEL_DIR PRIMUS_MODEL \ + PRIMUS_SEQ_LENGTH PRIMUS_MAX_POSITION_EMBEDDINGS \ + PRIMUS_TEAM PRIMUS_USER PRIMUS_EXP_NAME \ + PRIMUS_STACK_GROUPED_WEIGHT_TRITON PRIMUS_ROPE_TRITON \ + PRIMUS_SINKHORN_TRITON PRIMUS_HC_TRITON PRIMUS_INDEXER_TRITON \ + PRIMUS_INDEXER_TRITON_FULL PRIMUS_V4_ROUTER_TRITON \ + PRIMUS_MUON_BATCHED_NS PRIMUS_COMPRESS_ROPE_CACHE PRIMUS_COMPRESS_POOL_TRITON; do + ENV_ARGS+=("--env" "$v") +done +[[ -n "${HIP_VISIBLE_DEVICES:-}" ]] && ENV_ARGS+=("--env" "HIP_VISIBLE_DEVICES") +# EXTRA_CLI: extra trailing --flag value overrides appended after PROXY_OVERRIDES +# (argparse last-wins), for dimension bisects etc. +[[ -n "${EXTRA_CLI:-}" ]] && ENV_ARGS+=("--env" "EXTRA_CLI") + +# Persistent Triton compile cache. The --rm container makes TRITON_CACHE_DIR +# ephemeral, so every run recompiles ALL kernels from scratch (the slow CPU-bound +# LLVM step that dominates iteration 1, esp. under this node's MCE storm). Mount a +# host dir so compiled kernels (hsaco) are reused across runs -> iter-1 of every +# later run with the same shapes skips the cold compile. Triton keys cache entries +# by kernel-source + arch + constexpr hash, so a wheel/arch/shape change auto- +# invalidates (safe to keep warm). Disable with PRIMUS_TRITON_CACHE_DIR="". +export PRIMUS_TRITON_CACHE_DIR=${PRIMUS_TRITON_CACHE_DIR:-$PRIMUS_PATH/.triton_cache_shared} +if [ -n "$PRIMUS_TRITON_CACHE_DIR" ]; then + mkdir -p "$PRIMUS_TRITON_CACHE_DIR" + export TRITON_CACHE_DIR="$PRIMUS_TRITON_CACHE_DIR" + echo "[triton] persistent compile cache: $TRITON_CACHE_DIR ($(find "$TRITON_CACHE_DIR" -maxdepth 1 -type d 2>/dev/null | wc -l) entries)" +fi + +VOLUME_ARGS=(-v "$PRIMUS_PATH":"$PRIMUS_PATH" -v "$DATA_PATH":"$DATA_PATH") +[[ -d "$TE_WHEEL_DIR" ]] && VOLUME_ARGS+=(-v "$TE_WHEEL_DIR":"$TE_WHEEL_DIR") +[[ -d "$TE_DIR" ]] && VOLUME_ARGS+=(-v "$TE_DIR":"$TE_DIR") +[[ -n "${TURBO_WHEEL_DIR:-}" && -d "$TURBO_WHEEL_DIR" ]] && VOLUME_ARGS+=(-v "$TURBO_WHEEL_DIR":"$TURBO_WHEEL_DIR") +[[ -n "${FLYDSL_PKG_DIR:-}" && -d "$FLYDSL_PKG_DIR/flydsl" ]] && VOLUME_ARGS+=(-v "$FLYDSL_PKG_DIR":"$FLYDSL_PKG_DIR") +[[ -n "${TRITON_CACHE_DIR:-}" ]] && VOLUME_ARGS+=(-v "$TRITON_CACHE_DIR":"$TRITON_CACHE_DIR") +# Opt-in tuned hipBLASLt: mount the built library at the same path and pass the +# loader env into the container (only when enabled, to keep stock runs untouched). +if [ "$PRIMUS_TUNED_HIPBLASLT" = "1" ]; then + VOLUME_ARGS+=(-v "$HBL_TUNED_RELEASE":"$HBL_TUNED_RELEASE") + ENV_ARGS+=("--env" "PRIMUS_TUNED_HIPBLASLT" "--env" "HIPBLASLT_TENSILE_LIBPATH" \ + "--env" "HBL_TUNED_RELEASE") +fi +# Container-side loader injection for the tuned lib (prepends to the image's paths). +HBL_PRELOAD_PREFIX="" +if [ "$PRIMUS_TUNED_HIPBLASLT" = "1" ]; then + HBL_PRELOAD_PREFIX="export LD_LIBRARY_PATH=\"\$HBL_TUNED_RELEASE/library:\${LD_LIBRARY_PATH:-}\" && export LD_PRELOAD=\"\$HBL_TUNED_RELEASE/library/libhipblaslt.so.1\${LD_PRELOAD:+:\$LD_PRELOAD}\" && echo \"[hipblaslt] container LD_PRELOAD=\$LD_PRELOAD\" && " +fi + +TE_INSTALL_PREFIX="\ + if ls ${TE_WHEEL_DIR}/transformer_engine-*.whl >/dev/null 2>&1; then \ + echo '[TE] installing prebuilt wheel from ${TE_WHEEL_DIR}' && \ + pip install --quiet --force-reinstall --no-deps ${TE_WHEEL_DIR}/transformer_engine-*.whl && \ + pip install --quiet einops nvdlfw-inspect onnxscript onnx pydantic importlib-metadata packaging transformers pybind11; \ + else \ + echo '[TE] WARNING: no TE wheel found at ${TE_WHEEL_DIR}; run will likely fail'; \ + fi && \ + echo '[deps] installing Primus requirements' && \ + pip install --quiet -r requirements.txt && \ + if [ -n \"${TURBO_WHEEL_DIR:-}\" ] && ls ${TURBO_WHEEL_DIR}/primus_turbo-*.whl >/dev/null 2>&1; then \ + echo '[turbo] installing real primus_turbo wheel from ${TURBO_WHEEL_DIR}' && \ + pip install --quiet --force-reinstall --no-deps ${TURBO_WHEEL_DIR}/primus_turbo-*.whl && \ + python -c 'import primus_turbo, primus_turbo.pytorch as _; print(\"[turbo] primus_turbo\", primus_turbo.__version__, \"imported OK\")'; \ + fi && " + +docker run --rm \ + "${ENV_ARGS[@]}" \ + --ipc=host --network=host \ + --device=/dev/kfd --device=/dev/dri \ + --cap-add=SYS_PTRACE --cap-add=CAP_SYS_ADMIN \ + --security-opt seccomp=unconfined --group-add video \ + --privileged \ + --name primus-v4-pro-muon-1gpu \ + "${VOLUME_ARGS[@]}" \ + "$DOCKER_IMAGE" /bin/bash -c "\ + set -e && cd $PRIMUS_PATH && \ + ${HBL_PRELOAD_PREFIX}\ + ${TE_INSTALL_PREFIX}\ + echo '==================== V4-PRO + MUON 1-GPU PROXY (gfx1250, BF16, eager, no profiler) ====================' && \ + EXP=$EXP PRIMUS_MODEL=$PRIMUS_MODEL GPUS_PER_NODE=1 NNODES=1 bash examples/run_pretrain.sh \ + ${PROXY_OVERRIDES} ${EXTRA_CLI:-}" \ + 2>&1 | tee "$LOG" diff --git a/examples/deepseek-v4/run_dsv4_projection_1gpu.sh b/examples/deepseek-v4/run_dsv4_projection_1gpu.sh new file mode 100755 index 000000000..fe0ec6886 --- /dev/null +++ b/examples/deepseek-v4/run_dsv4_projection_1gpu.sh @@ -0,0 +1,158 @@ +#!/bin/bash +############################################################################### +# Primus PROJECTION (memory / performance) for DeepSeek-V4 on one gfx1250 GPU. +# +# Sibling of run_deepseek_v4_pro_muon_1gpu.sh. Same validated gfx1250 docker +# recipe and the same required workarounds, but instead of pretraining it runs +# the Primus projection tool (docs/projection.md): benchmark a couple of layers +# on this single GPU and analytically project memory + training performance to +# a multi-node target cluster. +# +# Usage: +# ./run_dsv4_projection_1gpu.sh # performance, pro, ->8 nodes +# MODE=memory ./run_dsv4_projection_1gpu.sh # memory projection only +# PRIMUS_MODEL=deepseek_v4_flash ./run_dsv4_projection_1gpu.sh # flash model +# TARGET_NODES=16 ./run_dsv4_projection_1gpu.sh # project to 16 nodes +# PROFILING_MODE=simulate GPU_ARCH=mi355x MODE=performance ./run_dsv4_projection_1gpu.sh # CPU-only +############################################################################### +set -eo pipefail + +# Repo root: this script lives under examples/deepseek-v4/, so resolve two levels +# up. All paths below (TE_WHEEL_DIR, third_party, VOLUME_ARGS, cd) are repo-root-relative. +SCRIPT_DIR=$(realpath -m "$(dirname "$0")/../..") +export DOCKER_IMAGE=${DOCKER_IMAGE:-registry-sc-harbor.amd.com/framework/therock-npi@sha256:feba897e2a32a2465b8b296ed2662b2ad6136b5f1cf6f6c2716a3674aafc30f3} +export TE_WHEEL_DIR=${TE_WHEEL_DIR:-$(realpath -m "$SCRIPT_DIR/../../mi450/dist/feba897")} + +# ---------- What to project ------------------------------------------------- +export MODE=${MODE:-performance} # memory | performance +export PRIMUS_MODEL=${PRIMUS_MODEL:-deepseek_v4_pro} # deepseek_v4_pro | deepseek_v4_flash +export EXP=${EXP:-examples/megatron/configs/MI355X/deepseek_v4_flash-FP8-pretrain.yaml} +export BENCHMARK_GPUS=${BENCHMARK_GPUS:-1} # benchmark on this many GPUs (1 here) +# This box has ONE physical GPU. We invoke `primus-cli direct --single` +# (ONE python3 process, NOT torchrun) — +# the same trick the validated run_dsv3_projection script uses. In --single mode +# torchrun is not used, so GPUS_PER_NODE no longer drives --nproc_per_node; it +# serves ONLY as the TARGET node size (8 GPUs/node), while the projection spawns +# its own nproc=1 benchmark subprocess pinned to the single physical GPU. This +# gives correct intra-/inter-node comm modeling for the real 8-GPU-node cluster. +export GPUS_PER_NODE=8 # TARGET node size (8 nodes x 8 = 64 GPUs) +export TARGET_NODES=${TARGET_NODES:-8} # production TP1*PP8*EP8 = 64 GPUs = 8 nodes +export PROFILING_MODE=${PROFILING_MODE:-benchmark} # benchmark | simulate | both +export GPU_ARCH=${GPU_ARCH:-} # e.g. mi355x for --profiling-mode simulate + +# ---------- Required gfx1250 workarounds (see the pretrain launcher) --------- +export HSA_NO_SCRATCH_RECLAIM=1 +# gfx1250 MES async-queue hang workaround — matched EXACTLY to the training +# launcher (run_deepseek_v4_pro_muon_1gpu.sh): AMD_SERIALIZE_COPY=3 alone, which +# was bisected sufficient for the V4-Pro proxy (KERNEL serialize + LAUNCH_BLOCKING +# found unnecessary, default off). If the projection still hangs with this, the +# cause is the full-model build (all 61 layers on one rank), not these knobs. +export AMD_SERIALIZE_COPY=${AMD_SERIALIZE_COPY:-3} +export AMD_SERIALIZE_KERNEL=${AMD_SERIALIZE_KERNEL:-0} +export HIP_LAUNCH_BLOCKING=${HIP_LAUNCH_BLOCKING:-0} +export HSA_ENABLE_SDMA=${HSA_ENABLE_SDMA:-0} # flaky SDMA completion-signal workaround +# Turbo-free TE-native FP8 (tensorwise / Float8CurrentScaling), matching the +# training launcher. Without this the model uses TE DelayedScaling, which asserts +# against the V4 attention's save_original_input. fp8_utils.py reads this env. +export PRIMUS_FP8_DISABLE_TURBO=${PRIMUS_FP8_DISABLE_TURBO:-1} +export NVTE_ROCM_ENABLE_MXFP8=${NVTE_ROCM_ENABLE_MXFP8:-1} +# RCCL all_reduce(AVG) hangs even at world_size=1 -> sitecustomize rewrites AVG->SUM/ws. +# Also put the vendored Emerging-Optimizers on PYTHONPATH so the muon optimizer +# (emerging_optimizers.*) imports — required for OPTIMIZER=muon. +export PYTHONPATH_IN="$SCRIPT_DIR/examples/deepseek-v4/rccl_avg_workaround:$SCRIPT_DIR/third_party/Emerging-Optimizers" + +LOG=${LOG:-dsv4-projection-${MODE}.log} + +# Config overrides appended as trailing CLI key/value pairs. V4 uses its OWN +# attention (multi_latent_attention=false + yarn via dual_rope), but Megatron's +# stock validate_args rejects rope_type=yarn unless MLA is on. The projection +# benchmarks a stock-Megatron layer (not the V4 custom attention), so force +# rope_type=rope at the Megatron-arg level — V4 applies yarn internally and +# rope-vs-yarn is a cheap elementwise op (negligible for timing). +# (moe_router_score_function: V4 uses sqrtsoftplus, but Megatron's stock +# validate requires sigmoid for expert-bias aux-loss-free routing; the score +# function is a pointwise on router logits and does not change GEMM timing.) +# (moe_token_dispatcher_type: V4 uses the turbo "flex" dispatcher, which asserts +# TPxEP>1; the single-GPU benchmark runs at EP=1. Force "alltoall" — dispatcher +# type only affects MoE *communication* (modeled analytically), not expert-GEMM +# compute, so benchmarked layer time is unchanged.) +# (enable_primus_turbo / use_turbo_deepep: a Primus patch [moe_dispatcher_patches] +# force-replaces the dispatcher with the turbo DeepEP "flex" one when BOTH are +# true, and flex asserts TPxEP>1 (can't benchmark on 1 GPU). gfx1250 runs +# turbo-free anyway (training disables it), so force them off -> standard +# alltoall dispatcher, single-GPU-benchmarkable.) +# (gradient_accumulation_fusion: needs APEX fused_weight_gradient_mlp_cuda, not +# in this container; off here exactly as in the training launcher.) +# (optimizer: use the yaml default adam — the projection only benchmarks layer +# fwd/bwd, so the optimizer choice doesn't affect timing, and adam avoids muon's +# "Emerging Optimizers" package dependency that isn't in this container. The +# trainer.py adam/muon get_*_optimizer call sites were patched to match the +# bundled Megatron signature [dropped the removed no_wd_decay_cond/scale_lr_cond/ +# lr_mult positionals that collided with use_gloo_process_groups].) +# (tokenizer NullTokenizer: the benchmark builds a mock dataset; Megatron's +# MockGPTDataset JSON-serializes the tokenizer via .unique_identifiers, which the +# DeepSeekV4 HuggingFace tokenizer lacks -> crash. NullTokenizer has it, needs no +# HF download, and preserves vocab_size (129280) so embedding/LM-head GEMM dims +# are unchanged. Layer compute is tokenizer-independent.) +# (use_v4_triton_attention/csa: enable the fused flash-style V4 attention kernels +# instead of eager attention so the benchmarked attention time is representative +# of the real training config — eager materializes [B,H,S,S] and hugely inflates +# attention at seq 4096. Verified working on gfx1250.) +# V4-specific flags mirrored from the training launcher (now that the V4 builder +# is used, the real DeepseekV4MoE/HybridLayer is built and needs these): clamped +# SwiGLU support on the grouped backend, turbo off, legacy/permute-fusion off. +EXTRA_OVERRIDES=${EXTRA_OVERRIDES:---rope_type rope --moe_router_score_function sigmoid --moe_token_dispatcher_type alltoall --enable_primus_turbo false --use_turbo_deepep false --use_turbo_grouped_gemm false --use_turbo_gemm false --use_v4_compiled_sinkhorn false --moe_use_legacy_grouped_gemm false --moe_permute_fusion false --gradient_accumulation_fusion false --mtp_num_layers 0 --tokenizer_type NullTokenizer --use_v4_triton_attention true --use_v4_triton_csa_attention true} + +# ---------- Build the projection CLI args ----------------------------------- +PROJ_ARGS="projection $MODE --config $EXP" +if [ "$MODE" = "performance" ]; then + PROJ_ARGS="$PROJ_ARGS --benchmark-gpus $BENCHMARK_GPUS --target-nodes $TARGET_NODES --profiling-mode $PROFILING_MODE" + [ -n "$GPU_ARCH" ] && PROJ_ARGS="$PROJ_ARGS --gpu-arch $GPU_ARCH" +fi +PROJ_ARGS="$PROJ_ARGS $EXTRA_OVERRIDES" + +# TE wheel install prefix (same as pretrain launcher) +TE_INSTALL="true" +if ls "${TE_WHEEL_DIR}"/transformer_engine-*.whl >/dev/null 2>&1; then + TE_INSTALL="pip install --quiet --force-reinstall --no-deps ${TE_WHEEL_DIR}/transformer_engine-*.whl && \ + pip install --quiet einops nvdlfw-inspect onnxscript onnx pydantic importlib-metadata packaging transformers pybind11" +fi +# simulate mode (CPU-only, no model instantiation) needs the Origami GEMM model. +ORIGAMI_INSTALL="true" +if [ "$PROFILING_MODE" = "simulate" ] || [ "$PROFILING_MODE" = "both" ]; then + ORIGAMI_INSTALL="pip install --quiet 'git+https://github.com/ROCm/rocm-libraries.git#subdirectory=shared/origami/python' || echo '[warn] origami install failed'" +fi + +VOLUME_ARGS=(-v "$SCRIPT_DIR":"$SCRIPT_DIR") +[[ -d "$TE_WHEEL_DIR" ]] && VOLUME_ARGS+=(-v "$TE_WHEEL_DIR":"$TE_WHEEL_DIR") + +echo "[projection] mode=$MODE model=$PRIMUS_MODEL target_nodes=$TARGET_NODES profiling_mode=$PROFILING_MODE config=$EXP" + +# Same docker invocation as run_deepseek_v4_pro_muon_1gpu.sh (validated gfx1250 recipe). +docker run --rm \ + --ipc=host --network=host \ + --device=/dev/kfd --device=/dev/dri \ + --cap-add=SYS_PTRACE --cap-add=CAP_SYS_ADMIN \ + --security-opt seccomp=unconfined --group-add video \ + --privileged \ + --name primus-v4-projection \ + -e NNODES=1 -e GPUS_PER_NODE="$GPUS_PER_NODE" \ + -e MASTER_ADDR=localhost -e MASTER_PORT=1234 \ + -e GLOO_SOCKET_IFNAME=lo -e NCCL_SOCKET_IFNAME=lo \ + -e NCCL_IB_DISABLE=1 -e NCCL_P2P_DISABLE=1 \ + -e HSA_NO_SCRATCH_RECLAIM="$HSA_NO_SCRATCH_RECLAIM" \ + -e AMD_SERIALIZE_COPY="$AMD_SERIALIZE_COPY" -e AMD_SERIALIZE_KERNEL="$AMD_SERIALIZE_KERNEL" \ + -e HIP_LAUNCH_BLOCKING="$HIP_LAUNCH_BLOCKING" -e HSA_ENABLE_SDMA="$HSA_ENABLE_SDMA" \ + -e PRIMUS_FP8_DISABLE_TURBO="$PRIMUS_FP8_DISABLE_TURBO" -e NVTE_ROCM_ENABLE_MXFP8="$NVTE_ROCM_ENABLE_MXFP8" \ + -e PRIMUS_PROJ_MAX_LAYERS="${PRIMUS_PROJ_MAX_LAYERS:-1}" -e PRIMUS_PROJ_COMPRESS_RATIOS="${PRIMUS_PROJ_COMPRESS_RATIOS:-}" \ + -e PRIMUS_MODEL="$PRIMUS_MODEL" -e PYTHONUNBUFFERED=1 \ + "${VOLUME_ARGS[@]}" \ + "$DOCKER_IMAGE" /bin/bash -c "\ + set -e && cd $SCRIPT_DIR && \ + export PYTHONPATH=$PYTHONPATH_IN:\${PYTHONPATH:-} && \ + ${TE_INSTALL} && \ + ${ORIGAMI_INSTALL} && \ + pip install --quiet -r requirements.txt && \ + echo '==================== DSV4 PROJECTION ($MODE) ====================' && \ + bash runner/primus-cli direct --single -- $PROJ_ARGS" \ + 2>&1 | tee "$LOG" diff --git a/examples/diffusion/README.md b/examples/diffusion/README.md new file mode 100644 index 000000000..42ad9a61a --- /dev/null +++ b/examples/diffusion/README.md @@ -0,0 +1,131 @@ +# Diffusion Examples + +This directory contains launch examples for the in-tree `diffusion` backend. + +## Common Launch Env + +```bash +export NNODES=${NNODES:-1} +export NODE_RANK=${NODE_RANK:-0} +export MASTER_ADDR=${MASTER_ADDR:-127.0.0.1} +export MASTER_PORT=${MASTER_PORT:-29500} +export GPUS_PER_NODE=${GPUS_PER_NODE:-8} +``` + +## FLUX.1-schnell Raw Image-Text + +Raw mode loads image-text samples and runs frozen T5, CLIP, and FLUX AE online. +The default `DATASET=cc12m-test` uses the Hugging Face dataset +`zirui3/cc12m-test`, so no dataset preprocessing is required for a smoke test. + +Download the encoders and autoencoder before launching training: + +```bash +huggingface-cli download google/t5-v1_1-xxl \ + --local-dir /models/t5-v1_1-xxl +huggingface-cli download openai/clip-vit-large-patch14 \ + --local-dir /models/clip-vit-large-patch14 +huggingface-cli download black-forest-labs/FLUX.1-dev ae.safetensors \ + --local-dir /models/FLUX.1-dev +``` + +Launch raw training: + +```bash +T5_ENCODER=/models/t5-v1_1-xxl \ +CLIP_ENCODER=/models/clip-vit-large-patch14 \ +VAE_CHECKPOINT=/models/FLUX.1-dev/ae.safetensors \ +MAX_STEPS=10 \ +torchrun \ + --nnodes="$NNODES" --node_rank="$NODE_RANK" \ + --master_addr="$MASTER_ADDR" --master_port="$MASTER_PORT" \ + --nproc_per_node="$GPUS_PER_NODE" \ + -m primus.cli.main train pretrain \ + --config examples/diffusion/configs/MI355X/flux.1_schnell_t2i-raw-pretrain.yaml +``` + +To use a local WebDataset directory instead, set `DATASET_PATH=/path/to/tars`. +To use the full Hugging Face dataset directly, add `DATASET=cc12m-wds` to the +launch command and omit `DATASET_PATH`. + +To run FLUX.1-dev, use the same training example shape and set the model preset +to `flux.1_dev_t2i.yaml`. FLUX.1-dev has a guidance embedding module; +FLUX.1-schnell does not. + +## Wan Data + +Wan examples use a JSONL metadata file plus a media directory: + +```bash +huggingface-cli download zirui3/tiny-video-samples \ + --repo-type dataset \ + --local-dir /data/tiny-video-samples +``` + +Expected layout: + +```text +/data/tiny-video-samples/ + meta.jsonl + data/*.mp4 +``` + +Download Wan checkpoints separately and set the model paths used by the selected +config. For Wan2.2 TI2V 5B, the default paths can be overridden with: + +```bash +export PRETRAINED_PATH=/models/Wan2.2-TI2V-5B +export INIT_CHECKPOINT=/models/Wan2.2-TI2V-5B +export TEXT_TOKENIZER=/models/Wan2.2-TI2V-5B/google/umt5-xxl +export TEXT_ENCODER=/models/Wan2.2-TI2V-5B/models_t5_umt5-xxl-enc-bf16.pth +export VAE_CHECKPOINT=/models/Wan2.2-TI2V-5B/Wan2.2_VAE.pth +``` + +## Wan Pretrain + +```bash +DATASET_PATH=/data/tiny-video-samples/meta.jsonl \ +DATA_FOLDER=/data/tiny-video-samples/data \ +ATTENTION_BACKEND=flash_attn_aiter \ +SP_SIZE=1 \ +MAX_STEPS=10 \ +torchrun \ + --nnodes="$NNODES" --node_rank="$NODE_RANK" \ + --master_addr="$MASTER_ADDR" --master_port="$MASTER_PORT" \ + --nproc_per_node="$GPUS_PER_NODE" \ + -m primus.cli.main train pretrain \ + --config examples/diffusion/configs/MI355X/wan2.2_ti2v_5b-pretrain.yaml +``` + +Use `SP_SIZE=4` or `SP_SIZE=8` when the model head count supports it. + +## Wan Posttrain + +```bash +INIT_CHECKPOINT=/models/Wan2.2-TI2V-5B \ +DATASET_PATH=/data/tiny-video-samples/meta.jsonl \ +DATA_FOLDER=/data/tiny-video-samples/data \ +ATTENTION_BACKEND=flash_attn_aiter \ +SP_SIZE=1 \ +MAX_STEPS=10 \ +torchrun \ + --nnodes="$NNODES" --node_rank="$NODE_RANK" \ + --master_addr="$MASTER_ADDR" --master_port="$MASTER_PORT" \ + --nproc_per_node="$GPUS_PER_NODE" \ + -m primus.cli.main train posttrain \ + --config examples/diffusion/configs/MI355X/wan2.2_ti2v_5b-posttrain.yaml +``` + +## Prepare Check + +Validate configured paths before launching: + +```bash +python3 runner/helpers/hooks/train/pretrain/diffusion/prepare.py \ + --config examples/diffusion/configs/MI355X/flux.1_schnell_t2i-raw-pretrain.yaml + +python3 runner/helpers/hooks/train/pretrain/diffusion/prepare.py \ + --config examples/diffusion/configs/MI355X/wan2.2_ti2v_5b-pretrain.yaml +``` + +On success the hook prints `env.PREPARED=1`. diff --git a/examples/diffusion/configs/MI355X/flux.1_schnell_t2i-pretrain.yaml b/examples/diffusion/configs/MI355X/flux.1_schnell_t2i-pretrain.yaml new file mode 100644 index 000000000..9429fb1f7 --- /dev/null +++ b/examples/diffusion/configs/MI355X/flux.1_schnell_t2i-pretrain.yaml @@ -0,0 +1,58 @@ +work_group: ${PRIMUS_TEAM:local} +user_name: ${PRIMUS_USER:local} +exp_name: ${PRIMUS_EXP_NAME:flux.1_schnell_t2i-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +platform: + config: platform_local.yaml + +modules: + pre_trainer: + framework: diffusion + config: pre_trainer.yaml + + model: flux.1_schnell_t2i.yaml + overrides: + sink_level: null + file_sink_level: INFO + stderr_sink_level: INFO + + metrics: + log_freq: 1 + enable_wandb: false + + training: + local_batch_size: ${LOCAL_BATCH_SIZE:1} + steps: ${MAX_STEPS:50} + num_train_epochs: 1000 + gradient_accumulation_steps: 1 + output_dir: ${OUTPUT_DIR:./output/flux.1_schnell_t2i-pretrain} + save_steps: 0 + save_strategy: ${SAVE_STRATEGY:dit_only} + run_name: flux.1_schnell_t2i-pretrain + + data: + dataset_path: ${DATASET_PATH:} + empty_encodings_path: ${EMPTY_ENCODINGS_PATH:} + prompt_dropout_prob: ${PROMPT_DROPOUT_PROB:0.1} + img_size: ${IMG_SIZE:256} + + parallelism: + sp_size: 1 + dp_replicate: ${DP_REPLICATE:1} + + optimizer: + lr: ${LR:2.0e-4} + weight_decay: ${WEIGHT_DECAY:0.1} + adam_beta2: ${ADAM_BETA2:0.95} + + lr_scheduler: + lr_scheduler_type: constant_with_warmup + warmup_steps: ${WARMUP_STEPS:1600} + + runtime: + attention_backend: ${ATTENTION_BACKEND:flash_attn_aiter} + gradient_checkpointing: ${GRADIENT_CHECKPOINTING:false} + compile_transformer_blocks: ${COMPILE_TRANSFORMER_BLOCKS:true} + fsdp2_reshard_after_forward: ${FSDP2_RESHARD_AFTER_FORWARD:true} + report_to: none diff --git a/examples/diffusion/configs/MI355X/flux.1_schnell_t2i-raw-pretrain.yaml b/examples/diffusion/configs/MI355X/flux.1_schnell_t2i-raw-pretrain.yaml new file mode 100644 index 000000000..3b8798bc8 --- /dev/null +++ b/examples/diffusion/configs/MI355X/flux.1_schnell_t2i-raw-pretrain.yaml @@ -0,0 +1,69 @@ +work_group: ${PRIMUS_TEAM:local} +user_name: ${PRIMUS_USER:local} +exp_name: ${PRIMUS_EXP_NAME:flux.1_schnell_t2i-raw-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +platform: + config: platform_local.yaml + +modules: + pre_trainer: + framework: diffusion + config: pre_trainer.yaml + + model: flux.1_schnell_t2i.yaml + overrides: + sink_level: null + file_sink_level: INFO + stderr_sink_level: INFO + + model: + config: + encoder: + t5_encoder: ${T5_ENCODER:google/t5-v1_1-xxl} + clip_encoder: ${CLIP_ENCODER:openai/clip-vit-large-patch14} + autoencoder: ${VAE_CHECKPOINT:black-forest-labs/FLUX.1-dev/ae.safetensors} + max_t5_length: ${MAX_T5_LENGTH:256} + + metrics: + log_freq: 1 + enable_wandb: false + + training: + local_batch_size: ${LOCAL_BATCH_SIZE:1} + steps: ${MAX_STEPS:50} + num_train_epochs: 1000 + gradient_accumulation_steps: 1 + output_dir: ${OUTPUT_DIR:./output/flux.1_schnell_t2i-raw-pretrain} + save_steps: 0 + save_strategy: ${SAVE_STRATEGY:dit_only} + run_name: flux.1_schnell_t2i-raw-pretrain + + data: + dataset_type: raw + dataset: ${DATASET:cc12m-test} + dataset_format: ${DATASET_FORMAT:webdataset} + dataset_path: ${DATASET_PATH:} + prompt_dropout_prob: ${PROMPT_DROPOUT_PROB:0.1} + img_size: ${IMG_SIZE:256} + skip_low_resolution: false + + parallelism: + sp_size: 1 + dp_replicate: ${DP_REPLICATE:1} + + optimizer: + lr: ${LR:2.0e-4} + weight_decay: ${WEIGHT_DECAY:0.1} + adam_beta2: ${ADAM_BETA2:0.95} + + lr_scheduler: + lr_scheduler_type: constant_with_warmup + warmup_steps: ${WARMUP_STEPS:1600} + + runtime: + attention_backend: ${ATTENTION_BACKEND:flash_attn_aiter} + gradient_checkpointing: ${GRADIENT_CHECKPOINTING:false} + compile_transformer_blocks: ${COMPILE_TRANSFORMER_BLOCKS:true} + fsdp2_reshard_after_forward: ${FSDP2_RESHARD_AFTER_FORWARD:true} + report_to: none diff --git a/examples/diffusion/configs/MI355X/wan2.1_t2v_1.3b-posttrain.yaml b/examples/diffusion/configs/MI355X/wan2.1_t2v_1.3b-posttrain.yaml new file mode 100644 index 000000000..cbd877eb8 --- /dev/null +++ b/examples/diffusion/configs/MI355X/wan2.1_t2v_1.3b-posttrain.yaml @@ -0,0 +1,53 @@ +work_group: ${PRIMUS_TEAM:local} +user_name: ${PRIMUS_USER:local} +exp_name: ${PRIMUS_EXP_NAME:wan2.1_t2v_1.3b-posttrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +platform: + config: platform_local.yaml + +modules: + post_trainer: + framework: diffusion + config: post_trainer.yaml + + # Model preset to fine-tune from INIT_CHECKPOINT. + model: wan2.1_t2v_1.3b_sft.yaml + overrides: + sink_level: null + file_sink_level: INFO + stderr_sink_level: INFO + + metrics: + log_freq: 1 + enable_wandb: false + + training: + local_batch_size: 1 + steps: ${MAX_STEPS:50} + num_train_epochs: 1000 + gradient_accumulation_steps: 1 + output_dir: ${OUTPUT_DIR:./output/wan2.1_t2v_1.3b-posttrain} + save_steps: 0 + run_name: wan2.1_t2v_1.3b-posttrain + + data: + dataset_path: ${DATASET_PATH:/data/tiny-video-samples/meta.jsonl} + data_folder: ${DATA_FOLDER:/data/tiny-video-samples/data} + frame_num: 81 + video_backend: imageio + text_tokenizer: ${TEXT_TOKENIZER:/models/Wan2.1-T2V-1.3B/google/umt5-xxl} + height: 480 + width: 832 + + parallelism: + sp_size: ${SP_SIZE:1} + dp_replicate: ${DP_REPLICATE:1} + + optimizer: + lr: 5.0e-6 + weight_decay: 0.01 + + runtime: + attention_backend: ${ATTENTION_BACKEND:flash_attn_aiter} + report_to: none diff --git a/examples/diffusion/configs/MI355X/wan2.1_t2v_1.3b-pretrain.yaml b/examples/diffusion/configs/MI355X/wan2.1_t2v_1.3b-pretrain.yaml new file mode 100644 index 000000000..c13a2cf0b --- /dev/null +++ b/examples/diffusion/configs/MI355X/wan2.1_t2v_1.3b-pretrain.yaml @@ -0,0 +1,53 @@ +work_group: ${PRIMUS_TEAM:local} +user_name: ${PRIMUS_USER:local} +exp_name: ${PRIMUS_EXP_NAME:wan2.1_t2v_1.3b-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +platform: + config: platform_local.yaml + +modules: + pre_trainer: + framework: diffusion + config: pre_trainer.yaml + + # Model preset to run. + model: wan2.1_t2v_1.3b.yaml + overrides: + sink_level: null + file_sink_level: INFO + stderr_sink_level: INFO + + metrics: + log_freq: 1 + enable_wandb: false + + training: + local_batch_size: 1 + steps: ${MAX_STEPS:50} + num_train_epochs: 1000 + gradient_accumulation_steps: 1 + output_dir: ${OUTPUT_DIR:./output/wan2.1_t2v_1.3b-pretrain} + save_steps: 0 + run_name: wan2.1_t2v_1.3b-pretrain + + data: + dataset_path: ${DATASET_PATH:/data/tiny-video-samples/meta.jsonl} + data_folder: ${DATA_FOLDER:/data/tiny-video-samples/data} + frame_num: 81 + video_backend: imageio + text_tokenizer: ${TEXT_TOKENIZER:/models/Wan2.1-T2V-1.3B/google/umt5-xxl} + height: 480 + width: 832 + + parallelism: + sp_size: ${SP_SIZE:1} + dp_replicate: ${DP_REPLICATE:1} + + optimizer: + lr: 1.0e-5 + weight_decay: 0.01 + + runtime: + attention_backend: ${ATTENTION_BACKEND:flash_attn_aiter} + report_to: none diff --git a/examples/diffusion/configs/MI355X/wan2.2_ti2v_5b-posttrain.yaml b/examples/diffusion/configs/MI355X/wan2.2_ti2v_5b-posttrain.yaml new file mode 100644 index 000000000..94ee0d09e --- /dev/null +++ b/examples/diffusion/configs/MI355X/wan2.2_ti2v_5b-posttrain.yaml @@ -0,0 +1,53 @@ +work_group: ${PRIMUS_TEAM:local} +user_name: ${PRIMUS_USER:local} +exp_name: ${PRIMUS_EXP_NAME:wan2.2_ti2v_5b-posttrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +platform: + config: platform_local.yaml + +modules: + post_trainer: + framework: diffusion + config: post_trainer.yaml + + # Model preset to fine-tune from INIT_CHECKPOINT. + model: wan2.2_ti2v_5b_sft.yaml + overrides: + sink_level: null + file_sink_level: INFO + stderr_sink_level: INFO + + metrics: + log_freq: 1 + enable_wandb: false + + training: + local_batch_size: 1 + steps: ${MAX_STEPS:50} + num_train_epochs: 1000 + gradient_accumulation_steps: 1 + output_dir: ${OUTPUT_DIR:./output/wan2.2_ti2v_5b-posttrain} + save_steps: 0 + run_name: wan2.2_ti2v_5b-posttrain + + data: + dataset_path: ${DATASET_PATH:/data/tiny-video-samples/meta.jsonl} + data_folder: ${DATA_FOLDER:/data/tiny-video-samples/data} + frame_num: 81 + video_backend: imageio + text_tokenizer: ${TEXT_TOKENIZER:/models/Wan2.2-TI2V-5B/google/umt5-xxl} + height: 480 + width: 832 + + parallelism: + sp_size: ${SP_SIZE:1} + dp_replicate: ${DP_REPLICATE:1} + + optimizer: + lr: 5.0e-6 + weight_decay: 0.01 + + runtime: + attention_backend: ${ATTENTION_BACKEND:flash_attn_aiter} + report_to: none diff --git a/examples/diffusion/configs/MI355X/wan2.2_ti2v_5b-pretrain.yaml b/examples/diffusion/configs/MI355X/wan2.2_ti2v_5b-pretrain.yaml new file mode 100644 index 000000000..765a454b5 --- /dev/null +++ b/examples/diffusion/configs/MI355X/wan2.2_ti2v_5b-pretrain.yaml @@ -0,0 +1,53 @@ +work_group: ${PRIMUS_TEAM:local} +user_name: ${PRIMUS_USER:local} +exp_name: ${PRIMUS_EXP_NAME:wan2.2_ti2v_5b-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +platform: + config: platform_local.yaml + +modules: + pre_trainer: + framework: diffusion + config: pre_trainer.yaml + + # Model preset to run. + model: wan2.2_ti2v_5b.yaml + overrides: + sink_level: null + file_sink_level: INFO + stderr_sink_level: INFO + + metrics: + log_freq: 1 + enable_wandb: false + + training: + local_batch_size: 1 + steps: ${MAX_STEPS:50} + num_train_epochs: 1000 + gradient_accumulation_steps: 1 + output_dir: ${OUTPUT_DIR:./output/wan2.2_ti2v_5b-pretrain} + save_steps: 0 + run_name: wan2.2_ti2v_5b-pretrain + + data: + dataset_path: ${DATASET_PATH:/data/tiny-video-samples/meta.jsonl} + data_folder: ${DATA_FOLDER:/data/tiny-video-samples/data} + frame_num: 81 + video_backend: imageio + text_tokenizer: ${TEXT_TOKENIZER:/models/Wan2.2-TI2V-5B/google/umt5-xxl} + height: 480 + width: 832 + + parallelism: + sp_size: ${SP_SIZE:1} + dp_replicate: ${DP_REPLICATE:1} + + optimizer: + lr: 1.0e-5 + weight_decay: 0.01 + + runtime: + attention_backend: ${ATTENTION_BACKEND:flash_attn_aiter} + report_to: none diff --git a/examples/maxtext/configs/MI300X/mixtral_8x22B-pretrain.yaml b/examples/maxtext/configs/MI300X/mixtral_8x22B-pretrain.yaml new file mode 100644 index 000000000..3db838b20 --- /dev/null +++ b/examples/maxtext/configs/MI300X/mixtral_8x22B-pretrain.yaml @@ -0,0 +1,41 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:mixtral_8x22B-pretrain} +workspace: ./output + +modules: + pre_trainer: + framework: maxtext + config: pre_trainer.yaml + + # model to run + model: mixtral_8x22B.yaml + overrides: + run_name: "mixtral_8x22b_training" + base_output_directory: "./output" + steps: 50 + profiler: "" + + # data + dataset_type: "synthetic" + hf_access_token: ${HF_TOKEN:""} + + # checkpoint + enable_checkpointing: false + async_checkpointing: false + + # inter-node parallelism strategy + dcn_data_parallelism: -1 + dcn_fsdp_parallelism: 1 + + # intra-node parallelism strategy + ici_fsdp_parallelism: 1 + ici_data_parallelism: 1 + ici_expert_parallelism: -1 + + sparse_matmul: false + megablox: false + capacity_factor: 1 + max_target_length: 4096 + per_device_batch_size: 4 + remat_policy: "save_dot_with_context_except_mlp" diff --git a/examples/maxtext/configs/MI300X/mixtral_8x7B-pretrain.yaml b/examples/maxtext/configs/MI300X/mixtral_8x7B-pretrain.yaml index d983c794e..0d1806150 100644 --- a/examples/maxtext/configs/MI300X/mixtral_8x7B-pretrain.yaml +++ b/examples/maxtext/configs/MI300X/mixtral_8x7B-pretrain.yaml @@ -39,3 +39,4 @@ modules: max_target_length: 4096 per_device_batch_size: 12 remat_policy: "save_dot_with_context_except_mlp" + moe_dispatch_no_expert_sharding: true diff --git a/examples/maxtext/configs/MI355X/mixtral_8x22B-pretrain.yaml b/examples/maxtext/configs/MI355X/mixtral_8x22B-pretrain.yaml new file mode 100644 index 000000000..d4ea7fd6b --- /dev/null +++ b/examples/maxtext/configs/MI355X/mixtral_8x22B-pretrain.yaml @@ -0,0 +1,41 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:mixtral_8x22B-pretrain} +workspace: ./output + +modules: + pre_trainer: + framework: maxtext + config: pre_trainer.yaml + + # model to run + model: mixtral_8x22B.yaml + overrides: + run_name: "mixtral_8x22b_training" + base_output_directory: "./output" + steps: 50 + profiler: "" + + # data + dataset_type: "synthetic" + hf_access_token: ${HF_TOKEN:""} + + # checkpoint + enable_checkpointing: false + async_checkpointing: false + + # inter-node parallelism strategy + dcn_data_parallelism: -1 + dcn_fsdp_parallelism: 1 + + # intra-node parallelism strategy + ici_fsdp_parallelism: 1 + ici_data_parallelism: 1 + ici_expert_parallelism: -1 + + sparse_matmul: false + megablox: false + capacity_factor: 1 + max_target_length: 4096 + per_device_batch_size: 8 + remat_policy: "save_dot_with_context_except_mlp" diff --git a/examples/maxtext/configs/MI355X/mixtral_8x7B-pretrain.yaml b/examples/maxtext/configs/MI355X/mixtral_8x7B-pretrain.yaml index 342f0f454..e0da812df 100644 --- a/examples/maxtext/configs/MI355X/mixtral_8x7B-pretrain.yaml +++ b/examples/maxtext/configs/MI355X/mixtral_8x7B-pretrain.yaml @@ -39,3 +39,4 @@ modules: max_target_length: 4096 per_device_batch_size: 11 remat_policy: "minimal" + moe_dispatch_no_expert_sharding: true diff --git a/examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml b/examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml new file mode 100644 index 000000000..dbd76aa26 --- /dev/null +++ b/examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml @@ -0,0 +1,209 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — DDP + Local Spec + FP8 Tensorwise (MI300X) +# +# Combines Megatron DDP + distributed optimizer with the local spec provider +# (PrimusTurboFloat8LocalSpecProvider) for FP8 tensorwise training. +# +# Key configuration: +# - Megatron DDP with overlap_grad_reduce + overlap_param_gather +# - PrimusTurboFloat8LocalSpecProvider (NO TransformerEngine dependency) +# - FP8 hybrid tensorwise (per-module FP8 via Primus Turbo) +# - Primus Turbo attention +# - torch.compile enabled (per_block strategy, compatible with local spec + overlap) +# - Energon pre-encoded dataset with stored VAE mean/logvar (resample mode) + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_ddp_local_fp8} + +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + # Short LR warmup ramp over the first optimizer steps for FP8 stability. + nemo_aligned_lr_warmup: true + warmup_train_steps: 2 + + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + # VAE latent normalization (required for resample mode) + vae_scale: 0.3611 + vae_shift: 0.1159 + + # Resample mode: re-draw latents from mean+logvar each step + vae_latent_mode: resample + + # RoPE: must be interleaved to match Flux's EmbedND doubled-frequency layout + rotary_interleaved: true + + # ========================================== + # PrimusTurboFloat8LocalSpecProvider + # ========================================== + transformer_impl: "local" + + # ========================================== + # DATASET CONFIGURATION + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 1000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # Batch configuration (scaled down for MI300X 192GB; tune to your hardware) + micro_batch_size: 32 + global_batch_size: 256 + seq_length: 512 # 256 img tokens + 256 text tokens (schnell) + + # ========================================== + # BF16 + FP8 Precision + # ========================================== + bf16: true + fp16: false + params_dtype: bfloat16 + grad_reduce_in_bf16: true + + # Optimizer settings + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + # Learning rate scheduler (warmup-hold, no decay) + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader settings + num_workers: 8 + dataloader_type: external + max_samples_per_sequence: null + + # ========================================== + # Megatron DDP + Distributed Optimizer + # ========================================== + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: false + use_megatron_fsdp: false + + use_distributed_optimizer: true + overlap_grad_reduce: true + overlap_param_gather: true + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: true + main_params_dtype: fp32 + main_grads_dtype: bf16 + exp_avg_dtype: bf16 + exp_avg_sq_dtype: bf16 + + ddp_bucket_size: 256000000 + use_fsdp2_fp32_param_optimizer: false + + ckpt_format: torch_dist + + gradient_accumulation_fusion: false + + check_for_nan_in_loss_and_grad: true + + # ========================================== + # FP8 Configuration — Tensorwise + Delayed FP8 via Primus Turbo + # ========================================== + + use_flash_attn: true + + fp8: "hybrid" + # TE-compatible unified form: fp8: hybrid + fp8_recipe: delayed selects + # tensorwise FP8 with delayed scaling. Resolution path: + # primus/backends/megatron/core/extensions/primus_turbo_float8_local.py + # :: Float8{Column,Row}ParallelLinear._use_delayed_scaling. + fp8_recipe: "delayed" + fp8_margin: 0 + fp8_amax_history_len: 1024 + fp8_amax_compute_algo: "max" + fp8_wgrad: true + fp8_dot_product_attention: false + fp8_multi_head_attention: false + fp8_reduce_amax: true + + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 180 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_ddp_local_fp8 + wandb_project: flux_12b_ddp_local_fp8 + log_throughput: true + wall_clock_step_timer: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # PyTorch Profiler + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo Configuration + enable_primus_turbo: true + use_turbo_attention: true + use_dual_fp8_output_projection: false + + seed: 2025 + per_step_rng_reseed: false + nemo_chimera_init: false + + # Torch Compile — compatible with Float8 local spec (per-module FP8) + torch_compile: + enable: true + strategy: "per_block" + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: false + emulate_precision_casts: false + fused_ln_modulate: true diff --git a/examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec.yaml b/examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec.yaml new file mode 100644 index 000000000..12b11d34a --- /dev/null +++ b/examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec.yaml @@ -0,0 +1,187 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — DDP + TransformerEngine Spec + BF16 (MI300X) +# +# BF16 training using Megatron DDP + distributed optimizer with +# TransformerEngine modules (TEColumnParallelLinear / TERowParallelLinear / +# TEDotProductAttention / TENorm). This is the BF16 baseline on the +# TransformerEngine path; see the *_te_spec_fp8 variant for FP8. +# +# Key settings: +# bf16: true +# params_dtype: bfloat16 +# micro_batch_size: 32 / global_batch_size: 256 +# +# Batch sizes are scaled down from the MI355X recipe (MBS=64/GBS=512) for +# MI300X (192GB) headroom; tune to your hardware. + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_ddp_te_bf16} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b_rope_fusion.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + # VAE latent normalization (required for resample mode) + vae_scale: 0.3611 + vae_shift: 0.1159 + + # Resample mode: re-draw latents from mean+logvar each step + vae_latent_mode: resample + + # RoPE: must be interleaved to match Flux's EmbedND doubled-frequency layout + rotary_interleaved: true + + # ========================================== + # TransformerEngine Spec (default) + # ========================================== + transformer_impl: "transformer_engine" + + # ========================================== + # ENERGON DATASET CONFIGURATION + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 1000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # Batch configuration (scaled down for MI300X; tune to your hardware) + micro_batch_size: 32 + global_batch_size: 256 + seq_length: 512 + + # ========================================== + # BF16 Precision + # ========================================== + bf16: true + fp16: false + params_dtype: bfloat16 + grad_reduce_in_bf16: true + + # Optimizer settings (identical to BF16 baseline) + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + # Learning rate scheduler (warmup-hold, no decay) + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader settings + num_workers: 8 + dataloader_type: external + max_samples_per_sequence: null + + # ========================================== + # Megatron DDP + Distributed Optimizer + # ========================================== + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: false + use_megatron_fsdp: false + use_fsdp2_fp32_param_optimizer: false + + ckpt_format: torch_dist + + use_distributed_optimizer: true + + overlap_grad_reduce: true + overlap_param_gather: true + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: false + + ddp_bucket_size: 256000000 + + gradient_accumulation_fusion: false + + # ========================================== + # Memory Optimizations + # ========================================== + + use_flash_attn: true + + fp8: null + + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 60 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_ddp_te_bf16 + wandb_project: flux_12b_ddp_te_bf16 + log_throughput: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # PyTorch Profiler + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo Configuration + enable_primus_turbo: false + use_turbo_attention: false + + seed: 2025 + te_rng_tracker: true + + # Torch Compile — stack strategy (compiles the double/single DiT block + # stacks as inductor regions). Matches the MI355X te_spec recipe and the + # MLPerf NeMo reference (COMPILE_DIT strategy=stack); reduces activation + # memory and improves throughput on the eager BF16 TE path. + torch_compile: + enable: true + strategy: "stack" + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: false diff --git a/examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml b/examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml new file mode 100644 index 000000000..2df61605f --- /dev/null +++ b/examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml @@ -0,0 +1,210 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — DDP + TE Spec + FP8 Delayed Scaling (MI300X) +# +# FP8 training using Megatron DDP + distributed optimizer with TransformerEngine +# modules and FP8 hybrid delayed scaling: +# - Megatron DDP with overlap_grad_reduce + overlap_param_gather +# - TEColumnParallelLinear / TERowParallelLinear / TEDotProductAttention / TENorm +# - FP8 hybrid (E4M3 fwd, E5M2 bwd) with delayed scaling (amax history 1024) +# - RoPE fusion via apply_rope_fusion: true +# - Energon pre-encoded dataset +# +# Required environment variables for MI300X (set before launch): +# export NVTE_FUSED_ATTN=1 +# export NVTE_FUSED_ATTN_CK=1 +# export NVTE_FP8_DPA_BWD=1 +# export NVTE_USE_HIPBLASLT=1 +# export USE_HIPBLASLT=1 +# export TORCH_BLAS_PREFER_HIPBLASLT=1 +# export NVTE_USE_CAST_TRANSPOSE_TRITON=1 + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_ddp_te_fp8} + +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + vae_scale: 0.3611 + vae_shift: 0.1159 + vae_latent_mode: resample + + # ========================================== + # RoPE Fusion + # ========================================== + rotary_interleaved: true + apply_rope_fusion: true + position_embedding_type: rope + + # ========================================== + # TransformerEngine Spec + # ========================================== + transformer_impl: "transformer_engine" + + # ========================================== + # FP8 — hybrid delayed scaling + # ========================================== + fp8: "hybrid" + fp8_recipe: "delayed" + fp8_margin: 0 + fp8_amax_history_len: 1024 + fp8_amax_compute_algo: "max" + fp8_wgrad: true + fp8_dot_product_attention: false + fp8_multi_head_attention: false + + # ========================================== + # Energon Dataset + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 1000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # ========================================== + # Batch Configuration + # ========================================== + # MI300X has 192GB HBM3 (vs 256GB on MI355X), so MBS reduced + # from 64 to 32 and GBS from 512 to 256 to avoid OOM with FP8. + micro_batch_size: 32 + global_batch_size: 256 + seq_length: 512 + + # ========================================== + # BF16 + FP8 Precision + # ========================================== + bf16: true + fp16: false + params_dtype: bfloat16 + grad_reduce_in_bf16: true + + # ========================================== + # Optimizer + # ========================================== + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader + num_workers: 8 + dataloader_type: external + max_samples_per_sequence: null + + # ========================================== + # Megatron DDP + Distributed Optimizer + # ========================================== + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: false + use_megatron_fsdp: false + + use_distributed_optimizer: true + overlap_grad_reduce: true + overlap_param_gather: true + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: false + + ddp_bucket_size: 256000000 + use_fsdp2_fp32_param_optimizer: false + + ckpt_format: torch_dist + + gradient_accumulation_fusion: false + + check_for_nan_in_loss_and_grad: true + + # ========================================== + # Memory / Misc + # ========================================== + use_flash_attn: true + empty_unused_memory_level: 0 + + # Manual GC — align GC timing across ranks to avoid stragglers + manual_gc: true + manual_gc_interval: 1000 + + distributed_timeout_minutes: 60 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_ddp_te_fp8 + wandb_project: flux_12b_ddp_te_fp8 + log_throughput: true + wall_clock_step_timer: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # Profiler + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo — disabled for pure TE path + enable_primus_turbo: false + use_turbo_attention: false + + seed: 2025 + te_rng_tracker: true + + # torch.compile — selective stack compilation for TE spec + torch_compile: + enable: true + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: false + strategy: "stack" + replace_qk_rmsnorm: true + disable_inductor_cudagraphs: false diff --git a/examples/megatron/configs/MI300X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec.yaml b/examples/megatron/configs/MI300X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec.yaml new file mode 100644 index 000000000..a4375f9a6 --- /dev/null +++ b/examples/megatron/configs/MI300X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec.yaml @@ -0,0 +1,177 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — FSDP2 + Local Spec + BF16, VAE Resample Mode (MI300X) +# +# FSDP2 (ZeRO-2) BF16 training with the local spec provider and +# vae_latent_mode: resample. +# +# In resample mode, latents are re-drawn from stored mean+logvar via +# reparameterization (mean + exp(0.5*logvar) * randn) at every training step. +# This introduces per-step stochasticity in the VAE latents. +# +# Dataset must be an Energon pre-encoded dataset containing mean.pth and +# logvar.pth per sample. + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_fsdp2_local_bf16} + +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + # VAE latent normalization (required for resample mode) + vae_scale: 0.3611 + vae_shift: 0.1159 + + # Resample mode: re-draw latents from mean+logvar each step + vae_latent_mode: resample + + # RoPE: must be interleaved to match Flux's EmbedND doubled-frequency layout + rotary_interleaved: true + + # ========================================== + # PrimusTurboLocalSpecProvider + # ========================================== + transformer_impl: "local" + + # ========================================== + # ENERGON DATASET CONFIGURATION + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 1000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # Batch configuration + micro_batch_size: 32 + global_batch_size: 256 + seq_length: 512 # 256 img tokens + 256 text tokens (schnell) + + # Mixed precision + bf16: true + fp16: false + + # Optimizer settings + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + # Learning rate scheduler (warmup-hold, no decay) + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader settings + num_workers: 8 + dataloader_type: external + max_samples_per_sequence: null + + # ========================================== + # PyTorch FSDP2 Configuration (ZeRO-2) + # ========================================== + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: true + use_megatron_fsdp: false + + torch_fsdp2_reshard_after_forward: false # ZeRO-2 + + use_fsdp2_fp32_param_optimizer: true + + ckpt_format: torch_dist + + use_distributed_optimizer: false + + overlap_grad_reduce: false + overlap_param_gather: false + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: false + + gradient_accumulation_fusion: false + + # ========================================== + # Memory Optimizations + # ========================================== + + use_flash_attn: true + + fp8: null + + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 60 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_fsdp2_local_bf16 + wandb_project: flux_12b_fsdp2_local_bf16 + log_throughput: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # PyTorch Profiler + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo Configuration + enable_primus_turbo: true + use_turbo_attention: true + + seed: 2025 + + # Torch Compile + torch_compile: + enable: true + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: true diff --git a/examples/megatron/configs/MI300X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec_fp8.yaml b/examples/megatron/configs/MI300X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec_fp8.yaml new file mode 100644 index 000000000..64c2a6d1d --- /dev/null +++ b/examples/megatron/configs/MI300X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec_fp8.yaml @@ -0,0 +1,190 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — FSDP2 + Local Spec + FP8 + FP8 All-Gather (MI300X) +# +# FP8 training on the FSDP2 (ZeRO-3) path with: +# - FP8 training (tensorwise, local spec, dual FP8 output projection) +# - FP8 all-gather (keep weight in FP8 after FSDP2 all-gather) +# - BF16 master weight optimizer (BF16 params, FP32 optimizer states) +# - torch.compile enabled + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_fsdp2_local_fp8} + +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + # VAE latent normalization (required for resample mode) + vae_scale: 0.3611 + vae_shift: 0.1159 + + # Resample mode: re-draw latents from mean+logvar each step + vae_latent_mode: resample + + # RoPE: must be interleaved to match Flux's EmbedND doubled-frequency layout + rotary_interleaved: true + + # ========================================== + # PrimusTurboLocalSpecProvider + # ========================================== + transformer_impl: "local" + + # ========================================== + # ENERGON DATASET CONFIGURATION + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 1000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # Batch configuration + micro_batch_size: 52 + global_batch_size: 416 + seq_length: 512 # 256 img tokens + 256 text tokens (schnell) + + # Mixed precision + bf16: true + fp16: false + + # Optimizer settings + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + # Learning rate scheduler (warmup-hold, no decay) + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader settings + num_workers: 8 + dataloader_type: external + max_samples_per_sequence: null + + # ========================================== + # PyTorch FSDP2 Configuration (ZeRO-2) + # ========================================== + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: true + use_megatron_fsdp: false + + torch_fsdp2_reshard_after_forward: true # ZeRO-3 + use_fsdp2_fp8_all_gather: true + # fp8_all_gather_deq_requant: true # Dequant FP8->BF16 after AG, fresh dynamic requant downstream + # use_triton_ops: true # Use Triton @triton_op modulate/LN+modulate (compile-transparent) + fsdp_prefetch_depth: 1 + fp8_precompute_data_cache: false + optimizer_foreach: false + use_cpp_fp8_quantize: true + + # Optimizer mode: BF16 params + FP32 master weights + use_fsdp2_fp32_param_optimizer: false + use_fsdp2_bf16_master_weight_optimizer: true + + ckpt_format: torch_dist + + use_distributed_optimizer: false + + overlap_grad_reduce: false + overlap_param_gather: false + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: false + + gradient_accumulation_fusion: false + + # ========================================== + # FP8 Configuration — Tensorwise via Primus Turbo + # ========================================== + + use_flash_attn: true + + fp8: "e4m3" + # Dynamic (tensorwise) scaling: the FSDP2 path does not yet exercise the + # delayed amax allreduce, so use tensorwise here. Switch to + # `fp8_recipe: "delayed"` once FSDP2 + delayed is wired up. + fp8_recipe: "tensorwise" + fp8_wgrad: true + fp8_dot_product_attention: false + fp8_multi_head_attention: false + + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 60 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_fsdp2_local_fp8 + wandb_project: flux_12b_fsdp2_local_fp8 + log_throughput: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # PyTorch Profiler + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo Configuration + enable_primus_turbo: true + use_turbo_attention: true + use_dual_fp8_output_projection: true + + seed: 2025 + + # Torch Compile — compatible with Float8 local spec (per-module FP8) + torch_compile: + enable: true + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: true diff --git a/examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain.yaml b/examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain.yaml new file mode 100644 index 000000000..865cfe832 --- /dev/null +++ b/examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain.yaml @@ -0,0 +1,170 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 535M Pretraining Configuration (Pre-encoded Data Mode) +# +# This config demonstrates Flux 535M training with pre-encoded features. +# Pre-encoded mode is faster and recommended for production training. +# +# Usage: +# EXP=examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain.yaml \ +# bash examples/run_pretrain.sh + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_535m_pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_535m.yaml + + # Trainer class for diffusion models + trainer_class: FluxPretrainTrainer + + overrides: + # ============================================================================ + # Model Configuration + # ============================================================================ + + model_type: diffusion_model + + # ============================================================================ + # Dataset Configuration — Synthetic / Mock Data (default) + # ============================================================================ + # + # This 535M config defaults to in-memory synthetic data so it runs + # standalone for sanity checks, CI, and training-pipeline validation + # without a prepared Energon dataset. Samples are random tensors with + # Flux-correct shapes (latent_channels=16, T5 seq=512, CLIP pooled=768). + # + # To train on real data instead: set `mock_data: false` and point + # `data_path` at a prepared Energon dataset (see notes at the bottom). + mock_data: true + mock_dataset: + class: "primus.backends.megatron.data.synthetic.PreGeneratedMockFluxDataset" + params: + num_samples: 256 # In-memory synthetic samples (iterated cyclically) + image_size: 256 # 256 -> 32x32 latents (light/fast for testing) + # data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Batch configuration + micro_batch_size: 2 # Per-GPU batch size (adjust based on VRAM) + global_batch_size: 16 # Total batch size across all GPUs + seq_length: 4096 # Sequence length for latent features + + # DataLoader settings (used only with real Energon data, i.e. mock_data: false) + num_workers: 4 # Number of data loading workers per GPU + dataloader_type: external # Required for Energon dataloaders + + # ============================================================================ + # Training Parameters + # ============================================================================ + + # Total training steps + train_iters: 100000 # Total training iterations + eval_interval: 1000 # Evaluate every N steps + eval_iters: 50 # Number of evaluation iterations + + # Logging + log_interval: 10 # Log every N steps + tensorboard_dir: output/tensorboard/flux_535m_pretrain + log_throughput: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + + # Checkpointing (disabled) + save_interval: 5000 # Save checkpoint every N steps + save: null + load: null # Path to checkpoint for resuming (optional) + finetune: false + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Precision + bf16: true # Use bfloat16 (recommended for AMD MI300X) + fp16: false + + # ============================================================================ + # Optimizer Configuration + # ============================================================================ + + # Optimizer + optimizer: adam + lr: 1.0e-4 # Peak learning rate + min_lr: 1.0e-5 # Minimum learning rate + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.999 + adam_eps: 1.0e-8 + + # Gradient clipping + clip_grad: 1.0 + + # ============================================================================ + # Learning Rate Scheduler + # ============================================================================ + + lr_decay_style: cosine + lr_warmup_iters: 1000 # Warmup iterations + lr_decay_iters: 100000 # Total decay steps (typically = train_iters) + + # ============================================================================ + # Distributed Training Configuration + # ============================================================================ + + # Parallelism settings (Flux 535M fits on 1 GPU, but can use DP for speed) + tensor_model_parallel_size: 1 # Tensor parallelism (no need for 535M) + pipeline_model_parallel_size: 1 # Pipeline parallelism + + # Advanced settings + overlap_grad_reduce: true # Overlap gradient communication + use_flash_attn: true # Use Flash Attention 2 + distributed_timeout_minutes: 60 + + # ============================================================================ + # Seed and Reproducibility + # ============================================================================ + + seed: 42 + + # ============================================================================ + # Monitoring and Logging + # ============================================================================ + + wandb_project: flux_535m_pretrain + wandb_exp_name: flux_535m_preencoded + +# ============================================================================ +# Notes +# ============================================================================ +# +# Dataset Preparation: +# 1. Prepare pre-encoded dataset: +# tools/docker/primus data diffusion-encoded \ +# --source-type directory --input-dir /data/raw \ +# --output-dir /data/encoded --model-path black-forest-labs/FLUX.1-dev +# 2. Copy dataset template to output directory: +# cp primus/configs/data/megatron/diffusion/templates/dataset_preencoded.yaml \ +# /data/encoded/dataset.yaml +# 3. Run Energon indexing: +# energon prepare /data/encoded --num-workers 8 +# 4. Update data_path above to: /data/encoded/dataset.yaml +# +# For more information, see: +# - primus/configs/data/megatron/diffusion/README.md +# - examples/megatron/diffusion/README.md +# +# Single-node training (8 GPUs): +# EXP=examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain.yaml \ +# GPUS_PER_NODE=8 bash examples/run_pretrain.sh +# +# Multi-node training (4 nodes, 8 GPUs each): +# EXP=examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain.yaml \ +# NNODES=4 bash examples/run_slurm_pretrain.sh +# diff --git a/examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain_fp8.yaml b/examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain_fp8.yaml new file mode 100644 index 000000000..fa4aa8da7 --- /dev/null +++ b/examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain_fp8.yaml @@ -0,0 +1,199 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 535M FP8 Pretraining Configuration (Testing/Development) +# +# This config is for testing FP8 functionality with the minimal Flux 535M model +# before scaling to the full 12B model. Use this to validate: +# - FP8 setup and configuration +# - Numerical stability +# - Memory and speed improvements +# - Transformer Engine compatibility +# +# Target Hardware: Single AMD MI300X GPU with ROCm 6.0+ +# Requires: Transformer Engine 2.1.0+ with ROCm backend +# +# Usage: +# EXP=examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain_fp8.yaml \ +# GPUS_PER_NODE=1 bash examples/run_pretrain.sh + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_535m_pretrain_fp8} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_535m_fp8.yaml # Use FP8-enabled 535M config + + # Trainer class for diffusion models + trainer_class: FluxPretrainTrainer + + overrides: + # ============================================================================ + # Model Configuration + # ============================================================================ + + model_type: diffusion_model + + # ============================================================================ + # Dataset Configuration — Synthetic / Mock Data (default) + # ============================================================================ + # + # This 535M config defaults to in-memory synthetic data so it runs + # standalone for sanity checks, CI, and training-pipeline validation + # without a prepared Energon dataset. Samples are random tensors with + # Flux-correct shapes (latent_channels=16, T5 seq=512, CLIP pooled=768). + # + # To train on real data instead: set `mock_data: false` and point + # `data_path` at a prepared Energon dataset (see notes at the bottom). + mock_data: true + mock_dataset: + class: "primus.backends.megatron.data.synthetic.PreGeneratedMockFluxDataset" + params: + num_samples: 256 # In-memory synthetic samples (iterated cyclically) + image_size: 256 # 256 -> 32x32 latents (light/fast for testing) + # data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Batch configuration (can use larger batch with FP8) + micro_batch_size: 4 # Can increase from 2 to 4 with FP8 on 535M + global_batch_size: 32 # Small batch for quick testing + seq_length: 4096 # Sequence length for latent features + + # DataLoader settings (used only with real Energon data, i.e. mock_data: false) + num_workers: 4 # Number of data loading workers per GPU + dataloader_type: external # Required for Energon dataloaders + + # ============================================================================ + # Training Parameters (Quick Testing) + # ============================================================================ + + # Short training for validation + train_iters: 1000 # Just 1K steps for FP8 validation + eval_interval: 100 # Evaluate every 100 steps + eval_iters: 10 # Quick evaluation + + # Logging + log_interval: 10 # Log every N steps + tensorboard_dir: output/tensorboard/flux_535m_pretrain_fp8 + log_throughput: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + + # Checkpointing (disabled) + save_interval: 500 # Save more frequently for testing + save: null + load: null # Path to checkpoint for resuming (optional) + finetune: false + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Precision + bf16: true # Use bfloat16 for non-FP8 ops + fp16: false + + # ============================================================================ + # Optimizer Configuration + # ============================================================================ + + # Optimizer + optimizer: adam + lr: 1.0e-4 # Peak learning rate + min_lr: 1.0e-5 # Minimum learning rate + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.999 + adam_eps: 1.0e-8 + + # Gradient clipping (important for FP8 stability) + clip_grad: 1.0 + + # ============================================================================ + # Learning Rate Scheduler + # ============================================================================ + + lr_decay_style: cosine + lr_warmup_iters: 100 # Short warmup for testing + lr_decay_iters: 1000 # Match train_iters + + # ============================================================================ + # Distributed Training Configuration + # ============================================================================ + + # Single GPU configuration + tensor_model_parallel_size: 1 # No TP needed for 535M + pipeline_model_parallel_size: 1 # No PP needed + context_parallel_size: 1 # No CP needed + + # Distributed settings + use_distributed_optimizer: false # Not needed for single GPU + overlap_grad_reduce: false # Not applicable for single GPU + use_flash_attn: true # Use Flash Attention 2 + distributed_timeout_minutes: 60 + + # ============================================================================ + # Memory Optimization + # ============================================================================ + + # Activation checkpointing (not needed for 535M with FP8) + recompute_granularity: null # No recompute needed + recompute_method: null + recompute_num_layers: null + + # Sequence parallelism + sequence_parallel: false # Not needed for single GPU + + # ============================================================================ + # Seed and Reproducibility + # ============================================================================ + + seed: 42 + + # ============================================================================ + # Monitoring and Logging + # ============================================================================ + + wandb_project: flux_535m_pretrain_fp8 + wandb_exp_name: flux_535m_fp8_test + +# ============================================================================ +# Notes - FP8 Validation with 535M +# ============================================================================ +# +# Hardware Requirements (with FP8): +# - Minimum: 1× MI300X 192GB +# - Memory per GPU: ~3-5GB (vs ~7-10GB BF16) +# - Training time: Minutes +# +# Validation Checklist: +# [ ] Setup FP8 environment (see docs/04-technical-guides/diffusion-models/fp8_training.md) +# [ ] Verify TE FP8 support is available +# [ ] Run this config to validate FP8 training +# [ ] Check logs for NaN/Inf (should be none) +# [ ] Verify memory usage is ~50% of BF16 +# [ ] Verify training speed is 1.5-2x faster than BF16 +# [ ] Check loss decreases normally +# +# Expected Results: +# - Training completes 1000 steps in 5-15 minutes +# - No NaN/Inf in losses +# - Memory usage: ~3-5GB +# - Speed: ~10-50 steps/sec (depending on hardware) +# - Loss should decrease normally +# +# If validation passes, proceed to one of the 12B FP8 configs, e.g.: +# - flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml (TransformerEngine FP8) +# - flux_12b_fsdp2_energon_schnell_resample_local_spec_fp8.yaml (local-spec FP8) +# +# Troubleshooting: +# - If NaN/Inf: Check Transformer Engine FP8 support +# - If OOM: Reduce micro_batch_size +# - If slow: Verify ROCm FP8 tensor cores are being used +# - If unstable: Try fp8_wgrad: false in model config +# +# For more information: See docs/04-technical-guides/diffusion-models/fp8_training.md diff --git a/examples/megatron/configs/MI300X/diffusion/flux_535m_with_guidance_embed.yaml b/examples/megatron/configs/MI300X/diffusion/flux_535m_with_guidance_embed.yaml new file mode 100644 index 000000000..38f10f88d --- /dev/null +++ b/examples/megatron/configs/MI300X/diffusion/flux_535m_with_guidance_embed.yaml @@ -0,0 +1,58 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 535M with Guidance Embedding (Advanced Configuration) +# +# This config demonstrates Flux training with guidance embedding enabled. +# This is an OPTIONAL advanced feature that allows for faster single-pass CFG +# during inference, but requires training with guidance embedding enabled. +# +# IMPORTANT: Most users should use the standard flux_535m_pretrain.yaml config. +# Only use this if you specifically need guidance embedding support. +# +# Usage: +# EXP=examples/megatron/configs/MI300X/diffusion/flux_535m_with_guidance_embed.yaml \ +# bash examples/run_pretrain.sh + +# Extend standard 535M config +extends: + - flux_535m_pretrain.yaml + +modules: + pre_trainer: + overrides: + # ============================================================================ + # Guidance Embedding Configuration (ADVANCED) + # ============================================================================ + + # Enable guidance embedding for single-pass CFG + # This adds a learned MLPEmbedder layer that conditions on guidance scale + guidance_embed: true + + # Guidance scale used during training + # Model learns to adapt its predictions based on this scale + guidance_scale: 3.5 + + # ============================================================================ + # Notes + # ============================================================================ + # + # Training with guidance embedding: + # - Adds ~1-2% more parameters (guidance MLPEmbedder) + # - Allows single-pass CFG during inference (faster) + # - Requires more training data/iterations to converge + # - Model learns guidance as a conditioning signal + # + # Inference with guidance embedding: + # - Pipeline automatically detects guidance_embed layer + # - Uses single forward pass instead of batch doubling + # - ~2x faster CFG compared to explicit CFG + # - Guidance scale can be varied at inference time + # + # Standard approach (guidance_embed: false): + # - Default for most Primus training + # - Uses explicit CFG (batch doubling) at inference + # - More compatible with existing checkpoints + # - Slightly slower but more flexible + # + # See examples/megatron/diffusion/README.md for more details. diff --git a/examples/megatron/configs/MI300X/zebra_llama_1B-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_1B-pretrain.yaml index ca65bb754..f741c08fe 100644 --- a/examples/megatron/configs/MI300X/zebra_llama_1B-pretrain.yaml +++ b/examples/megatron/configs/MI300X/zebra_llama_1B-pretrain.yaml @@ -20,7 +20,7 @@ modules: log_avg_skip_iterations: 2 log_avg_reset_interval: 50 - train_iters: 100 + train_iters: 50 micro_batch_size: 8 global_batch_size: 64 diff --git a/examples/megatron/configs/MI300X/zebra_llama_1B_gdn-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_1B_gdn-pretrain.yaml new file mode 100644 index 000000000..a45aed5bb --- /dev/null +++ b/examples/megatron/configs/MI300X/zebra_llama_1B_gdn-pretrain.yaml @@ -0,0 +1,83 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:zebra_llama_1B_gdn-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + # model to run + model: zebra_llama_1B_gdn.yaml + overrides: + # log + wandb_project: "Primus_Zebra_Llama_1B_GDN_Pretrain" + stderr_sink_level: DEBUG + + eval_iters: 0 + + num_workers: 0 + create_attention_mask_in_dataloader: false + + profile: false + use_pytorch_profiler: false + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + + train_iters: 50 + micro_batch_size: 4 + global_batch_size: 32 + + seq_length: 8192 + max_position_embeddings: 8192 + original_max_position_embeddings: 8192 + + lr: 2.0e-4 + min_lr: 2.0e-5 + lr_warmup_iters: 200 + lr_decay_iters: 38147 + lr_decay_style: cosine + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: true + + # Pure GDN hybrid spec + spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'gdn_hybrid_stack_spec'] + + # Tokenizer + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: fla-hub/gla-1.3B-100B + + # parallel + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + overlap_grad_reduce: false + overlap_param_gather: false + gradient_accumulation_fusion: false + use_torch_fsdp2: false + use_distributed_optimizer: true + + # data + mock_data: true + train_data_path: null + valid_data_path: null + test_data_path: null + + # ckpt + finetune: false + auto_continue_train: false + load: null + save: ./output/zebra_llama_1B_gdn-pretrain + save_interval: 1000 + disable_last_saving: false + ckpt_format: torch + + # Turbo + enable_primus_turbo: false + use_turbo_attention: false + + # context parallel + context_parallel_size: 1 diff --git a/examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure-pretrain.yaml new file mode 100644 index 000000000..56b242bd7 --- /dev/null +++ b/examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure-pretrain.yaml @@ -0,0 +1,93 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:zebra_llama_1B_gdn_pure-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: zebra_llama_1B_gdn_pure.yaml + overrides: + wandb_project: "Primus_Zebra_Llama_1B_GDN_Pure_Pretrain" + stderr_sink_level: DEBUG + + eval_iters: 0 + + num_workers: 0 + create_attention_mask_in_dataloader: false + + profile: false + use_pytorch_profiler: false + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + + # Training schedule — matched to FLA (4 GPUs): + # batch=16, update=1, gpus=4, context=2048 + # global_batch = 16 * 4 = 64, tokens/step = 64 * 2048 = 131,072 + # total tokens ~= 76294 * 131072 ≈ 10B + train_iters: 50 + micro_batch_size: 16 + global_batch_size: 128 + + seq_length: 2048 + max_position_embeddings: 2048 + original_max_position_embeddings: 2048 + + # Optimizer — matched to FLA: + # lr=2e-4, cosine (min_lr_rate=0.1 → min_lr=2e-5) + # AdamW, beta1=0.9, beta2=0.95, weight_decay=0.01 + # max_grad_norm=1.0, warmup_steps=200 + # Both DeepSpeed ZeRO-2 and Megatron clip on ||avg_grad||. + # DeepSpeed pre-divides by dp_size before reduce_scatter(SUM), + # so its grad norm is on averaged gradients. Match FLA's 1.0 directly. + clip_grad: 1.0 + lr: 2.0e-4 + min_lr: 2.0e-5 + lr_warmup_iters: 200 + lr_decay_iters: 76294 + lr_decay_style: cosine + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: false + + # Pure GDN hybrid spec (hybrid_attention_ratio=0.0 → all GDN, no MLA) + spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'gdn_hybrid_stack_spec'] + + # Tokenizer + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: meta-llama/Llama-3.2-1B + + # Parallelism + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + overlap_grad_reduce: false + overlap_param_gather: false + gradient_accumulation_fusion: false + use_torch_fsdp2: false + use_distributed_optimizer: true + + # Data + mock_data: true + train_data_path: null + valid_data_path: null + test_data_path: null + + # Checkpoints + finetune: false + auto_continue_train: true + load: null + save: ./output/zebra_llama_1B_gdn_pure-pretrain + save_interval: 2048 + disable_last_saving: false + ckpt_format: torch + + # Turbo + enable_primus_turbo: false + use_turbo_attention: false + + # Context parallel + context_parallel_size: 1 diff --git a/examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure_100B-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure_100B-pretrain.yaml new file mode 100644 index 000000000..2f2e2048b --- /dev/null +++ b/examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure_100B-pretrain.yaml @@ -0,0 +1,498 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:zebra_llama_1B_gdn_pure_100B-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +# ───────────────────────────────────────────────────────────────────────────── +# Pure GDN 1B — 100B tokens on FineWeb-Edu (matched to FLA's +# `setup_and_train_gdn_pure_1B_100B.sh` so the loss curves can be compared +# iter-for-iter once we drive Primus from FLA's token order via PRIMUS_FLA_DATA). +# +# FLA's run: +# model: configs/gated_deltanet_1B_pure_100B.json (same arch as the +# 10B config we already replicate in zebra_llama_1B_gdn_pure.yaml) +# lr: 3e-4 (vs the 10B run's 2e-4 — bigger LR for the longer schedule) +# scheduler: cosine_with_min_lr (min_lr ≈ 0.1 × peak = 3e-5) +# warmup: 2000 iters (vs 200 on the 10B run) +# batch: 64 per GPU (vs 16 on the 10B run) +# update: 1 (no grad-accum) +# gpus: 8 +# → global_batch_size = 64 × 8 = 512 +# → tokens/iter = 512 × 2048 = 1,048,576 +# steps: 95368 (95368 × 1,048,576 ≈ 100B tokens) +# data: HuggingFaceFW/fineweb-edu, sample-100BT +# cache: .../data/HuggingFaceFW/fineweb-edu/sample-100BT/train +# (~364 GB on disk — pre-tokenized by FLA) +# +# Wall-time estimate on 8×MI300X with the full FLA-parity stack (~1.8 s/iter +# based on the GDN-hybrid 300M @ 2.2 s/iter rescaled for the larger model): +# 95368 × ~1.8 s ≈ 48 h (~2 days) +# ───────────────────────────────────────────────────────────────────────────── + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: zebra_llama_1B_gdn_pure.yaml + overrides: + wandb_project: "Primus_Zebra_Llama_1B_GDN_Pure_100B" + stderr_sink_level: DEBUG + + eval_iters: 0 + + # FLA's `train.sh` defaults `--dataloader_num_workers 32` and the 100B + # run does NOT override it. At b=64 mbs, ~131k tokens/GPU/iter, 32 + # workers (vs Primus's previous 8 carried over from the 300M run) keeps + # the DataLoader queue full so the GPU never starves between iters. + # PyTorch's default `prefetch_factor=2` is already what FLA uses. + num_workers: 32 + create_attention_mask_in_dataloader: false + + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + + # Perf: Megatron defaults to inserting dist.barrier() before every L1 timer + # measurement (~5-10/iter). Costs throughput, no correctness impact. + barrier_with_L1_time: false + + # ───────────────────────────────────────────────────────────────────── + # SPEED RECOVERY (lifted from 300M YAML which matched FLA at +0.27%): + # + # The profile of iters 15-16 showed 487 ms/iter spent in `aten::item` + # (CPU↔GPU sync points). The 300M run avoided this by disabling NaN + # checking and bumping log_interval to 100. Both are loss-identical + # — only affect logging and sync, not training math. + # + # check_for_nan_in_loss_and_grad: false + # • Megatron default = true, which fires `loss.item()` and an + # `isnan`/`isinf` validate_result on the loss EVERY iter + # (pretrain_mamba.py:178). Each is a CPU-GPU sync. Saves ~50 ms. + # • If loss ever spikes to NaN you'll still see it in the log + # output, just won't auto-rerun the step. + # + # log_interval: 32 + # • Default is 1, meaning every step we run timers, compute + # throughput, `loss.item()`, and print to stdout/log files — + # ~200 ms of sync+formatting per iter. Logging every 32 iters + # EXACTLY matches FLA's `--logging_steps 32` (legacy/training/ + # train.sh:55) so loss curves line up tick-for-tick with the + # FLA reference log when overlaid for parity verification. + # At ~7.5 s/iter that's a log line every 4 min — plenty. + # + # tensorboard_log_interval: 32 + # • Same logic for TB/wandb logging. Default is 1 = sync every + # iter. ~100 ms recovered. + # + # Combined expected speedup: ~300-400 ms/iter (7.6 s → ~7.2 s), + # all loss-identical. This is what the 300M YAML had. + # ───────────────────────────────────────────────────────────────────── + check_for_nan_in_loss_and_grad: false + # FLA's 100B launcher uses `logging=10` → `--logging_steps 10`. Matching + # exactly so the Primus loss log lines up tick-for-tick with FLA's + # reference `train_gdn_pure_1B_100B.log`. + log_interval: 10 + tensorboard_log_interval: 10 + + # ───────────────────────────────────────────────────────────────────── + # FLA-parity numerics (lifted verbatim from the validated 300M parity + # YAML — these are the *exact* settings that produced the iter-1 + # bit-perfect match and the < 0.5% late-training residual documented + # in docs/hybrid_models/GDN_FLA_PARITY.md). Skipping any of them re-introduces a known + # source of drift: + # + # layernorm_epsilon — Megatron's TransformerConfig defaults to 1e-5; + # FLA uses 1e-6. ~1% per-layer divergence. + # hidden_dropout — Megatron's TransformerConfig defaults these to + # attention_dropout 0.1 each (transformer_config.py L152). Even + # though the model YAML asks for 0, the EXP-level + # defaults leak through and we'd train with 10% + # dropout while FLA trains with 0. CRITICAL. + # no_persist_layer_norm — disables Apex's persistent buffer kernel + # which has slightly different rounding. + # ───────────────────────────────────────────────────────────────────── + layernorm_epsilon: 1.0e-6 + hidden_dropout: 0.0 + attention_dropout: 0.0 + no_persist_layer_norm: true + + # ── Training schedule ─ matches FLA EXACTLY (bit-perfect every knob): + # batch=64, grad_accum=1, gpus=8, seq=2048 + # global_batch_size = 64 × 8 = 512 + # tokens/iter = 512 × 2048 = 1,048,576 + # total tokens = 95368 × 1,048,576 ≈ 100 B + train_iters: 50 + micro_batch_size: 64 + global_batch_size: 512 + + seq_length: 2048 + max_position_embeddings: 2048 + original_max_position_embeddings: 2048 + + # ── Optimizer ─ exact bit-match to FLA's 100B CLI: + # --learning_rate 3.0e-4 + # --lr_scheduler_type cosine_with_min_lr + # --warmup_steps 2000 + # --optim adamw_torch_fused + # --adam_beta1=0.9 --adam_beta2=0.95 + # --weight_decay 0.01 + # --max_grad_norm 1.0 + # --seed 42 --bf16 + # + # CRITICAL: although FLA's train.sh does NOT pass --lr_scheduler_kwargs, + # FLA's run.py OVERRIDES it programmatically — see + # flash-linear-attention/legacy/training/run.py:96-97 : + # if args.lr_scheduler_type == 'cosine_with_min_lr': + # args.lr_scheduler_kwargs = {'min_lr_rate': 0.1} + # So FLA's lr decays from 3e-4 to 0.1*3e-4 = 3e-5 over 95368 iters, + # NOT to 0 as the HF default would imply. Megatron's cosine is + # lr = min_lr + 0.5*(1+cos(π*p))*(max_lr - min_lr) + # HF's cosine_with_min_lr is the algebraically identical form + # lr = peak * (factor*(1-min_lr_rate) + min_lr_rate) + # so setting `min_lr: 3.0e-5` here matches FLA's lr at every step, + # bit-exact. (Setting `min_lr: 0.0` — what we had earlier — would + # cause Primus's lr to undershoot FLA's in late training; the loss + # curves would diverge after ~iter 50000.) + # (Warmup is `init_lr + (max_lr-init_lr)*step/warmup` in Megatron and + # `peak*step/warmup` in HF — identical when init_lr=0, which is the + # Megatron default.) + clip_grad: 1.0 + lr: 3.0e-4 + min_lr: 3.0e-5 + lr_warmup_iters: 2000 + lr_decay_iters: 95368 + lr_decay_style: cosine + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: false + + # Match FLA's --seed 42 exactly. Primus's default is 1234; using FLA's + # seed minimises init-RNG drift between the two runs (still not bit- + # identical because Megatron and HF transformers walk parameters in + # different orders, but it eliminates the 12 % of variance that comes + # from the seed itself). + seed: 42 + + # ───────────────────────────────────────────────────────────────────── + # NO-TE spec — the validated 300M parity run uses this variant because + # FLA's reference is built on native PyTorch nn.Linear / RMSNorm. TE's + # ColumnParallelLinear / TENorm wrappers introduce small numerical + # differences (cast ordering, persistent buffers) that drift the loss + # by ~0.1% per layer. Using the no-TE spec aligns Megatron's layer + # numerics with FLA's. (See docs/hybrid_models/GDN_FLA_PARITY.md §A.) + # ───────────────────────────────────────────────────────────────────── + spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'gdn_hybrid_stack_spec_no_te'] + + # Tokenizer (cached locally — same path FLA's run uses) + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: meta-llama/Llama-3.2-1B + + # ── Parallelism ─ + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + + # ───────────────────────────────────────────────────────────────────── + # DDP / optimizer sharding — Megatron-FSDP (ZeRO-2 equivalent) + # ───────────────────────────────────────────────────────────────────── + # + # WHY we switched from ZeRO-1 to Megatron-FSDP (this run only): + # + # Profile of iter 15-16 (captured 2026-05-25) showed: + # + # GPU busy: ~43 % (TFLOP/s = 261, peak 1300) + # aten::empty 1500 calls × 2.3 ms = 3390 ms / iter + # aten::empty_like 1206 calls × 2.6 ms = 3114 ms / iter + # aten::empty_strided 1100 calls × 2.3 ms = 2575 ms / iter + # aten::mm dispatch 576 calls × 9.5 ms = 5485 ms / iter + # + # Normal aten::empty is *microseconds*. Ours is **1000× slower** + # because the PyTorch HIP allocator is constantly hitting hipMalloc/ + # hipFree at 81 % VRAM (156 GB / 192 GB). For comparison the 300M + # run runs at 30 % VRAM, the allocator cache stays warm, every + # aten::empty is sub-µs, and it achieves 643 TFLOP/s (49 % of peak). + # + # Megatron's own FSDP docs explicitly call out this exact failure + # mode (third_party/Megatron-LM/docs/user-guide/features/ + # custom_fsdp.md §5 line 187): + # + # "FSDP can lead to crashes of the PyTorch memory allocator cache, + # and a large number of cudaMalloc and cudaFree calls. This + # problem is challenging and can only be mitigated by avoiding + # frequent hits on the GPU memory limit." + # + # FLA's reference run uses DeepSpeed ZeRO-Stage-2 (ds_config.json + # "stage": 2, contiguous_gradients=true) which shards BOTH optimizer + # state AND gradients. Our prior config used Megatron's + # `use_distributed_optimizer=true` which is ZeRO-Stage-1 (only opt + # state sharded — full grads stay on every rank, ≈ 4.8 GB / rank). + # Megatron-FSDP with `data_parallel_sharding_strategy: optim_grads` + # is the bit-for-bit equivalent of FLA's setup: shards both opt-state + # AND grads, leaves params un-sharded. Memory savings ≈ 4.2 GB/rank + # (from 156 → ~152 GB, 81 % → 79 %), which along with removing the + # per-iter `empty_cache()` (see empty_unused_memory_level below) + # should give the allocator enough headroom to stop thrashing. + # + # Why optim_grads (ZeRO-2) NOT optim_grads_params (ZeRO-3): + # • optim_grads_params shards params too, but Megatron-FSDP only + # auto-wraps `TransformerLayer` instances as FSDP units. Our + # GDN layers are `MambaLayer` (a separate Megatron class), so + # they would NOT be FSDP-wrapped and would silently fall back + # to no-sharding for the GDN half of the model. + # • FLA itself uses ZeRO-2, not ZeRO-3. + # • Loss numerics are identical to FLA only with ZeRO-2 (ZeRO-3 + # introduces a tiny float-rounding difference in the per-shard + # param reconstruction). + # + # Compatibility caveats verified: + # ✓ Megatron-FSDP requires use_distributed_optimizer=true (kept) + # ✓ Requires gradient_accumulation_fusion=false (kept) + # ✓ Auto-handles CUDA_DEVICE_MAX_CONNECTIONS via Primus's + # env_patches.py:set_cuda_device_max_connections() — sets to + # 8 when use_megatron_fsdp=true (vs 1 for non-FSDP) + # ✓ GDN layer has reset_parameters() (megatron/core/ssm/ + # gated_delta_net.py:227), so meta-device init would work too + # (but we leave init_model_with_meta_device=false for safety) + # ✓ Prior segfault (overlap_grad_reduce=true + ZeRO-1) was NOT + # about FSDP — it was a separate Megatron bug in the ZeRO-1 + # bucket layout for heterogeneous Mamba params. FSDP uses its + # own (different) buffer code path. + # + # If FSDP causes any crash, revert these 2 lines to restore ZeRO-1: + # use_megatron_fsdp: false + # data_parallel_sharding_strategy: no_shard + # (Rest of config is FSDP-safe AND ZeRO-1-safe.) + # ───────────────────────────────────────────────────────────────────── + use_distributed_optimizer: true + # ── Megatron-FSDP ATTEMPTS (2026-05-25 + 2026-05-26) ───────────────── + # Take #1 (2026-05-25): `use_megatron_fsdp: true` + + # `data_parallel_sharding_strategy: optim_grads` crashed at iter-1 + # post-step NaN-check all_reduce with `Failed to CUDA calloc + # 268435456 bytes` (256 MiB). + # + # Root cause (diagnosed 2026-05-26): RCCL's lazy comm-init allocates + # BUFFSIZE × #channels per NCCL communicator. Defaults = + # 4 MiB × 64 channels = 256 MiB per comm. Megatron-FSDP creates + # extra comm groups (HSDP outer + DP inner) on top of the one + # ZeRO-1 needs, and at 99% VRAM there is no contiguous 256 MiB hole. + # + # Take #2 (2026-05-26): added NCCL channel clamp via launcher env vars + # (NCCL_MIN_NCHANNELS=1 NCCL_MAX_NCHANNELS=4 NCCL_NCHANNELS_PER_PEER=1 + # + ckpt_format: fsdp_dtensor since Megatron asserts it). + # Result: 500 iters complete, exit 0, ZERO crashes. + # + # Steady-state iter time: 2.84 s/iter (vs ZeRO-1's 2.75 s/iter). + # FSDP wins on non-flush iters (2.67 s vs 2.75 s) but loses on + # empty_cache_interval=32 flushes (3.18 s vs ~2.98 s) because FSDP + # has to re-allgather sharded params after the cache drop, while + # ZeRO-1 keeps full param replicas resident. + # + # NET (Take #2): FSDP 2.84 s/iter vs ZeRO-1 2.75 s/iter (90 ms slower). + # + # Take #3 (2026-05-26): Controlled 50-iter experiment EXP5 with + # `empty_cache_interval` raised from 32 to 128 (one flush per 128 + # iters instead of one per 32): + # - ZeRO-1 baseline (EXP0): 2768 ms steady-state + # - FSDP + flush@128 (EXP5): 2684 ms steady-state (-83 ms, -3.0%) + # - 100B run savings: 95k iters × 83 ms = 2.2 hours + # - Loss bit-identical to ZeRO-1 at iter 50: 11.7538 + # + # Why this works: between flushes, FSDP's per-iter allgather work is + # pipelined into the backward pass (Megatron-FSDP enables this + # internally even though `overlap_param_gather` is off). On flush + # iters FSDP still pays the re-allgather cost, but at 1-in-128 the + # amortized penalty is ~6 ms/iter vs the ~85 ms/iter saved. + # + # EXP6 (ZeRO-3 / optim_grads_params) FAILED at iter 1 with: + # "RuntimeError: Pointer argument cannot be accessed from Triton + # (cpu tensor?)" + # Because ZeRO-3 shards parameters across ranks, and the FLA Triton + # kernel cannot dereference a sharded DTensor. ZeRO-2 (optim_grads) + # is the maximum sharding compatible with our Triton GDN. + # + # FSDP working config + full experiment results: + # experiments/results/SYNTHESIS.md + # experiments/run_perf_exp.sh + # examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure_exp5-fsdp-rareflush.yaml + # ────────────────────────────────────────────────────────────────── + use_megatron_fsdp: true + data_parallel_sharding_strategy: optim_grads + # 2026-05-26 EXP7: FSDP path's overlap is a DIFFERENT code path than + # ZeRO-1's (which segfaulted in EXP4). With both flags on: + # ZeRO-1 baseline: 2768 ms/iter (+19.8% vs FLA) + # FSDP+rare flush (EXP5): 2684 ms/iter (+16.2% vs FLA) + # FSDP+overlap (EXP7): 2414 ms/iter (+4.5% vs FLA) ← winner + # 100B run savings: 95k iters × (2768-2414) ms = ~9.3 hours vs old prod + overlap_grad_reduce: true + overlap_param_gather: true + gradient_accumulation_fusion: false + ddp_average_in_collective: true + use_torch_fsdp2: false + + # ───────────────────────────────────────────────────────────────────── + # PyTorch profiler — already captured iter 15-17 trace which showed + # 487 ms/iter in `aten::item` (the 300M-parity log_interval/NaN-check + # fixes above target exactly this). Disabling for this run. + # + # To re-enable: set `profile: true`, `use_pytorch_profiler: true` + # and pick `profile_step_start`/`profile_step_end`. + # ───────────────────────────────────────────────────────────────────── + profile: false + use_pytorch_profiler: false + tensorboard_dir: output/amd/root/zebra_llama_1B_gdn_pure_100B-pretrain/tensorboard + + # ───────────────────────────────────────────────────────────────────── + # MEMORY: empty_unused_memory_level + empty_cache_interval + # ───────────────────────────────────────────────────────────────────── + # + # empty_unused_memory_level=1 → calls `torch.cuda.empty_cache()` at + # training.py:1759, IMMEDIATELY before optimizer.step() (which + # internally calls get_grad_norm_fp32() → torch.distributed.all_reduce + # in clip_grads.py:130). + # + # That all_reduce is where NCCL crashes if we disable empty_cache + # entirely: + # "Failed to CUDA calloc 4194304 bytes" inside ProcessGroupNCCL. + # NCCL's calloc bypasses PyTorch's allocator and goes straight to + # hipMalloc — at 81% VRAM there is no contiguous 4 MiB block free + # unless PyTorch first returns its cached pool to the driver. + # + # 2026-05-26 — PROFILE + FIX: + # PyTorch profiler attribution on iter 21 of the live 100B run + # measured 6,937 ms of the 7,650 ms iter (91%) in hipMalloc + hipFree, + # because the per-iter empty_cache() was thrashing ~250 cached + # blocks per iter against the ROCm driver (~17 ms per hipMalloc). + # + # FIX: keep `empty_unused_memory_level: 1` (Megatron's gate) but + # only fire empty_cache() every N iters via the Primus patch + # primus/backends/megatron/patches/empty_cache_interval_patches.py. + # The patch reads `empty_cache_interval` from this YAML (preferred), + # then falls back to the PRIMUS_EMPTY_CACHE_INTERVAL env var, then + # to the default 1 (passthrough). + # + # Iter 0 always fires empty_cache() so NCCL's first workspace alloc + # happens against a clean cache. Iters 1..N-1 skip it, iter N fires + # again, etc. NCCL workspace persists across the gap because once + # allocated it is not freed by PyTorch's allocator (NCCL owns the + # block). + # + # Measured impact (50-iter diag at empty_cache_interval=32, + # 2026-05-26): + # before fix: 7.65 s/iter ( 262 TFLOP/s/GPU, 20% of peak) + # after fix: 2.79 s/iter ( 727 TFLOP/s/GPU, 56% of peak) + # speedup: 2.74× + # loss curve: iter-1 bit-identical to baseline (no math impact) + # 100B wall: was 8.4 days → now 3.08 days (~5.3 days saved) + # + # Knob choice: + # - 1 = original per-iter behaviour (safest, slowest) + # - 32 = our default (best tested point; ~2.8× speedup) + # - 64+ = even fewer flushes; tested briefly, slightly faster + # between flushes but bigger spike on the trigger iter + # - 0 = NEVER flush (CRASHES at iter-1 NCCL alloc; do not use) + # ───────────────────────────────────────────────────────────────────── + empty_unused_memory_level: 1 + empty_cache_interval: 128 # 2026-05-26 EXP5: 32→128 saves 83 ms/iter (-3% wall, -2.2 h on 100B run) + + # ───────────────────────────────────────────────────────────────────── + # FLA runtime knobs — declarative YAML surface for the PRIMUS_FLA_* / + # PRIMUS_FUSED_CE* env vars (consumed by the + # primus.backends.megatron.patches.fla_runtime_patches patch which + # re-exports them as env vars at phase="build_args"). These + # exactly mirror the values the legacy launcher script sets via + # `export PRIMUS_FLA_*` — keeping them here makes the YAML the + # single source of truth. Env vars set on the launcher still win + # over the YAML (backward compat). + # ───────────────────────────────────────────────────────────────────── + use_fla_fused_swiglu: true # FLA Triton SwiGLU (matches fla.modules.swiglu) + use_fla_fused_rmsnorm: true # FLA Triton RMSNorm + FusedRMSNormGated + use_fla_fused_gated_norm: true # Same env var (PRIMUS_FLA_NORM); kept explicit for clarity + use_fla_short_conv: true # FLA Triton causal_conv1d for GDN short-conv + # ── Dataset source selector ─────────────────────────────────────── + # use_fla_data + fla_cache_dir together pick the data path: + # use_fla_data=false (or fla_cache_dir empty): vanilla Megatron + # GPTDataset reading train_data_path (.bin/.idx). Megatron's + # native sampler order — fine for standalone runs, NOT bit- + # comparable to FLA's loss curve. + # use_fla_data=true AND fla_cache_dir=: replace + # GPTDataset with tools/fla_order_dataset.FLAOrderGPTDataset + # which emits tokens in the exact same order as FLA's HF + # DistributedSampler (eliminates the data-ordering drift). + # The launcher script sets PRIMUS_FLA_CACHE_DIR; if you launch + # without it, uncomment fla_cache_dir below to make this YAML + # self-contained. + use_fla_data: true # PRIMUS_FLA_DATA + # fla_cache_dir: /home//flash-linear-attention/legacy/training/data/HuggingFaceFW/fineweb-edu/sample-100BT/train + fused_ce_mode: 1 # 1=chunked FLA FusedLinearCrossEntropyLoss + fused_ce_chunks: 32 # Chunk count for FLA fused CE (32 = launcher default) + + # ───────────────────────────────────────────────────────────────────── + # SwiGLU path — disable Megatron's bias-fused SwiGLU so the MLP + # actually routes through FLA's Triton SwiGLU (the PRIMUS_FLA_SWIGLU=1 + # env var's intent). + # + # `primus/configs/models/megatron/language_model.yaml` sets + # `bias_swiglu_fusion: true`, which Megatron translates to + # `bias_activation_fusion=true` (see arguments.py L1610-1613) and + # forces the MLP forward through `bias_swiglu_impl` (mlp.py L308). + # That code path NEVER checks `self._use_fla_swiglu` — meaning + # PRIMUS_FLA_SWIGLU=1 has been silently inert ever since. The + # backward for `bias_swiglu_impl` materialises a ~4 GB grad + # temporary at b=64, s=2048, intermediate=8192 — exactly the + # iter-1 OOM we hit (HIPBLAS_STATUS_ALLOC_FAILED → 4.00 GiB request). + # + # Setting this to false: + # • routes MLP through the `_use_fla_swiglu` branch (mlp.py L323) + # • uses FLA's `swiglu` Triton kernel (the kernel we actually want + # for parity — same one FLA's reference run uses) + # • saves ~4 GB of backward-pass memory + # All bit-perfect parity guarantees are preserved (this is in fact + # what we were claiming to do all along — see docs/hybrid_models/GDN_FLA_PARITY.md §B + # patch 03 "mlp-fla-swiglu"). + # ───────────────────────────────────────────────────────────────────── + bias_swiglu_fusion: false + + # NOTE: `recompute_granularity` is intentionally NOT set here. It's a + # no-op for pure GDN — MambaBlock.forward()/HybridStack.forward() are + # plain `for layer in self.layers:` loops with zero recompute logic + # (verified in third_party/Megatron-LM/megatron/core/ssm/mamba_block.py + # and primus/backends/megatron/core/models/hybrid/hybrid_block.py). + # The actual fix for the iter-1 OOM is PRIMUS_FUSED_CE_CHUNKS=32 set + # by the launcher — see launch_gdn_pure_1B_100B.sh and + # third_party/Megatron-LM/megatron/core/models/mamba/mamba_model.py. + + # Data + mock_data: true + train_data_path: null + valid_data_path: null + test_data_path: null + + # ── Checkpoints ─ + finetune: false + auto_continue_train: true + load: null + # Checkpoint save path + save: ./output/zebra_llama_1B_gdn_pure_100B-pretrain + # FLA's 100B launcher passes `save=99999` (HF `--save_steps 99999`), + # meaning over 95368 iters HF never writes an intermediate ckpt — only + # the final one at end-of-training (then `--save_total_limit 3` keeps + # the last 3 across all runs in the dir). + # + # For Primus we cannot match `99999` literally because + # `auto_continue_train: true` needs SOME periodic checkpoint to be able + # to resume mid-flight from a crash on a 48 h run. Set save_interval to + # 10000 (~10 B tokens, ~10 ckpts over the run) — closer in spirit to + # FLA's "almost never save" and ~2× lighter than the previous 5000 + # setting. `disable_last_saving: false` still guarantees the very last + # iter is saved exactly like FLA does at end-of-training. + save_interval: 10000 + disable_last_saving: false + ckpt_format: fsdp_dtensor # Megatron-FSDP requires this when use_megatron_fsdp=true + + # Turbo (kept off for the comparison run; can flip on later) + enable_primus_turbo: false + use_turbo_attention: false + + # Context parallel + context_parallel_size: 1 diff --git a/examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure_exp7-fsdp-overlap.yaml b/examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure_exp7-fsdp-overlap.yaml new file mode 100644 index 000000000..09d67ba9d --- /dev/null +++ b/examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure_exp7-fsdp-overlap.yaml @@ -0,0 +1,511 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:zebra_llama_1B_gdn_pure_exp7-fsdp-overlap} +workspace: ${PRIMUS_WORKSPACE:./output} + +# ───────────────────────────────────────────────────────────────────────────── +# Pure GDN 1B — 100B tokens on FineWeb-Edu (matched to FLA's +# `setup_and_train_gdn_pure_1B_100B.sh` so the loss curves can be compared +# iter-for-iter once we drive Primus from FLA's token order via PRIMUS_FLA_DATA). +# +# FLA's run: +# model: configs/gated_deltanet_1B_pure_100B.json (same arch as the +# 10B config we already replicate in zebra_llama_1B_gdn_pure.yaml) +# lr: 3e-4 (vs the 10B run's 2e-4 — bigger LR for the longer schedule) +# scheduler: cosine_with_min_lr (min_lr ≈ 0.1 × peak = 3e-5) +# warmup: 2000 iters (vs 200 on the 10B run) +# batch: 64 per GPU (vs 16 on the 10B run) +# update: 1 (no grad-accum) +# gpus: 8 +# → global_batch_size = 64 × 8 = 512 +# → tokens/iter = 512 × 2048 = 1,048,576 +# steps: 95368 (95368 × 1,048,576 ≈ 100B tokens) +# data: HuggingFaceFW/fineweb-edu, sample-100BT +# cache: .../data/HuggingFaceFW/fineweb-edu/sample-100BT/train +# (~364 GB on disk — pre-tokenized by FLA) +# +# Wall-time estimate on 8×MI300X with the full FLA-parity stack (~1.8 s/iter +# based on the GDN-hybrid 300M @ 2.2 s/iter rescaled for the larger model): +# 95368 × ~1.8 s ≈ 48 h (~2 days) +# ───────────────────────────────────────────────────────────────────────────── + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: zebra_llama_1B_gdn_pure.yaml + overrides: + wandb_project: "Primus_Zebra_Llama_1B_GDN_Pure_100B" + stderr_sink_level: DEBUG + + eval_iters: 0 + + # FLA's `train.sh` defaults `--dataloader_num_workers 32` and the 100B + # run does NOT override it. At b=64 mbs, ~131k tokens/GPU/iter, 32 + # workers (vs Primus's previous 8 carried over from the 300M run) keeps + # the DataLoader queue full so the GPU never starves between iters. + # PyTorch's default `prefetch_factor=2` is already what FLA uses. + num_workers: 32 + create_attention_mask_in_dataloader: false + + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + + # Perf: Megatron defaults to inserting dist.barrier() before every L1 timer + # measurement (~5-10/iter). Costs throughput, no correctness impact. + barrier_with_L1_time: false + + # ───────────────────────────────────────────────────────────────────── + # SPEED RECOVERY (lifted from 300M YAML which matched FLA at +0.27%): + # + # The profile of iters 15-16 showed 487 ms/iter spent in `aten::item` + # (CPU↔GPU sync points). The 300M run avoided this by disabling NaN + # checking and bumping log_interval to 100. Both are loss-identical + # — only affect logging and sync, not training math. + # + # check_for_nan_in_loss_and_grad: false + # • Megatron default = true, which fires `loss.item()` and an + # `isnan`/`isinf` validate_result on the loss EVERY iter + # (pretrain_mamba.py:178). Each is a CPU-GPU sync. Saves ~50 ms. + # • If loss ever spikes to NaN you'll still see it in the log + # output, just won't auto-rerun the step. + # + # log_interval: 32 + # • Default is 1, meaning every step we run timers, compute + # throughput, `loss.item()`, and print to stdout/log files — + # ~200 ms of sync+formatting per iter. Logging every 32 iters + # EXACTLY matches FLA's `--logging_steps 32` (legacy/training/ + # train.sh:55) so loss curves line up tick-for-tick with the + # FLA reference log when overlaid for parity verification. + # At ~7.5 s/iter that's a log line every 4 min — plenty. + # + # tensorboard_log_interval: 32 + # • Same logic for TB/wandb logging. Default is 1 = sync every + # iter. ~100 ms recovered. + # + # Combined expected speedup: ~300-400 ms/iter (7.6 s → ~7.2 s), + # all loss-identical. This is what the 300M YAML had. + # ───────────────────────────────────────────────────────────────────── + check_for_nan_in_loss_and_grad: false + # FLA's 100B launcher uses `logging=10` → `--logging_steps 10`. Matching + # exactly so the Primus loss log lines up tick-for-tick with FLA's + # reference `train_gdn_pure_1B_100B.log`. + log_interval: 10 + tensorboard_log_interval: 10 + + # ───────────────────────────────────────────────────────────────────── + # FLA-parity numerics (lifted verbatim from the validated 300M parity + # YAML — these are the *exact* settings that produced the iter-1 + # bit-perfect match and the < 0.5% late-training residual documented + # in docs/hybrid_models/GDN_FLA_PARITY.md). Skipping any of them re-introduces a known + # source of drift: + # + # layernorm_epsilon — Megatron's TransformerConfig defaults to 1e-5; + # FLA uses 1e-6. ~1% per-layer divergence. + # hidden_dropout — Megatron's TransformerConfig defaults these to + # attention_dropout 0.1 each (transformer_config.py L152). Even + # though the model YAML asks for 0, the EXP-level + # defaults leak through and we'd train with 10% + # dropout while FLA trains with 0. CRITICAL. + # no_persist_layer_norm — disables Apex's persistent buffer kernel + # which has slightly different rounding. + # ───────────────────────────────────────────────────────────────────── + layernorm_epsilon: 1.0e-6 + hidden_dropout: 0.0 + attention_dropout: 0.0 + no_persist_layer_norm: true + + # ── Training schedule ─ matches FLA EXACTLY (bit-perfect every knob): + # batch=64, grad_accum=1, gpus=8, seq=2048 + # global_batch_size = 64 × 8 = 512 + # tokens/iter = 512 × 2048 = 1,048,576 + # total tokens = 95368 × 1,048,576 ≈ 100 B + train_iters: 50 # PERF EXP: 50 iters to measure steady-state + micro_batch_size: 64 + global_batch_size: 512 + + seq_length: 2048 + max_position_embeddings: 2048 + original_max_position_embeddings: 2048 + + # ── Optimizer ─ exact bit-match to FLA's 100B CLI: + # --learning_rate 3.0e-4 + # --lr_scheduler_type cosine_with_min_lr + # --warmup_steps 2000 + # --optim adamw_torch_fused + # --adam_beta1=0.9 --adam_beta2=0.95 + # --weight_decay 0.01 + # --max_grad_norm 1.0 + # --seed 42 --bf16 + # + # CRITICAL: although FLA's train.sh does NOT pass --lr_scheduler_kwargs, + # FLA's run.py OVERRIDES it programmatically — see + # flash-linear-attention/legacy/training/run.py:96-97 : + # if args.lr_scheduler_type == 'cosine_with_min_lr': + # args.lr_scheduler_kwargs = {'min_lr_rate': 0.1} + # So FLA's lr decays from 3e-4 to 0.1*3e-4 = 3e-5 over 95368 iters, + # NOT to 0 as the HF default would imply. Megatron's cosine is + # lr = min_lr + 0.5*(1+cos(π*p))*(max_lr - min_lr) + # HF's cosine_with_min_lr is the algebraically identical form + # lr = peak * (factor*(1-min_lr_rate) + min_lr_rate) + # so setting `min_lr: 3.0e-5` here matches FLA's lr at every step, + # bit-exact. (Setting `min_lr: 0.0` — what we had earlier — would + # cause Primus's lr to undershoot FLA's in late training; the loss + # curves would diverge after ~iter 50000.) + # (Warmup is `init_lr + (max_lr-init_lr)*step/warmup` in Megatron and + # `peak*step/warmup` in HF — identical when init_lr=0, which is the + # Megatron default.) + clip_grad: 1.0 + lr: 3.0e-4 + min_lr: 3.0e-5 + lr_warmup_iters: 2000 + lr_decay_iters: 95368 + lr_decay_style: cosine + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: false + + # Match FLA's --seed 42 exactly. Primus's default is 1234; using FLA's + # seed minimises init-RNG drift between the two runs (still not bit- + # identical because Megatron and HF transformers walk parameters in + # different orders, but it eliminates the 12 % of variance that comes + # from the seed itself). + seed: 42 + + # ───────────────────────────────────────────────────────────────────── + # NO-TE spec — the validated 300M parity run uses this variant because + # FLA's reference is built on native PyTorch nn.Linear / RMSNorm. TE's + # ColumnParallelLinear / TENorm wrappers introduce small numerical + # differences (cast ordering, persistent buffers) that drift the loss + # by ~0.1% per layer. Using the no-TE spec aligns Megatron's layer + # numerics with FLA's. (See docs/hybrid_models/GDN_FLA_PARITY.md §A.) + # ───────────────────────────────────────────────────────────────────── + spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'gdn_hybrid_stack_spec_no_te'] + + # Tokenizer (cached locally — same path FLA's run uses) + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: meta-llama/Llama-3.2-1B + + # ── Parallelism ─ + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + + # ───────────────────────────────────────────────────────────────────── + # DDP / optimizer sharding — Megatron-FSDP (ZeRO-2 equivalent) + # ───────────────────────────────────────────────────────────────────── + # + # WHY we switched from ZeRO-1 to Megatron-FSDP (this run only): + # + # Profile of iter 15-16 (captured 2026-05-25) showed: + # + # GPU busy: ~43 % (TFLOP/s = 261, peak 1300) + # aten::empty 1500 calls × 2.3 ms = 3390 ms / iter + # aten::empty_like 1206 calls × 2.6 ms = 3114 ms / iter + # aten::empty_strided 1100 calls × 2.3 ms = 2575 ms / iter + # aten::mm dispatch 576 calls × 9.5 ms = 5485 ms / iter + # + # Normal aten::empty is *microseconds*. Ours is **1000× slower** + # because the PyTorch HIP allocator is constantly hitting hipMalloc/ + # hipFree at 81 % VRAM (156 GB / 192 GB). For comparison the 300M + # run runs at 30 % VRAM, the allocator cache stays warm, every + # aten::empty is sub-µs, and it achieves 643 TFLOP/s (49 % of peak). + # + # Megatron's own FSDP docs explicitly call out this exact failure + # mode (third_party/Megatron-LM/docs/user-guide/features/ + # custom_fsdp.md §5 line 187): + # + # "FSDP can lead to crashes of the PyTorch memory allocator cache, + # and a large number of cudaMalloc and cudaFree calls. This + # problem is challenging and can only be mitigated by avoiding + # frequent hits on the GPU memory limit." + # + # FLA's reference run uses DeepSpeed ZeRO-Stage-2 (ds_config.json + # "stage": 2, contiguous_gradients=true) which shards BOTH optimizer + # state AND gradients. Our prior config used Megatron's + # `use_distributed_optimizer=true` which is ZeRO-Stage-1 (only opt + # state sharded — full grads stay on every rank, ≈ 4.8 GB / rank). + # Megatron-FSDP with `data_parallel_sharding_strategy: optim_grads` + # is the bit-for-bit equivalent of FLA's setup: shards both opt-state + # AND grads, leaves params un-sharded. Memory savings ≈ 4.2 GB/rank + # (from 156 → ~152 GB, 81 % → 79 %), which along with removing the + # per-iter `empty_cache()` (see empty_unused_memory_level below) + # should give the allocator enough headroom to stop thrashing. + # + # Why optim_grads (ZeRO-2) NOT optim_grads_params (ZeRO-3): + # • optim_grads_params shards params too, but Megatron-FSDP only + # auto-wraps `TransformerLayer` instances as FSDP units. Our + # GDN layers are `MambaLayer` (a separate Megatron class), so + # they would NOT be FSDP-wrapped and would silently fall back + # to no-sharding for the GDN half of the model. + # • FLA itself uses ZeRO-2, not ZeRO-3. + # • Loss numerics are identical to FLA only with ZeRO-2 (ZeRO-3 + # introduces a tiny float-rounding difference in the per-shard + # param reconstruction). + # + # Compatibility caveats verified: + # ✓ Megatron-FSDP requires use_distributed_optimizer=true (kept) + # ✓ Requires gradient_accumulation_fusion=false (kept) + # ✓ Auto-handles CUDA_DEVICE_MAX_CONNECTIONS via Primus's + # env_patches.py:set_cuda_device_max_connections() — sets to + # 8 when use_megatron_fsdp=true (vs 1 for non-FSDP) + # ✓ GDN layer has reset_parameters() (megatron/core/ssm/ + # gated_delta_net.py:227), so meta-device init would work too + # (but we leave init_model_with_meta_device=false for safety) + # ✓ Prior segfault (overlap_grad_reduce=true + ZeRO-1) was NOT + # about FSDP — it was a separate Megatron bug in the ZeRO-1 + # bucket layout for heterogeneous Mamba params. FSDP uses its + # own (different) buffer code path. + # + # If FSDP causes any crash, revert these 2 lines to restore ZeRO-1: + # use_megatron_fsdp: false + # data_parallel_sharding_strategy: no_shard + # (Rest of config is FSDP-safe AND ZeRO-1-safe.) + # ───────────────────────────────────────────────────────────────────── + use_distributed_optimizer: true + # ── Megatron-FSDP ATTEMPTS (2026-05-25 + 2026-05-26) ───────────────── + # Take #1 (2026-05-25): `use_megatron_fsdp: true` + + # `data_parallel_sharding_strategy: optim_grads` crashed at iter-1 + # post-step NaN-check all_reduce with `Failed to CUDA calloc + # 268435456 bytes` (256 MiB). + # + # Root cause (diagnosed 2026-05-26): RCCL's lazy comm-init allocates + # BUFFSIZE × #channels per NCCL communicator. Defaults = + # 4 MiB × 64 channels = 256 MiB per comm. Megatron-FSDP creates + # extra comm groups (HSDP outer + DP inner) on top of the one + # ZeRO-1 needs, and at 99% VRAM there is no contiguous 256 MiB hole. + # + # Take #2 (2026-05-26): added NCCL channel clamp via launcher env vars + # (NCCL_MIN_NCHANNELS=1 NCCL_MAX_NCHANNELS=4 NCCL_NCHANNELS_PER_PEER=1 + # + ckpt_format: fsdp_dtensor since Megatron asserts it). + # Result: 500 iters complete, exit 0, ZERO crashes. + # + # Steady-state iter time: 2.84 s/iter (vs ZeRO-1's 2.75 s/iter). + # FSDP wins on non-flush iters (2.67 s vs 2.75 s) but loses on + # empty_cache_interval=32 flushes (3.18 s vs ~2.98 s) because FSDP + # has to re-allgather sharded params after the cache drop, while + # ZeRO-1 keeps full param replicas resident. + # + # NET (Take #2): FSDP 2.84 s/iter vs ZeRO-1 2.75 s/iter (90 ms slower). + # + # Take #3 (2026-05-26): Controlled 50-iter experiment EXP5 with + # `empty_cache_interval` raised from 32 to 128 (one flush per 128 + # iters instead of one per 32): + # - ZeRO-1 baseline (EXP0): 2768 ms steady-state + # - FSDP + flush@128 (EXP5): 2684 ms steady-state (-83 ms, -3.0%) + # - 100B run savings: 95k iters × 83 ms = 2.2 hours + # - Loss bit-identical to ZeRO-1 at iter 50: 11.7538 + # + # Why this works: between flushes, FSDP's per-iter allgather work is + # pipelined into the backward pass (Megatron-FSDP enables this + # internally even though `overlap_param_gather` is off). On flush + # iters FSDP still pays the re-allgather cost, but at 1-in-128 the + # amortized penalty is ~6 ms/iter vs the ~85 ms/iter saved. + # + # EXP6 (ZeRO-3 / optim_grads_params) FAILED at iter 1 with: + # "RuntimeError: Pointer argument cannot be accessed from Triton + # (cpu tensor?)" + # Because ZeRO-3 shards parameters across ranks, and the FLA Triton + # kernel cannot dereference a sharded DTensor. ZeRO-2 (optim_grads) + # is the maximum sharding compatible with our Triton GDN. + # + # FSDP working config + full experiment results: + # experiments/results/SYNTHESIS.md + # experiments/run_perf_exp.sh + # examples/megatron/configs/MI300X/zebra_llama_1B_gdn_pure_exp5-fsdp-rareflush.yaml + # ────────────────────────────────────────────────────────────────── + use_megatron_fsdp: true + data_parallel_sharding_strategy: optim_grads + # EXP7: FSDP path's overlap_grad_reduce is DIFFERENT code than ZeRO-1's + # (which segfaulted in EXP4). FSDP uses ReduceScatter into its own buffer. + # If this works, we save ~152 ms/iter (NCCL all-reduce → overlapped with backward). + overlap_grad_reduce: true # EXP7: enable ReduceScatter overlap (FSDP path, not ZeRO-1 path) + overlap_param_gather: true # EXP7: enable AllGather overlap (free with overlap_grad_reduce) + gradient_accumulation_fusion: false + ddp_average_in_collective: true + use_torch_fsdp2: false + + # ───────────────────────────────────────────────────────────────────── + # PyTorch profiler — already captured iter 15-17 trace which showed + # 487 ms/iter in `aten::item` (the 300M-parity log_interval/NaN-check + # fixes above target exactly this). Disabling for this run. + # + # To re-enable: set `profile: true`, `use_pytorch_profiler: true` + # and pick `profile_step_start`/`profile_step_end`. + # ───────────────────────────────────────────────────────────────────── + profile: false + use_pytorch_profiler: false + tensorboard_dir: /tmp/primus_perf_exp7-fsdp-overlap_tb + + # ───────────────────────────────────────────────────────────────────── + # MEMORY: empty_unused_memory_level + empty_cache_interval + # ───────────────────────────────────────────────────────────────────── + # + # empty_unused_memory_level=1 → calls `torch.cuda.empty_cache()` at + # training.py:1759, IMMEDIATELY before optimizer.step() (which + # internally calls get_grad_norm_fp32() → torch.distributed.all_reduce + # in clip_grads.py:130). + # + # That all_reduce is where NCCL crashes if we disable empty_cache + # entirely: + # "Failed to CUDA calloc 4194304 bytes" inside ProcessGroupNCCL. + # NCCL's calloc bypasses PyTorch's allocator and goes straight to + # hipMalloc — at 81% VRAM there is no contiguous 4 MiB block free + # unless PyTorch first returns its cached pool to the driver. + # + # 2026-05-26 — PROFILE + FIX: + # PyTorch profiler attribution on iter 21 of the live 100B run + # measured 6,937 ms of the 7,650 ms iter (91%) in hipMalloc + hipFree, + # because the per-iter empty_cache() was thrashing ~250 cached + # blocks per iter against the ROCm driver (~17 ms per hipMalloc). + # + # FIX: keep `empty_unused_memory_level: 1` (Megatron's gate) but + # only fire empty_cache() every N iters via the Primus patch + # primus/backends/megatron/patches/empty_cache_interval_patches.py. + # The patch reads `empty_cache_interval` from this YAML (preferred), + # then falls back to the PRIMUS_EMPTY_CACHE_INTERVAL env var, then + # to the default 1 (passthrough). + # + # Iter 0 always fires empty_cache() so NCCL's first workspace alloc + # happens against a clean cache. Iters 1..N-1 skip it, iter N fires + # again, etc. NCCL workspace persists across the gap because once + # allocated it is not freed by PyTorch's allocator (NCCL owns the + # block). + # + # Measured impact (50-iter diag at empty_cache_interval=32, + # 2026-05-26): + # before fix: 7.65 s/iter ( 262 TFLOP/s/GPU, 20% of peak) + # after fix: 2.79 s/iter ( 727 TFLOP/s/GPU, 56% of peak) + # speedup: 2.74× + # loss curve: iter-1 bit-identical to baseline (no math impact) + # 100B wall: was 8.4 days → now 3.08 days (~5.3 days saved) + # + # Knob choice: + # - 1 = original per-iter behaviour (safest, slowest) + # - 32 = our default (best tested point; ~2.8× speedup) + # - 64+ = even fewer flushes; tested briefly, slightly faster + # between flushes but bigger spike on the trigger iter + # - 0 = NEVER flush (CRASHES at iter-1 NCCL alloc; do not use) + # ───────────────────────────────────────────────────────────────────── + empty_unused_memory_level: 1 + empty_cache_interval: 128 # 2026-05-26 EXP5: 32→128 saves 83 ms/iter (-3% wall, -2.2 h on 100B run) + + # ───────────────────────────────────────────────────────────────────── + # FLA runtime knobs — declarative YAML surface for the PRIMUS_FLA_* / + # PRIMUS_FUSED_CE* env vars (consumed by the + # primus.backends.megatron.patches.fla_runtime_patches patch which + # re-exports them as env vars at phase="build_args"). These + # exactly mirror the values the legacy launcher script sets via + # `export PRIMUS_FLA_*` — keeping them here makes the YAML the + # single source of truth. Env vars set on the launcher still win + # over the YAML (backward compat). + # ───────────────────────────────────────────────────────────────────── + use_fla_fused_swiglu: true # FLA Triton SwiGLU (matches fla.modules.swiglu) + use_fla_fused_rmsnorm: true # FLA Triton RMSNorm + FusedRMSNormGated + use_fla_fused_gated_norm: true # Same env var (PRIMUS_FLA_NORM); kept explicit for clarity + use_fla_short_conv: true # FLA Triton causal_conv1d for GDN short-conv + # ── Dataset source selector ─────────────────────────────────────── + # use_fla_data + fla_cache_dir together pick the data path: + # use_fla_data=false (or fla_cache_dir empty): vanilla Megatron + # GPTDataset reading train_data_path (.bin/.idx). This is + # Megatron's native sampler order — fine for standalone runs + # but NOT bit-comparable to FLA's loss curve. + # use_fla_data=true AND fla_cache_dir=: replace + # GPTDataset with tools/fla_order_dataset.FLAOrderGPTDataset + # which emits tokens in the exact same order as FLA's HF + # DistributedSampler (eliminates the data-ordering drift that + # caused the +2.4 nat warm-up spike we debugged for GDN/KDA). + # The launcher script sets PRIMUS_FLA_CACHE_DIR; if you launch + # without it, uncomment fla_cache_dir below to make this YAML + # self-contained. + use_fla_data: true # PRIMUS_FLA_DATA + # fla_cache_dir: /home//flash-linear-attention/legacy/training/data/HuggingFaceFW/fineweb-edu/sample-100BT/train + fused_ce_mode: 1 # 1=chunked FLA FusedLinearCrossEntropyLoss + fused_ce_chunks: 32 # Chunk count for FLA fused CE (32 = launcher default) + + # ───────────────────────────────────────────────────────────────────── + # SwiGLU path — disable Megatron's bias-fused SwiGLU so the MLP + # actually routes through FLA's Triton SwiGLU (the PRIMUS_FLA_SWIGLU=1 + # env var's intent). + # + # `primus/configs/models/megatron/language_model.yaml` sets + # `bias_swiglu_fusion: true`, which Megatron translates to + # `bias_activation_fusion=true` (see arguments.py L1610-1613) and + # forces the MLP forward through `bias_swiglu_impl` (mlp.py L308). + # That code path NEVER checks `self._use_fla_swiglu` — meaning + # PRIMUS_FLA_SWIGLU=1 has been silently inert ever since. The + # backward for `bias_swiglu_impl` materialises a ~4 GB grad + # temporary at b=64, s=2048, intermediate=8192 — exactly the + # iter-1 OOM we hit (HIPBLAS_STATUS_ALLOC_FAILED → 4.00 GiB request). + # + # Setting this to false: + # • routes MLP through the `_use_fla_swiglu` branch (mlp.py L323) + # • uses FLA's `swiglu` Triton kernel (the kernel we actually want + # for parity — same one FLA's reference run uses) + # • saves ~4 GB of backward-pass memory + # All bit-perfect parity guarantees are preserved (this is in fact + # what we were claiming to do all along — see docs/hybrid_models/GDN_FLA_PARITY.md §B + # patch 03 "mlp-fla-swiglu"). + # ───────────────────────────────────────────────────────────────────── + bias_swiglu_fusion: false + + # NOTE: `recompute_granularity` is intentionally NOT set here. It's a + # no-op for pure GDN — MambaBlock.forward()/HybridStack.forward() are + # plain `for layer in self.layers:` loops with zero recompute logic + # (verified in third_party/Megatron-LM/megatron/core/ssm/mamba_block.py + # and primus/backends/megatron/core/models/hybrid/hybrid_block.py). + # The actual fix for the iter-1 OOM is PRIMUS_FUSED_CE_CHUNKS=32 set + # by the launcher — see launch_gdn_pure_1B_100B.sh and + # third_party/Megatron-LM/megatron/core/models/mamba/mamba_model.py. + + # ── Data ─ FLA-aligned via PRIMUS_FLA_DATA=1 at runtime. + # + # `train_data_path` below is just a Megatron config-parser placeholder; + # FLAOrderGPTDataset overrides it when PRIMUS_FLA_DATA=1 + a valid + # PRIMUS_FLA_CACHE_DIR are exported. Point the launcher's + # PRIMUS_FLA_CACHE_DIR at the sample-100BT cache (FLA has it on disk + # at /home//flash-linear-attention/legacy/training/ + # data/HuggingFaceFW/fineweb-edu/sample-100BT/train). + # + # The 10BT .bin/.idx is reused as a placeholder so Megatron's index + # builder doesn't choke on a missing file — its actual indices and + # tokens are never read when FLAOrderGPTDataset is active. + mock_data: false + train_data_path: > + data/fla_aligned/fla_fineweb_edu_10BT_text_sentence + valid_data_path: null + test_data_path: null + + # ── Checkpoints ─ + finetune: false + auto_continue_train: true + load: null + # FLA's `path=` for this run is /home//checkpoints/ + # gdn_pure_1B_100B. We save to a sibling dir prefixed with `primus_` so + # the two runs' checkpoints can coexist on disk and downstream eval + # tools (tools/hybrid/eval_gdn_lm_eval.py) can A/B them side-by-side. + save: /tmp/primus_perf_exp_ckpt_unused + # FLA's 100B launcher passes `save=99999` (HF `--save_steps 99999`), + # meaning over 95368 iters HF never writes an intermediate ckpt — only + # the final one at end-of-training (then `--save_total_limit 3` keeps + # the last 3 across all runs in the dir). + # + # For Primus we cannot match `99999` literally because + # `auto_continue_train: true` needs SOME periodic checkpoint to be able + # to resume mid-flight from a crash on a 48 h run. Set save_interval to + # 10000 (~10 B tokens, ~10 ckpts over the run) — closer in spirit to + # FLA's "almost never save" and ~2× lighter than the previous 5000 + # setting. `disable_last_saving: false` still guarantees the very last + # iter is saved exactly like FLA does at end-of-training. + save_interval: 100000 # PERF EXP: never save + disable_last_saving: false + ckpt_format: fsdp_dtensor # Megatron-FSDP requires this when use_megatron_fsdp=true + + # Turbo (kept off for the comparison run; can flip on later) + enable_primus_turbo: false + use_turbo_attention: false + + # Context parallel + context_parallel_size: 1 diff --git a/examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml new file mode 100644 index 000000000..c228cf00c --- /dev/null +++ b/examples/megatron/configs/MI300X/zebra_llama_1B_kda-pretrain.yaml @@ -0,0 +1,84 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:zebra_llama_1B_kda-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + # model to run + model: zebra_llama_1B.yaml + overrides: + # log + wandb_project: "Primus_Zebra_Llama_1B_KDA_Pretrain" + stderr_sink_level: DEBUG + + eval_iters: 0 + + num_workers: 0 + create_attention_mask_in_dataloader: false + + profile: false + use_pytorch_profiler: false + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + + train_iters: 50 + micro_batch_size: 4 + global_batch_size: 32 # micro_batch_size * num_gpus + + seq_length: 8192 + max_position_embeddings: 8192 + original_max_position_embeddings: 8192 + + lr: 2.0e-4 + min_lr: 2.0e-5 + lr_warmup_iters: 200 + lr_decay_iters: 38147 + lr_decay_style: cosine + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: true + + # Use KDA (Kimi Delta Attention) hybrid spec + spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'kda_hybrid_stack_spec'] + use_fla_triton_kda: true + + # Tokenizer + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: meta-llama/Llama-3.2-1B + + # parallel + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + overlap_grad_reduce: false + overlap_param_gather: false + gradient_accumulation_fusion: false + use_torch_fsdp2: false + use_distributed_optimizer: true + + # data + mock_data: true + train_data_path: null + valid_data_path: null + test_data_path: null + + # ckpt + finetune: false + auto_continue_train: true + load: null + save: ./output/zebra_llama_1B_kda-pretrain + save_interval: 1000 + disable_last_saving: false + ckpt_format: torch + + # Turbo + enable_primus_turbo: false + use_turbo_attention: false + + # context parallel + context_parallel_size: 1 diff --git a/examples/megatron/configs/MI300X/zebra_llama_1B_kda_pure-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_1B_kda_pure-pretrain.yaml new file mode 100644 index 000000000..2750f2e6f --- /dev/null +++ b/examples/megatron/configs/MI300X/zebra_llama_1B_kda_pure-pretrain.yaml @@ -0,0 +1,94 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:zebra_llama_1B_kda_pure-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: zebra_llama_1B_kda_pure.yaml + overrides: + wandb_project: "Primus_Zebra_Llama_1B_KDA_Pure_Pretrain" + stderr_sink_level: DEBUG + + eval_iters: 0 + + num_workers: 0 + create_attention_mask_in_dataloader: false + + profile: false + use_pytorch_profiler: false + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + + # Training schedule — matched to FLA (4 GPUs): + # batch=16, update=1, gpus=4, context=2048 + # global_batch = 16 * 4 = 64, tokens/step = 64 * 2048 = 131,072 + # total tokens ~= 76294 * 131072 ≈ 10B + train_iters: 50 + micro_batch_size: 16 + global_batch_size: 128 + + seq_length: 2048 + max_position_embeddings: 2048 + original_max_position_embeddings: 2048 + + # Optimizer — matched to FLA: + # lr=2e-4, cosine (min_lr_rate=0.1 → min_lr=2e-5) + # AdamW, beta1=0.9, beta2=0.95, weight_decay=0.01 + # max_grad_norm=1.0, warmup_steps=200 + # Both DeepSpeed ZeRO-2 and Megatron clip on ||avg_grad||. + # DeepSpeed pre-divides by dp_size before reduce_scatter(SUM), + # so its grad norm is on averaged gradients. Match FLA's 1.0 directly. + clip_grad: 1.0 + lr: 2.0e-4 + min_lr: 2.0e-5 + lr_warmup_iters: 200 + lr_decay_iters: 76294 + lr_decay_style: cosine + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: false + + # Use KDA hybrid spec with hybrid_attention_ratio=0.0 (pure KDA) + spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'kda_hybrid_stack_spec'] + use_fla_triton_kda: true + + # Tokenizer + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: meta-llama/Llama-3.2-1B + + # Parallelism + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + overlap_grad_reduce: false + overlap_param_gather: false + gradient_accumulation_fusion: false + use_torch_fsdp2: false + use_distributed_optimizer: true + + # Data + mock_data: true + train_data_path: null + valid_data_path: null + test_data_path: null + + # Checkpoints + finetune: false + auto_continue_train: true + load: null + save: ./output/zebra_llama_1B_kda_pure-pretrain + save_interval: 2048 + disable_last_saving: false + ckpt_format: torch + + # Turbo + enable_primus_turbo: false + use_turbo_attention: false + + # Context parallel + context_parallel_size: 1 diff --git a/examples/megatron/configs/MI300X/zebra_llama_300M_gdn_hybrid-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_300M_gdn_hybrid-pretrain.yaml new file mode 100644 index 000000000..8fa3e3230 --- /dev/null +++ b/examples/megatron/configs/MI300X/zebra_llama_300M_gdn_hybrid-pretrain.yaml @@ -0,0 +1,171 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:zebra_llama_300M_gdn_hybrid-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: zebra_llama_300M_gdn_hybrid.yaml + overrides: + wandb_project: "Primus_Zebra_Llama_300M_GDN_Hybrid_Pretrain" + stderr_sink_level: DEBUG + + eval_iters: 0 + + # FLA parity: HF Trainer prefetches lightly from a memmap'd parquet, so + # 2 dataloader workers per rank (= 16 forked subprocesses on 8 GPUs) is + # enough to keep the dataloader pipeline full without bloating host RSS. + # `num_workers: 8` (= 64 forked subprocesses) was OOM-killing the host + # at ~iter 1200 on this 125 GB-RAM box. + num_workers: 2 + create_attention_mask_in_dataloader: false + + profile: false + use_pytorch_profiler: false + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + log_interval: 100 + check_for_nan_in_loss_and_grad: false + + # Perf: disable barrier-with-L1-time (serializes ranks per L1 timer call) + barrier_with_L1_time: false + + # Match FLA seed for reproducibility comparison + seed: 42 + + # norm_epsilon in the model YAML maps to args.norm_epsilon, but + # TransformerConfig uses layernorm_epsilon (default 1e-5). + # Set explicitly to match FLA's 1e-6. + layernorm_epsilon: 1.0e-6 + + # FLA does no dropout; force off (Megatron defaults are 0.1) + hidden_dropout: 0.0 + attention_dropout: 0.0 + + # Training schedule — matched to FLA (8 GPUs, FineWeb-Edu sample-10BT): + # FLA: per_device_train_batch_size=128 × 8 GPUs → global=1024 + # tokens/step = 1024 × 2048 = 2,097,152 + # 4768 steps × 2.097M ≈ 10B tokens + train_iters: 50 + micro_batch_size: 8 + global_batch_size: 64 + + seq_length: 2048 + max_position_embeddings: 2048 + original_max_position_embeddings: 2048 + + # Optimizer — matched to FLA: + # lr=2e-4, cosine (min_lr_rate=0.1 → min_lr=2e-5) + # AdamW, beta1=0.9, beta2=0.95, weight_decay=0.01 + # max_grad_norm=1.0, warmup_steps=200 + clip_grad: 1.0 + lr: 2.0e-4 + min_lr: 2.0e-5 + lr_warmup_iters: 200 + lr_decay_iters: 4768 + lr_decay_style: cosine + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: false + + # ───────────────────────────────────────────────────────────────────── + # FLA runtime knobs — declarative YAML surface for the + # PRIMUS_FLA_*/PRIMUS_FUSED_CE* env vars. Consumed by + # primus.backends.megatron.patches.fla_runtime_patches (phase= + # "build_args") which re-exports them as env vars so the existing + # consumers in fla_flash_attention.py / gated_delta_net.py / + # hybrid_block.py / mamba_model.py / mlp.py / pretrain_mamba.py + # see identical values. Env vars set on the launcher still win + # over the YAML (backward compat). + # ───────────────────────────────────────────────────────────────────── + use_fla_fused_swiglu: true # FLA Triton SwiGLU + use_fla_fused_rmsnorm: true # FLA Triton RMSNorm + FusedRMSNormGated + use_fla_fused_gated_norm: true # Same env var (PRIMUS_FLA_NORM) + use_fla_short_conv: true # FLA Triton causal_conv1d for GDN short-conv + # ── Dataset source selector ─────────────────────────────────────── + # use_fla_data + fla_cache_dir together pick the data path: + # use_fla_data=false (or fla_cache_dir empty): vanilla Megatron + # GPTDataset reading train_data_path (.bin/.idx). + # use_fla_data=true AND fla_cache_dir=: replace + # GPTDataset with tools/fla_order_dataset.FLAOrderGPTDataset + # for bit-identical token order to FLA's HF DistributedSampler. + # The launcher script sets PRIMUS_FLA_CACHE_DIR; if you launch + # without it, uncomment fla_cache_dir below. + use_fla_data: true # PRIMUS_FLA_DATA + # fla_cache_dir: /home//flash-linear-attention/legacy/training/data/HuggingFaceFW/fineweb-edu/sample-10BT/train + fused_ce_mode: 1 # 1=chunked FLA FusedLinearCrossEntropyLoss + fla_mla_attn: "1" # FLA flash-attn for the MLA blocks + + # Hybrid GDN+MLA stack (no-TE spec to match FLA layers; same spec as pure GDN) + # With hybrid_attention_ratio=0.25, the allocator places MLA at mixer blocks + # [0, 4, 8] — identical to FLA's `attn.layers: [0, 4, 8]`. + spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'gdn_hybrid_stack_spec_no_te'] + no_persist_layer_norm: true + + # Tokenizer + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: meta-llama/Llama-3.2-1B + + # Parallelism. + # + # Distributed optimizer (ZeRO-1) shards optimizer state across DP ranks + # and amortizes the per-iter Adam step over the DP group. Previously + # disabled because pairing it with `overlap_param_gather: true` skewed + # per-rank GPU memory by ~30 GB (gathered param buffer aggregated on + # ranks 0-1 → 189 GB used, ranks 2-7 → 155 GB), OOMing FLA's LCE + # 7.83 GB chunk. With the new FLA-fusion stack (PRIMUS_FLA_NORM=1 → + # FusedRMSNormGated, in-kernel gate fusion) every rank now sits at + # ~171 GB / 192 GB, leaving ~21 GB headroom — enough for the + # distributed optimizer's gather buffer as long as `overlap_param_gather` + # stays off. + # + # Expected gain: ~25 ms/iter (closes the bulk of the remaining gap vs + # FLA's DeepSpeed-ZeRO-2 optimizer step amortization). + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + use_distributed_optimizer: true # ZeRO-1 — shards optimizer state + overlap_grad_reduce: false + overlap_param_gather: false # leave OFF — the skew that caused OOM + gradient_accumulation_fusion: false + use_torch_fsdp2: false + ddp_average_in_collective: true + + # Data — FLA-aligned FineWeb-Edu sample-10BT (same indexed binary the + # pure GDN/KDA 300M runs consume; uses FLAOrderGPTDataset for identical + # token ordering to FLA) + mock_data: true + train_data_path: null + valid_data_path: null + test_data_path: null + + # Checkpoints — train from scratch by default. To start from an + # FLA-initialized weights snapshot for iter-1 parity, set + # finetune: true + # load: /path/to/fla_init_hybrid_ckpt + # no_load_optim: true + # no_load_rng: true + finetune: false + auto_continue_train: false + load: null + save: ./output/zebra_llama_300M_gdn_hybrid-pretrain + # FLA parity: FLA's setup_and_train_gdn_hybrid_300M.sh uses `save=99999`, + # i.e. it only writes a checkpoint at the very end of the 4768-step run. + # The mid-training save we previously did at iter 1024 spiked host RSS + # (each rank materialises its full state-dict in CPU memory during the + # save) and the OS OOM-killer took us out around iter 1200 on this + # 125 GB-RAM host. Match FLA: skip mid-run saves, keep the end save. + save_interval: 99999 + disable_last_saving: false + ckpt_format: torch + + # Turbo (kept off to match FLA's vanilla PyTorch path) + enable_primus_turbo: false + use_turbo_attention: false + + # Context parallel + context_parallel_size: 1 diff --git a/examples/megatron/configs/MI300X/zebra_llama_300M_gdn_pure-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_300M_gdn_pure-pretrain.yaml new file mode 100644 index 000000000..178cf0381 --- /dev/null +++ b/examples/megatron/configs/MI300X/zebra_llama_300M_gdn_pure-pretrain.yaml @@ -0,0 +1,123 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:zebra_llama_300M_gdn_pure-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: zebra_llama_300M_gdn_pure.yaml + overrides: + wandb_project: "Primus_Zebra_Llama_300M_GDN_Pure_Pretrain" + stderr_sink_level: DEBUG + + eval_iters: 0 + + num_workers: 8 + create_attention_mask_in_dataloader: false + + profile: false + use_pytorch_profiler: false + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + log_interval: 100 + check_for_nan_in_loss_and_grad: false + + # Perf: Megatron defaults to inserting dist.barrier() before every L1 + # timer measurement (~5-10/iter) to make per-stage timings comparable. + # This serializes ranks. Disable for production training. + barrier_with_L1_time: false + + # Match FLA seed for reproducibility comparison + seed: 42 + + # Fix: norm_epsilon in model YAML maps to args.norm_epsilon, but + # TransformerConfig uses layernorm_epsilon (default 1e-5). + # Must explicitly set layernorm_epsilon to match FLA's 1e-6. + layernorm_epsilon: 1.0e-6 + + # CRITICAL: Megatron's TransformerConfig defaults hidden_dropout=0.1 and + # attention_dropout=0.1 (transformer_config.py L152). Even though + # mamba_base.yaml sets these to 0.0, the YAML inheritance is being + # overridden by language_model.yaml (which sets 0.1) and the override is + # not propagating to args. This caused embeddings to be dropout-perturbed + # at every iteration in prior runs (verified empirically: same token + # produced DIFFERENT embedding vectors with 1/(1-0.1)=1.111 scaling). + # FLA does NOT apply any dropout. Force these to 0 here to match. + hidden_dropout: 0.0 + attention_dropout: 0.0 + + # Training schedule — matched to FLA (8 GPUs): + # FLA: per_device_train_batch_size=128, 8 GPUs → global=1024 + # tokens/step = 1024 * 2048 = 2,097,152 + # total tokens ~= 4768 * 2,097,152 ≈ 10B + train_iters: 50 + micro_batch_size: 8 + global_batch_size: 64 + + seq_length: 2048 + max_position_embeddings: 2048 + original_max_position_embeddings: 2048 + + # Optimizer — matched to FLA: + # lr=2e-4, cosine (min_lr_rate=0.1 → min_lr=2e-5) + # AdamW, beta1=0.9, beta2=0.95, weight_decay=0.01 + # max_grad_norm=1.0, warmup_steps=200 + clip_grad: 1.0 + lr: 2.0e-4 + min_lr: 2.0e-5 + lr_warmup_iters: 200 + lr_decay_iters: 4768 + lr_decay_style: cosine + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: false + + # Pure GDN hybrid spec (hybrid_attention_ratio=0.0 → all GDN, no MLA) + # Use no-TE spec to match FLA's native PyTorch layers for loss curve alignment + spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'gdn_hybrid_stack_spec_no_te'] + no_persist_layer_norm: true + + # Tokenizer + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: meta-llama/Llama-3.2-1B + + # Parallelism + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + # Perf: For a small 300M model, distributed optimizer (ZeRO-1) adds + # REDUCE-SCATTER + ALL-GATHER overhead with no memory savings (full + # optimizer state ~3.6GB easily fits per-rank). Switch to plain DDP + # ALL-REDUCE to match FLA. Memory cost: +3GB/rank, expected: ~10-15% faster. + # NOTE: overlap_param_gather requires distributed optimizer, so disable it. + overlap_grad_reduce: false + overlap_param_gather: false + gradient_accumulation_fusion: false + use_torch_fsdp2: false + use_distributed_optimizer: false + ddp_average_in_collective: true + + # Data + mock_data: true + train_data_path: null + valid_data_path: null + test_data_path: null + + # Checkpoints + finetune: false + auto_continue_train: false + save: ./output/zebra_llama_300M_gdn_pure-pretrain + save_interval: 1024 + disable_last_saving: false + ckpt_format: torch + + # Turbo + enable_primus_turbo: false + use_turbo_attention: false + + # Context parallel + context_parallel_size: 1 diff --git a/examples/megatron/configs/MI300X/zebra_llama_300M_kda_pure-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_300M_kda_pure-pretrain.yaml new file mode 100644 index 000000000..f769fc9cd --- /dev/null +++ b/examples/megatron/configs/MI300X/zebra_llama_300M_kda_pure-pretrain.yaml @@ -0,0 +1,161 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:zebra_llama_300M_kda_pure-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: zebra_llama_300M_kda_pure.yaml + overrides: + wandb_project: "Primus_Zebra_Llama_300M_KDA_Pure_Pretrain" + stderr_sink_level: DEBUG + + eval_iters: 0 + + num_workers: 8 + create_attention_mask_in_dataloader: false + + profile: false + use_pytorch_profiler: false + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + log_interval: 100 + check_for_nan_in_loss_and_grad: false + + # Perf: Megatron defaults to inserting dist.barrier() before every L1 + # timer measurement (~5-10/iter) to make per-stage timings comparable. + # This serializes ranks. Disable for production training. + barrier_with_L1_time: false + + # Match FLA seed for reproducibility comparison + seed: 42 + + # Fix: norm_epsilon in model YAML maps to args.norm_epsilon, but + # TransformerConfig uses layernorm_epsilon (default 1e-5). + # Must explicitly set layernorm_epsilon to match FLA's 1e-6. + layernorm_epsilon: 1.0e-6 + + # CRITICAL: Megatron's TransformerConfig defaults hidden_dropout=0.1 and + # attention_dropout=0.1. Even though mamba_base.yaml sets these to 0.0, + # the YAML inheritance is being overridden by language_model.yaml (which + # sets 0.1) and the override is not propagating to args. FLA does NOT + # apply any dropout. Force these to 0 here to match. + hidden_dropout: 0.0 + attention_dropout: 0.0 + + # Training schedule — matched to FLA (8 GPUs): + # FLA: per_device_train_batch_size=128, 8 GPUs → global=1024 + # tokens/step = 1024 * 2048 = 2,097,152 + # total tokens ~= 4768 * 2,097,152 ≈ 10B + train_iters: 50 + micro_batch_size: 8 + global_batch_size: 64 + + seq_length: 2048 + max_position_embeddings: 2048 + original_max_position_embeddings: 2048 + + # Optimizer — matched to FLA: + # lr=2e-4, cosine (min_lr_rate=0.1 → min_lr=2e-5) + # AdamW, beta1=0.9, beta2=0.95, weight_decay=0.01 + # max_grad_norm=1.0, warmup_steps=200 + clip_grad: 1.0 + lr: 2.0e-4 + min_lr: 2.0e-5 + lr_warmup_iters: 200 + lr_decay_iters: 4768 + lr_decay_style: cosine + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: false + + # Pure KDA hybrid spec — no-TE variant matches FLA's KDABlock layout + # exactly (fla/models/kda/modeling_kda.py): + # - Pre-norm computed ONCE per layer in the wrapper + # (KimiDeltaAttentionLayer.norm = WrappedTorchNorm). + # - Mixer in_proj is plain ColumnParallelLinear (no fused norm). + # - Mixer gate_norm = IdentityOp (side projections reuse the + # wrapper-normed tensor — no second norm pass). + # The TE variant (kda_hybrid_stack_spec) re-normalizes hidden_states + # inside the mixer via gate_norm, costing ~12-15 GiB activation memory + # per layer × 12 = ~40 GiB peak, plus ~50 ms/iter for the extra norm + # launches. The no-TE variant saves both, mirroring how GDN matched + # FLA in docs/hybrid_models/GDN_FLA_PARITY.md. + spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'kda_hybrid_stack_spec_no_te'] + use_fla_triton_kda: true + + # Full FLA-exact kernel fusion (matches fla/layers/kda.py exactly): + # use_fla_kda_in_kernel_gate=True → chunk_kda(use_gate_in_kernel=True, + # use_qk_l2norm_in_kernel=True); gate `-exp(A_log)*softplus(g+dt_bias) + # +cumsum` fused inside the Triton kernel and recomputed in backward, + # so the [B,T,H,K] fp32 activated-gate tensor is never materialized. + # use_fla_fused_norm_gated=True → out_norm = FusedRMSNormGated + # (RMSNorm + sigmoid-gate + multiply in one Triton kernel); avoids + # the post-norm fp32 tensor and the fp32-upcast gate save-for-backward. + # Speed: ~+15 % vs the unfused path (matches FLA's ~1480 ms/iter). + # Memory: ~-20 GiB peak (FusedRMSNormGated drops ~10 GiB activation + + # in-kernel gate drops ~3 GiB + fewer DDP buckets ≈ ~20 GiB total). + # Loss-curve note: on ROCm the in-kernel `-exp(A_log)*softplus` and the + # fused norm+sigmoid+multiply accumulators run in bf16; ±1 ulp drift + # compounds across 12 layers and gives ~+0.2-0.4 lm-loss above FLA + # *unless* we also load FLA's init checkpoint (the drift cancels when + # both runs start from identical weights — proven on GDN). Without the + # init ckpt expect loss curve to track FLA shape but offset; with it, + # expect bit-perfect parity (GDN-style). + use_fla_kda_in_kernel_gate: true + use_fla_fused_norm_gated: true + + no_persist_layer_norm: true + + # Tokenizer + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: meta-llama/Llama-3.2-1B + + # Parallelism + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + # Plain DDP (no distributed optimizer) — required for FLA loss-curve + # parity per docs/hybrid_models/GDN_FLA_PARITY.md line 143-149. Megatron's + # DistributedOptimizer (ZeRO-1) applies the optimizer update per-shard + # then all-gathers, which is mathematically equivalent in fp64 but + # NOT bit-identical to plain AdamW in bf16 → those ±1 ulp ordering + # differences compound into a measurable loss drift over ~100 iters + # even when iter-1 forward and gradient match FLA exactly. + # + # Memory cost: ~3.6 GiB / rank extra (un-sharded optim state). With + # FLA-init checkpoint + both fusions on + expandable_segments we have + # ~12 GiB free headroom at iter 200, so this fits. + overlap_grad_reduce: false + overlap_param_gather: false # requires distributed optimizer + gradient_accumulation_fusion: false + use_torch_fsdp2: false + use_distributed_optimizer: false + ddp_average_in_collective: true # divide gradients in NCCL collective + + # Data — FLA-aligned FineWeb-Edu sample-10BT + # Converted from FLA's preprocessed Arrow dataset so both + # frameworks see the exact same tokens in the same order. + mock_data: true + train_data_path: null + valid_data_path: null + test_data_path: null + + # Checkpoints + finetune: false + auto_continue_train: false + save: ./output/zebra_llama_300M_kda_pure-pretrain + save_interval: 1024 + disable_last_saving: false + ckpt_format: torch + + # Turbo + enable_primus_turbo: false + use_turbo_attention: false + + # Context parallel + context_parallel_size: 1 diff --git a/examples/megatron/configs/MI300X/zebra_llama_300M_mamba_hybrid-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_300M_mamba_hybrid-pretrain.yaml new file mode 100644 index 000000000..cd6ecaecf --- /dev/null +++ b/examples/megatron/configs/MI300X/zebra_llama_300M_mamba_hybrid-pretrain.yaml @@ -0,0 +1,137 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:zebra_llama_300M_mamba_hybrid-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: zebra_llama_300M_mamba_hybrid.yaml + overrides: + wandb_project: "Primus_Zebra_Llama_300M_Mamba_Hybrid_Pretrain" + stderr_sink_level: DEBUG + + eval_iters: 0 + + # 2 dataloader workers per rank — keeps the dataloader pipeline full + # without bloating host RSS (16 forked subprocs total at 8 GPUs). + num_workers: 2 + create_attention_mask_in_dataloader: false + + profile: false + use_pytorch_profiler: false + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + log_interval: 100 + check_for_nan_in_loss_and_grad: false + barrier_with_L1_time: false + + seed: 42 + + # RMSNorm 1e-6 (override Megatron's default 1e-5) + layernorm_epsilon: 1.0e-6 + + hidden_dropout: 0.0 + attention_dropout: 0.0 + + # Training schedule — same as the GDN hybrid 300M run for direct + # comparison: + # 8 GPUs, micro=128, global=1024 + # tokens/step = 1024 × 2048 = 2,097,152 + # 4768 steps × 2.097M ≈ 10B tokens + train_iters: 50 + micro_batch_size: 8 + global_batch_size: 64 + + seq_length: 2048 + max_position_embeddings: 2048 + original_max_position_embeddings: 2048 + + # Optimizer — AdamW with cosine decay (lr 2e-4 → 2e-5 over 4768 iters) + clip_grad: 1.0 + lr: 2.0e-4 + min_lr: 2.0e-5 + lr_warmup_iters: 200 + lr_decay_iters: 4768 + lr_decay_style: cosine + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: false + + # ───────────────────────────────────────────────────────────────────── + # FLA runtime knobs — declarative YAML surface for the + # PRIMUS_FLA_*/PRIMUS_FUSED_CE* env vars. Mirror exactly the env + # vars `tools/hybrid/launch_mamba_hybrid_300M.sh` previously exported. + # Consumed by primus.backends.megatron.patches.fla_runtime_patches + # at phase="build_args". Env vars on the launcher still win. + # ───────────────────────────────────────────────────────────────────── + use_fla_fused_swiglu: true # FLA Triton SwiGLU + use_fla_fused_rmsnorm: true # FLA Triton RMSNorm + use_fla_fused_gated_norm: true # Same env var (PRIMUS_FLA_NORM) + # ── Dataset source selector ─────────────────────────────────────── + # use_fla_data + fla_cache_dir together pick the data path: + # use_fla_data=false (or fla_cache_dir empty): vanilla Megatron + # GPTDataset reading train_data_path (.bin/.idx). + # use_fla_data=true AND fla_cache_dir=: replace + # GPTDataset with tools/fla_order_dataset.FLAOrderGPTDataset + # for bit-identical token order to FLA's HF DistributedSampler. + # The launcher script sets PRIMUS_FLA_CACHE_DIR; if you launch + # without it, uncomment fla_cache_dir below. + use_fla_data: true # PRIMUS_FLA_DATA + # fla_cache_dir: /home//flash-linear-attention/legacy/training/data/HuggingFaceFW/fineweb-edu/sample-10BT/train + fused_ce_mode: 1 # 1=chunked FLA FusedLinearCrossEntropyLoss + fla_mla_attn: "1" # FLA flash-attn for the MLA blocks + + # Mamba2 + MLA hybrid stack via the proven `HybridStack` (no-TE) path. + # + # Why not the upstream `hybrid_stack_spec` (MambaStack)? Megatron's + # `MambaStack` builder passes `pp_layer_offset` to MLASelfAttention, + # but this version of MLASelfAttention.__init__ doesn't accept it + # (Megatron API drift). The HybridStack path used by the GDN/KDA + # hybrids doesn't pass that kwarg, so we mirror it for Mamba2. + spec: ['primus.backends.megatron.core.models.hybrid.hybrid_mamba_mla_layer_specs', 'mamba_hybrid_stack_spec_no_te'] + no_persist_layer_norm: true + + # Tokenizer + tokenizer_type: HuggingFaceTokenizer + tokenizer_model: meta-llama/Llama-3.2-1B + + # Parallelism — ZeRO-1 distributed optimizer for memory headroom. + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + use_distributed_optimizer: true + overlap_grad_reduce: false + overlap_param_gather: false + gradient_accumulation_fusion: false + use_torch_fsdp2: false + ddp_average_in_collective: true + + # Data — FLA-aligned FineWeb-Edu 10BT binary (same dataset the GDN + # hybrid 300M consumed, so per-token loss curves are directly + # comparable). No PRIMUS_FLA_DATA / PRIMUS_FLA_CACHE_DIR needed — + # this is the indexed mmap version Megatron consumes natively. + mock_data: true + train_data_path: null + valid_data_path: null + test_data_path: null + + # Checkpoints — train from scratch; save once at the end (matches the + # GDN hybrid run, avoids mid-training host-RAM OOM from the state-dict + # materialisation). + finetune: false + auto_continue_train: false + load: null + save: ./output/zebra_llama_300M_mamba_hybrid-pretrain + save_interval: 99999 + disable_last_saving: false + ckpt_format: torch + + # Turbo off (matches the GDN hybrid baseline) + enable_primus_turbo: false + use_turbo_attention: false + + context_parallel_size: 1 diff --git a/examples/megatron/configs/MI300X/zebra_llama_3B-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_3B-pretrain.yaml index b8019be76..82473699a 100644 --- a/examples/megatron/configs/MI300X/zebra_llama_3B-pretrain.yaml +++ b/examples/megatron/configs/MI300X/zebra_llama_3B-pretrain.yaml @@ -20,7 +20,7 @@ modules: log_avg_skip_iterations: 2 log_avg_reset_interval: 50 - train_iters: 100 + train_iters: 50 micro_batch_size: 4 global_batch_size: 32 diff --git a/examples/megatron/configs/MI300X/zebra_llama_8B-pretrain.yaml b/examples/megatron/configs/MI300X/zebra_llama_8B-pretrain.yaml index a7083c069..24962376f 100644 --- a/examples/megatron/configs/MI300X/zebra_llama_8B-pretrain.yaml +++ b/examples/megatron/configs/MI300X/zebra_llama_8B-pretrain.yaml @@ -20,7 +20,7 @@ modules: log_avg_skip_iterations: 2 log_avg_reset_interval: 50 - train_iters: 100 + train_iters: 50 micro_batch_size: 2 global_batch_size: 16 diff --git a/examples/megatron/configs/MI325X/deepseek_v2_lite-BF16-pretrain.yaml b/examples/megatron/configs/MI325X/deepseek_v2_lite-BF16-pretrain.yaml index d2c8d73f4..a8636a21f 100644 --- a/examples/megatron/configs/MI325X/deepseek_v2_lite-BF16-pretrain.yaml +++ b/examples/megatron/configs/MI325X/deepseek_v2_lite-BF16-pretrain.yaml @@ -44,6 +44,9 @@ modules: expert_model_parallel_size: ${PRIMUS_EP:8} overlap_grad_reduce: true overlap_param_gather: true + # NOTE: gradient_accumulation_fusion=true raised throughput ~1% but caused + # an intermittent NaN in the grad-norm check (backward wgrad accumulation), + # so it is kept off for reliability. gradient_accumulation_fusion: false # data @@ -73,10 +76,13 @@ modules: # Turbo enable_primus_turbo: true use_turbo_attention: true - use_turbo_grouped_gemm: false + # PrimusTurbo grouped GEMM for the MoE experts (replaces the removed + # use_turbo_grouped_gemm). Required for sync-free MoE stage >= 2. + use_turbo_grouped_gemm: true # deepep use_turbo_deepep: true + # DeepEP does not support moe_shared_expert_overlap; keep it off. moe_shared_expert_overlap: false moe_router_dtype: fp32 @@ -87,7 +93,7 @@ modules: # sync-free moe support stage 0-3, 0 means not use sync-free moe # stage 3 is completely no gpu-cpu sync in MoE, but cost more memory # stage 2 is recommended for better performance - turbo_sync_free_moe_stage: 1 + turbo_sync_free_moe_stage: 2 # remove once super flag is functional again moe_use_fused_router_with_aux_score: true diff --git a/examples/megatron/configs/MI325X/gpt_oss_20B-FP8-pretrain.yaml b/examples/megatron/configs/MI325X/gpt_oss_20B-FP8-pretrain.yaml index f4feaffdc..8eb3d0fcd 100644 --- a/examples/megatron/configs/MI325X/gpt_oss_20B-FP8-pretrain.yaml +++ b/examples/megatron/configs/MI325X/gpt_oss_20B-FP8-pretrain.yaml @@ -110,3 +110,14 @@ modules: cross_entropy_loss_fusion: true fp8: hybrid + # FP8 delayed scaling (default) is fastest, but the delayed_fp8_scaling + # stream patches crash with a GPU memory fault (sum_and_scatter) at ~iter + # 6; disable them. Alternative: fp8_recipe=tensorwise (no patches). + fp8_recipe: delayed + disable_delayed_scaling_patches: true + # enable_primus_turbo: true + # use_turbo_attention: true + # use_turbo_grouped_gemm: false + # enable_primus_turbo: false + # enable_turbo_attention_float8 : false + # enable_turbo_gemm_float8 : false diff --git a/examples/megatron/configs/MI325X/llama3.1_70B-FP8-pretrain.yaml b/examples/megatron/configs/MI325X/llama3.1_70B-FP8-pretrain.yaml index 55809a6d4..3ff515822 100644 --- a/examples/megatron/configs/MI325X/llama3.1_70B-FP8-pretrain.yaml +++ b/examples/megatron/configs/MI325X/llama3.1_70B-FP8-pretrain.yaml @@ -81,5 +81,10 @@ modules: # enable fp8 training fp8: hybrid + # FP8 delayed scaling (default) + the delayed_fp8_scaling stream patches + # crash with a GPU memory fault; disable the patches. (This also drops the + # data-prefetch double-buffer, freeing some device memory.) + fp8_recipe: delayed + disable_delayed_scaling_patches: true moe_use_legacy_grouped_gemm: false no_fp8_weight_transpose_cache: true diff --git a/examples/megatron/configs/MI325X/llama3.1_8B-FP8-pretrain.yaml b/examples/megatron/configs/MI325X/llama3.1_8B-FP8-pretrain.yaml index 2228dcffa..13d35f0ca 100644 --- a/examples/megatron/configs/MI325X/llama3.1_8B-FP8-pretrain.yaml +++ b/examples/megatron/configs/MI325X/llama3.1_8B-FP8-pretrain.yaml @@ -77,4 +77,14 @@ modules: # enable fp8 training fp8: hybrid + # FP8 recipe: keep delayed scaling (the inherited default), which is the + # fastest FP8 path here (~3% higher throughput than tensorwise/current + # scaling because it reuses cached amax history instead of recomputing + # per-step amax). However, the delayed_fp8_scaling stream patches + # (secondary-stream grad-zero + data prefetch) have a use-after-free that + # crashes with a GPU memory fault (sum_and_scatter, "write to read-only + # page") at ~iter 6, so disable them. Alternative: fp8_recipe=tensorwise + # (Turbo-native, no patches, ~3% slower). + fp8_recipe: delayed + disable_delayed_scaling_patches: true moe_use_legacy_grouped_gemm: false diff --git a/examples/megatron/configs/MI325X/llama3.3_70B-FP8-pretrain.yaml b/examples/megatron/configs/MI325X/llama3.3_70B-FP8-pretrain.yaml index 6337830f1..f79dc1bc9 100644 --- a/examples/megatron/configs/MI325X/llama3.3_70B-FP8-pretrain.yaml +++ b/examples/megatron/configs/MI325X/llama3.3_70B-FP8-pretrain.yaml @@ -17,7 +17,11 @@ modules: log_avg_reset_interval: 50 train_iters: 10 - micro_batch_size: 3 + # NOTE: reduced from micro_batch_size=3 to 1. At MBS=3 this config both + # OOMs and, once memory is freed, hits a deterministic FP8 NaN loss at + # iter 2 (reproducible with both delayed and tensorwise scaling). MBS=1 + # trains stably (matches the working llama3.1_70B FP8 config). + micro_batch_size: 1 global_batch_size: 24 seq_length: 8192 @@ -69,7 +73,7 @@ modules: # recompute recompute_granularity: full # full, selective recompute_method: block # uniform, block - recompute_num_layers: 80 # int + recompute_num_layers: 72 # int # Turbo enable_primus_turbo: true @@ -77,10 +81,12 @@ modules: use_turbo_grouped_gemm: false # Cross entropy flags - # cross_entropy_fusion_impl: "te" - # cross_entropy_loss_fusion: true + cross_entropy_fusion_impl: "te" + cross_entropy_loss_fusion: true # enable fp8 training fp8: hybrid + fp8_recipe: delayed + disable_delayed_scaling_patches: true moe_use_legacy_grouped_gemm: false no_fp8_weight_transpose_cache: true diff --git a/examples/megatron/configs/MI325X/qwen3_30B_A3B-BF16-pretrain.yaml b/examples/megatron/configs/MI325X/qwen3_30B_A3B-BF16-pretrain.yaml index a9c45a614..c6772b255 100644 --- a/examples/megatron/configs/MI325X/qwen3_30B_A3B-BF16-pretrain.yaml +++ b/examples/megatron/configs/MI325X/qwen3_30B_A3B-BF16-pretrain.yaml @@ -82,7 +82,13 @@ modules: # Turbo enable_primus_turbo: true - use_turbo_attention: false + # AMD-optimized attention kernel (faster and typically lower activation + # memory than the default backend). + use_turbo_attention: true + # NOTE: use_turbo_grouped_gemm (the replacement for the removed + # use_turbo_grouped_gemm) was tested but uses more device memory than the + # legacy grouped GEMM here and OOMs at MBS=8 (only 5 recompute layers), so + # this model stays on the legacy grouped-GEMM path. use_turbo_grouped_gemm: false use_turbo_rms_norm: false # bug diff --git a/examples/megatron/configs/MI325X/qwen3_30B_A3B-FP8-pretrain.yaml b/examples/megatron/configs/MI325X/qwen3_30B_A3B-FP8-pretrain.yaml index ef8965ae6..47871d4fd 100644 --- a/examples/megatron/configs/MI325X/qwen3_30B_A3B-FP8-pretrain.yaml +++ b/examples/megatron/configs/MI325X/qwen3_30B_A3B-FP8-pretrain.yaml @@ -39,9 +39,13 @@ modules: norm_epsilon: 1.0e-6 # recompute + # Recomputing full MoE layers (router + permute + 128-expert grouped GEMM) + # is very expensive (~2k tokens/s per layer here), so recompute only 4 of + # 48 layers. FP8 weights leave enough memory headroom to keep the rest of + # the activations resident. recompute_granularity: full # full, selective recompute_method: block # uniform, block - recompute_num_layers: 5 # int + recompute_num_layers: 4 # int # parallel tensor_model_parallel_size: ${PRIMUS_TP:1} @@ -82,6 +86,10 @@ modules: # Turbo enable_primus_turbo: true + # NOTE: turbo attention is intentionally OFF for FP8. The BF16 turbo kernel + # inside the FP8 pipeline is ~8% slower than the default TE attention, and + # the FP8 flash-attention kernel (enable_turbo_attention_float8) is not + # compatible with this model's GQA head layout. use_turbo_attention: false use_turbo_grouped_gemm: false use_turbo_rms_norm: false # bug @@ -91,7 +99,9 @@ modules: moe_shared_expert_overlap: false moe_router_dtype: fp32 - # 64 or 80 for ep8, 32 for ep16-64 is best practice + # 64 or 80 for ep8, 32 for ep16-64 is best practice. 80 is required for + # the fast regime here: dropping below ~78 CUs cliffs throughput ~13% + # (DeepEP comm stalls), so keep 80. turbo_deepep_num_cu: 80 turbo_deepep_use_comm_stream: false @@ -105,5 +115,10 @@ modules: # enable fp8 training fp8: hybrid + # FP8 delayed scaling (default) is fastest, but the delayed_fp8_scaling + # stream patches crash with a GPU memory fault (sum_and_scatter) at ~iter + # 6; disable them. Alternative: fp8_recipe=tensorwise (no patches). + fp8_recipe: delayed + disable_delayed_scaling_patches: true moe_use_legacy_grouped_gemm: true gradient_accumulation_fusion: true diff --git a/examples/megatron/configs/MI355X/deepseek1.5B-odc-lbmini.yaml b/examples/megatron/configs/MI355X/deepseek1.5B-odc-lbmini.yaml new file mode 100644 index 000000000..a0ab10937 --- /dev/null +++ b/examples/megatron/configs/MI355X/deepseek1.5B-odc-lbmini.yaml @@ -0,0 +1,116 @@ +### DeepSeek-R1-Distill-Qwen-1.5B ODC LB-Mini SFT example (single-node). +### Sequence-length load balancing (LB-Mini) with the "fit" cost model on the +### torch-FSDP2 + ODC path; packed (thd) attention enabled. + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:deepseek1.5B-odc-lbmini} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + post_trainer: + framework: megatron + config: sft_trainer.yaml + + model: deepseek_r1_distill_qwen_1.5B.yaml + + overrides: + stage: sft + + hf_path: deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B + tokenizer_model: deepseek-ai/DeepSeek-R1-Distill-Qwen-1.5B + + sft_dataset_name: "zai-org/LongAlign-10k" + sft_conversation_format: "messages" + + # ODC on-demand communication (rocSHMEM/XGMI P2P gradient reduction) on the + # torch-FSDP2 path. Master switch (was the ODC_ENABLE env var). + enable_odc: true + odc_phase: 2 + + # thd segmented (packed) attention. + enable_packed_sequences: true + use_packed_attention: true + # LB-Mini sequence-length load balancing (was the ODC_LB_MINI env var). + enable_odc_lb_mini: true + # LB-Mini cost model: fit (attention-aware a*s^2 + b*s, 1.5B coefficients). + lb_mini_cost_model: fit + lb_mini_max_token_len: 32768 + + # ODC P2P backend: rocshmem (validated single-node host/XGMI-IPC). trainer_base default is mori. + odc_p2p_backend: rocshmem + + wandb_project: "Primus_ODC_DeepSeek1.5B" + stderr_sink_level: DEBUG + log_avg_skip_iterations: 2 + log_avg_reset_interval: 100 + + train_iters: 50 + micro_batch_size: 1 + global_batch_size: 16 + + seq_length: 32768 + max_position_embeddings: 32768 + + lr: 1.0e-5 + min_lr: 0.0 + lr_warmup_iters: 2 + lr_decay_iters: 100 + lr_decay_style: cosine + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + + seed: 1234 + eod_mask_loss: false + init_method_std: 0.008 + norm_epsilon: 1.0e-6 + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + context_parallel_size: 1 + sequence_parallel: false + + enable_fused_linear_ce: false + + use_torch_fsdp2: true + use_megatron_fsdp: false + use_distributed_optimizer: false + overlap_grad_reduce: false + overlap_param_gather: false + gradient_accumulation_fusion: false + + finetune: true + load: null + save: null + save_interval: 1000 + eval_interval: 1000 + no_save_optim: null + no_save_rng: null + disable_last_saving: true + ckpt_format: torch_dist + + bf16: true + + enable_primus_turbo: false + use_turbo_attention: false + use_turbo_grouped_mlp: false + use_turbo_rms_norm: false + + eval_iters: 0 + + profile: false + use_pytorch_profiler: true + use_nsys_profiler: false + profile_step_start: 10 + profile_step_end: 11 + profile_ranks: [0] + pytorch_profiler_collect_shapes: true + pytorch_profiler_collect_callstack: false + pytorch_profiler_collect_chakra: false + record_shapes: false + record_memory_history: false + nvtx_ranges: false + + lora: + enabled: false diff --git a/examples/megatron/configs/MI355X/deepseek_v4_flash-BF16-pretrain.yaml b/examples/megatron/configs/MI355X/deepseek_v4_flash-BF16-pretrain.yaml new file mode 100644 index 000000000..277c129f7 --- /dev/null +++ b/examples/megatron/configs/MI355X/deepseek_v4_flash-BF16-pretrain.yaml @@ -0,0 +1,138 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:deepseek_v4_flash-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +# DeepSeek-V4 Flash BF16 pretraining config tuned for MI355X. +# This is a smoke / scaffold config — values (especially parallelism) will be +# revised once the V4 builder + hybrid attention land in Phase 3-5. + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: ${PRIMUS_MODEL:deepseek_v4_flash}.yaml + overrides: + # log + wandb_project: "Primus_DeepSeek_V4_Pretrain" + stderr_sink_level: DEBUG + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + + # hyper parameters + train_iters: 50 + micro_batch_size: 1 + global_batch_size: 256 + seq_length: ${PRIMUS_SEQ_LENGTH:4096} + max_position_embeddings: ${PRIMUS_MAX_POSITION_EMBEDDINGS:4096} + lr: 1.0e-5 + min_lr: 0.0 + lr_warmup_iters: 2 + lr_decay_iters: null + lr_decay_style: cosine + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: true + init_method_std: 0.008 + norm_epsilon: 1.0e-6 + + # parallel — scaffold defaults; will be retuned in Phase 6. + tensor_model_parallel_size: ${PRIMUS_TP:1} + pipeline_model_parallel_size: ${PRIMUS_PP:8} + expert_model_parallel_size: ${PRIMUS_EP:8} + overlap_grad_reduce: true + overlap_param_gather: true + gradient_accumulation_fusion: true + + # data + mock_data: true + train_data_path: ${PRIMUS_TOKENIZED_DATA_PATH:null} + valid_data_path: null + test_data_path: null + + # ---------- DeepSeek-V4 specific ---------- + hybrid_attention_enabled: true + attn_sink: true + hc_use_sinkhorn: true + mtp_use_separate_hc_head: true + moe_router_score_function: sqrtsoftplus + swiglu_limit: 10.0 + + # ---------- Optimizer ---------- + # NOTE: Phase 7 will add Muon for the latent / hidden projections. + # Until then we run plain BF16 AdamW (precision-aware). + use_precision_aware_optimizer: true + main_grads_dtype: bf16 + exp_avg_dtype: bf16 + exp_avg_sq_dtype: bf16 + + # rope fusion + enable_experimental: true + apply_rope_fusion: false # V4 uses partial RoPE on a 512-dim head + + # recompute + recompute_granularity: full + recompute_method: uniform + recompute_num_layers: 1 + + # ckpt + finetune: false + auto_continue_train: false + load: null + no_load_optim: null + no_load_rng: null + save: null + save_interval: 20000 + no_save_optim: null + no_save_rng: null + disable_last_saving: true + ckpt_format: torch + eval_iters: 0 + + # turbo / deepep — keep off by default; enable via env vars when + # benchmarking the Turbo path (see run_deepseek_v4.sh). Plan-3 P22 + # routes the dense (compress_ratio == 0) attention layers through + # PrimusTurboAttention when ``use_turbo_attention=true``; the V4 + # builder auto-derives ``use_sink_attention`` / + # ``sink_sliding_window`` from ``attn_sink`` / ``attn_sliding_window`` + # so the YAML stays free of Turbo-internal knobs. + enable_primus_turbo: ${PRIMUS_ENABLE_TURBO:true} + # use_turbo_attention stays OFF: the dense (cr=0) path must run the + # triton_v2 sparse-MLA backend below, not PrimusTurboAttention (which + # would take dispatch precedence). + use_turbo_attention: ${PRIMUS_USE_TURBO_ATTENTION:false} + use_turbo_grouped_gemm: true + use_turbo_rms_norm: true + use_turbo_deepep: ${PRIMUS_USE_TURBO_DEEPEP:true} + moe_shared_expert_overlap: false + moe_router_dtype: fp32 + + # deepep tuning (64 or 80 for ep8, 32 for ep16-64 is best practice) + turbo_deepep_num_cu: 80 + turbo_deepep_use_comm_stream: false + + # sync-free moe support (stage 1-2; 0 = off). stage 2 = best perf. + turbo_sync_free_moe_stage: 1 + + # V4 attention backend selection (unified string selectors; default triton_v2). + # use_v4_attention_backend (dense cr=0 / HCA cr=128): eager|triton_v1|triton_v2|gluon + # use_v4_csa_attention_backend (CSA cr=4): eager|triton_v0|triton_v1|triton_v2|gluon|flydsl_v0 + use_v4_attention_backend: ${PRIMUS_USE_V4_ATTENTION_BACKEND:triton_v2} + use_v4_csa_attention_backend: ${PRIMUS_USE_V4_CSA_ATTENTION_BACKEND:triton_v2} + + # FP8 (E4M3) Indexer QK path (CSA selector); BF16 index-score preserved. + use_v4_fp8_indexer: ${PRIMUS_USE_V4_FP8_INDEXER:false} + + # plan-5 P29 (RESCOPED): wrap sinkhorn_normalize with a cached + # torch.compile(fullgraph=True, dynamic=False) build. Default + # false until G32 (parity) + G33b (trace) flip it on. + use_v4_compiled_sinkhorn: ${PRIMUS_USE_V4_COMPILED_SINKHORN:false} + + moe_use_fused_router_with_aux_score: true + moe_permute_fusion: true + + # Cross entropy flags + cross_entropy_fusion_impl: "te" + cross_entropy_loss_fusion: true diff --git a/examples/megatron/configs/MI355X/deepseek_v4_flash-FP8-pretrain.yaml b/examples/megatron/configs/MI355X/deepseek_v4_flash-FP8-pretrain.yaml new file mode 100644 index 000000000..bc5026810 --- /dev/null +++ b/examples/megatron/configs/MI355X/deepseek_v4_flash-FP8-pretrain.yaml @@ -0,0 +1,166 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:deepseek_v4_flash-fp8-pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +# DeepSeek-V4 Flash FP8 pretraining config tuned for MI355X. +# Derived from deepseek_v4_flash-BF16-pretrain.yaml; the ONLY substantive +# delta is the FP8 training block at the bottom. +# +# Precision recipe (paper §4.x quantization / techblog §9.6): +# Paper = "FP4 + FP8 Mixed": MoE experts + CSA-Indexer QK in FP4 (MXFP4), +# EVERYTHING ELSE in FP8, all with the **ue8m0** microscaling scale format. +# ue8m0 = OCP Microscaling (MX) block scale → on this stack that is +# `fp8_recipe: mxfp8` → TE MXFP8BlockScaling → Primus-Turbo MX_BLOCKWISE +# with scale_dtype=E8M0 (fp8_utils.py:148), native on MI355X/CDNA4. +# +# Integration gap vs the paper (NOT config — Primus V4 TODO, techblog item 10 +# "Phase 2 FP4/FP8 Mixed"): the FP4 expert / FP4-Indexer path is not yet wired +# in V4, so experts run at FP8 here (FP8 everywhere) rather than FP4. This is +# the closest supported step toward the paper recipe. FP8 is highly outlier- +# sensitive, which is why the paper pairs it with clamped SwiGLU (swiglu_limit, +# set below) — keep that on whenever FP8 is enabled. + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + model: ${PRIMUS_MODEL:deepseek_v4_flash}.yaml + overrides: + # log + wandb_project: "Primus_DeepSeek_V4_Pretrain" + stderr_sink_level: DEBUG + log_avg_skip_iterations: 2 + log_avg_reset_interval: 50 + + # hyper parameters + train_iters: 50 + micro_batch_size: 1 + global_batch_size: 256 + seq_length: ${PRIMUS_SEQ_LENGTH:4096} + max_position_embeddings: ${PRIMUS_MAX_POSITION_EMBEDDINGS:4096} + lr: 1.0e-5 + min_lr: 0.0 + lr_warmup_iters: 2 + lr_decay_iters: null + lr_decay_style: cosine + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + eod_mask_loss: true + init_method_std: 0.008 + norm_epsilon: 1.0e-6 + + # parallel — scaffold defaults; will be retuned in Phase 6. + tensor_model_parallel_size: ${PRIMUS_TP:1} + pipeline_model_parallel_size: ${PRIMUS_PP:8} + expert_model_parallel_size: ${PRIMUS_EP:8} + overlap_grad_reduce: true + overlap_param_gather: true + gradient_accumulation_fusion: true + + # data + mock_data: true + train_data_path: ${PRIMUS_TOKENIZED_DATA_PATH:null} + valid_data_path: null + test_data_path: null + + # ---------- DeepSeek-V4 specific ---------- + hybrid_attention_enabled: true + attn_sink: true + hc_use_sinkhorn: true + mtp_use_separate_hc_head: true + moe_router_score_function: sqrtsoftplus + swiglu_limit: 10.0 + + # ---------- Optimizer ---------- + # NOTE: Phase 7 will add Muon for the latent / hidden projections. + # Until then we run plain BF16 AdamW (precision-aware). + use_precision_aware_optimizer: true + main_grads_dtype: bf16 + exp_avg_dtype: bf16 + exp_avg_sq_dtype: bf16 + + # rope fusion + enable_experimental: true + apply_rope_fusion: false # V4 uses partial RoPE on a 512-dim head + + # recompute + recompute_granularity: full + recompute_method: uniform + recompute_num_layers: 1 + + # ckpt + finetune: false + auto_continue_train: false + load: null + no_load_optim: null + no_load_rng: null + save: null + save_interval: 20000 + no_save_optim: null + no_save_rng: null + disable_last_saving: true + ckpt_format: torch + eval_iters: 0 + + # turbo / deepep — keep off by default; enable via env vars when + # benchmarking the Turbo path (see run_deepseek_v4.sh). Plan-3 P22 + # routes the dense (compress_ratio == 0) attention layers through + # PrimusTurboAttention when ``use_turbo_attention=true``; the V4 + # builder auto-derives ``use_sink_attention`` / + # ``sink_sliding_window`` from ``attn_sink`` / ``attn_sliding_window`` + # so the YAML stays free of Turbo-internal knobs. + enable_primus_turbo: ${PRIMUS_ENABLE_TURBO:true} + # use_turbo_attention stays OFF: the dense (cr=0) path must run the + # triton_v2 sparse-MLA backend below, not PrimusTurboAttention (which + # would take dispatch precedence). + use_turbo_attention: ${PRIMUS_USE_TURBO_ATTENTION:false} + use_turbo_grouped_gemm: true + use_turbo_rms_norm: true + use_turbo_deepep: ${PRIMUS_USE_TURBO_DEEPEP:true} + moe_shared_expert_overlap: false + moe_router_dtype: fp32 + + # deepep tuning (64 or 80 for ep8, 32 for ep16-64 is best practice) + turbo_deepep_num_cu: 80 + turbo_deepep_use_comm_stream: false + + # sync-free moe support (stage 1-2; 0 = off). stage 2 = best perf. + turbo_sync_free_moe_stage: 1 + + # V4 attention backend selection (unified string selectors; default triton_v2). + # use_v4_attention_backend (dense cr=0 / HCA cr=128): eager|triton_v1|triton_v2|gluon + # use_v4_csa_attention_backend (CSA cr=4): eager|triton_v0|triton_v1|triton_v2|gluon|flydsl_v0 + use_v4_attention_backend: ${PRIMUS_USE_V4_ATTENTION_BACKEND:triton_v2} + use_v4_csa_attention_backend: ${PRIMUS_USE_V4_CSA_ATTENTION_BACKEND:triton_v2} + + # plan-5 P29 (RESCOPED): wrap sinkhorn_normalize with a cached + # torch.compile(fullgraph=True, dynamic=False) build. Default + # false until G32 (parity) + G33b (trace) flip it on. + use_v4_compiled_sinkhorn: ${PRIMUS_USE_V4_COMPILED_SINKHORN:false} + + moe_use_fused_router_with_aux_score: true + moe_permute_fusion: true + + # Cross entropy flags + cross_entropy_fusion_impl: "te" + cross_entropy_loss_fusion: true + + # ---------- FP8 training ---------- + # fp8 = number format (E4M3); fp8_recipe = scaling strategy. + # The paper uses ue8m0 microscaling (= mxfp8, 1x32 block / E8M0 scale), but + # mxfp8 is NOT runnable on this gfx950 build: turbo grouped-GEMM has no MX + # path, and TE's ROCm MXFP8 GEMM requires K%128==0 (V4 has a K=224 proj). + # So we use `tensorwise` (per-tensor scale) — the working recipe. This gives + # the paper's fp8 *layout* (all weight GEMMs fp8) with a non-ue8m0 scale. + # Override via FP8 / FP8_RECIPE env knobs (CLI wins); FP8=null => BF16. + fp8: e4m3 + fp8_recipe: tensorwise + # Route the attention/dense linear projections through PrimusTurboLinear so + # they quantize to fp8 too (not just the MoE experts) — matches the paper's + # "fp8 on all weight GEMMs". Without this the projections stay bf16. + use_turbo_gemm: true + + moe_router_padding_for_quantization: true diff --git a/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml new file mode 100644 index 000000000..8ba4c13fb --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml @@ -0,0 +1,209 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — DDP + Local Spec + FP8 Tensorwise (MI355X) +# +# Combines Megatron DDP + distributed optimizer with the local spec provider +# (PrimusTurboFloat8LocalSpecProvider) for FP8 tensorwise training. +# +# Key configuration: +# - Megatron DDP with overlap_grad_reduce + overlap_param_gather +# - PrimusTurboFloat8LocalSpecProvider (NO TransformerEngine dependency) +# - FP8 hybrid tensorwise (per-module FP8 via Primus Turbo) +# - Primus Turbo attention +# - torch.compile enabled (per_block strategy, compatible with local spec + overlap) +# - Energon pre-encoded dataset with stored VAE mean/logvar (resample mode) + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_ddp_local_fp8} + +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + # Short LR warmup ramp over the first optimizer steps for FP8 stability. + nemo_aligned_lr_warmup: true + warmup_train_steps: 2 + + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + # VAE latent normalization (required for resample mode) + vae_scale: 0.3611 + vae_shift: 0.1159 + + # Resample mode: re-draw latents from mean+logvar each step + vae_latent_mode: resample + + # RoPE: must be interleaved to match Flux's EmbedND doubled-frequency layout + rotary_interleaved: true + + # ========================================== + # PrimusTurboFloat8LocalSpecProvider + # ========================================== + transformer_impl: "local" + + # ========================================== + # DATASET CONFIGURATION + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 1000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # Batch configuration + micro_batch_size: 64 + global_batch_size: 512 + seq_length: 512 # 256 img tokens + 256 text tokens (schnell) + + # ========================================== + # BF16 + FP8 Precision + # ========================================== + bf16: true + fp16: false + params_dtype: bfloat16 + grad_reduce_in_bf16: true + + # Optimizer settings + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + # Learning rate scheduler (warmup-hold, no decay) + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader settings + num_workers: 8 + dataloader_type: external + max_samples_per_sequence: null + + # ========================================== + # Megatron DDP + Distributed Optimizer + # ========================================== + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: false + use_megatron_fsdp: false + + use_distributed_optimizer: true + overlap_grad_reduce: true + overlap_param_gather: true + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: true + main_params_dtype: fp32 + main_grads_dtype: bf16 + exp_avg_dtype: bf16 + exp_avg_sq_dtype: bf16 + + ddp_bucket_size: 256000000 + use_fsdp2_fp32_param_optimizer: false + + ckpt_format: torch_dist + + gradient_accumulation_fusion: false + + check_for_nan_in_loss_and_grad: true + + # ========================================== + # FP8 Configuration — Tensorwise + Delayed FP8 via Primus Turbo + # ========================================== + + use_flash_attn: true + + fp8: "hybrid" + # TE-compatible unified form: fp8: hybrid + fp8_recipe: delayed selects + # tensorwise FP8 with delayed scaling. Resolution path: + # primus/backends/megatron/core/extensions/primus_turbo_float8_local.py + # :: Float8{Column,Row}ParallelLinear._use_delayed_scaling. + fp8_recipe: "delayed" + fp8_margin: 0 + fp8_amax_history_len: 1024 + fp8_amax_compute_algo: "max" + fp8_wgrad: true + fp8_dot_product_attention: false + fp8_multi_head_attention: false + fp8_reduce_amax: true + + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 180 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_ddp_local_fp8 + wandb_project: flux_12b_ddp_local_fp8 + log_throughput: true + wall_clock_step_timer: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # PyTorch Profiler + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo Configuration + enable_primus_turbo: true + use_turbo_attention: true + use_dual_fp8_output_projection: false + + seed: 2025 + per_step_rng_reseed: false + nemo_chimera_init: false + + # Torch Compile — compatible with Float8 local spec (per-module FP8) + torch_compile: + enable: true + strategy: "per_block" + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: false + emulate_precision_casts: false + fused_ln_modulate: true diff --git a/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8_mlperf.yaml b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8_mlperf.yaml new file mode 100644 index 000000000..d67662f76 --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8_mlperf.yaml @@ -0,0 +1,202 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — DDP + Local Spec + FP8 Tensorwise (MLPerf Mode) +# +# NOTE: This is a benchmark-reproduction config for the MLPerf Training Flux.1 +# benchmark. It mirrors MLPerf logging/convergence conventions and is intended +# for reproducing benchmark results, not as a general-purpose training starting +# point. For everyday FP8 training use +# flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml. +# +# MLPerf-compliant variant of the DDP + local spec FP8 config: +# - mlperf_mode: true — enables MLPerf logging via mlperf_logging.mllog +# - warmup_train_steps: 2 — synthetic data warmup for torch.compile + FP8 +# - target_val_loss: 0.586 — convergence target for early stopping +# - DDP + distributed optimizer without precision-aware optimizer +# - PrimusTurboFloat8LocalSpecProvider with FP8 tensorwise +# - Suppresses TensorBoard/WandB/print_rank_last during training +# - Emits structured MLPerf events (INIT_START, RUN_START, EVAL, etc.) + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_ddp_local_fp8_mlperf} + +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # MLPerf Mode + # ========================================== + mlperf_mode: true + warmup_train_steps: 2 + target_val_loss: 0.586 + + # ========================================== + # MLPerf Training v5.1 Alignment + # ========================================== + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + # VAE latent normalization (required for resample mode) + vae_scale: 0.3611 + vae_shift: 0.1159 + vae_latent_mode: resample + + # RoPE: must be interleaved to match Flux's EmbedND doubled-frequency layout + rotary_interleaved: true + + # ========================================== + # PrimusTurboFloat8LocalSpecProvider + # ========================================== + transformer_impl: "local" + + # ========================================== + # DATA — Real Energon data (not mock) + # ========================================== + mock_data: false + dataloader_type: external + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + num_workers: 16 + prefetch_factor: 4 + max_samples_per_sequence: null + + # Training iterations + train_iters: 5000 + eval_interval: 512 + eval_iters: 10 + log_interval: 10 + save_interval: 10000 + + # Batch configuration + micro_batch_size: 64 + global_batch_size: 512 + seq_length: 512 + + # ========================================== + # BF16 + FP8 Precision + # ========================================== + bf16: true + fp16: false + params_dtype: bfloat16 + grad_reduce_in_bf16: true + + # Optimizer settings (MLPerf v5.1) + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + # Learning rate scheduler (warmup-hold, no decay) + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # ========================================== + # Megatron DDP + Distributed Optimizer + # ========================================== + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: false + use_megatron_fsdp: false + + use_distributed_optimizer: true + overlap_grad_reduce: true + overlap_param_gather: true + overlap_param_gather_with_optimizer_step: false + # Use FP32 optimizer states (m,v): strictly better time-to-train with + # no throughput trade-off vs BF16 on this FP8 + emulate_precision_casts recipe. + use_precision_aware_optimizer: false + main_params_dtype: fp32 + main_grads_dtype: fp32 + exp_avg_dtype: fp32 + exp_avg_sq_dtype: fp32 + + ddp_bucket_size: 256000000 + use_fsdp2_fp32_param_optimizer: false + + ckpt_format: torch_dist + + gradient_accumulation_fusion: false + check_for_nan_in_loss_and_grad: false + + # ========================================== + # FP8 — Tensorwise via Primus Turbo + # ========================================== + use_flash_attn: true + + fp8: "hybrid" + # TE-compatible unified form: fp8: hybrid + fp8_recipe: delayed selects + # tensorwise FP8 with delayed scaling. + fp8_recipe: "delayed" + fp8_wgrad: true + fp8_dot_product_attention: false + fp8_multi_head_attention: false + fp8_amax_history_len: 1 + fp8_amax_compute_algo: "most_recent" + + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 60 + distributed_backend: nccl + + # Checkpointing (disabled for MLPerf) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging — suppressed by mlperf_mode + tensorboard_dir: null + wandb_project: null + log_throughput: false + wall_clock_step_timer: true + log_timers_to_tensorboard: false + log_batch_size_to_tensorboard: false + log_learning_rate_to_tensorboard: false + log_memory_to_tensorboard: false + + # Profiler — disabled for MLPerf + profile: false + + # Primus Turbo + enable_primus_turbo: true + use_turbo_attention: true + use_dual_fp8_output_projection: false + + seed: 42 + # MLPerf-aligned per-step CUDA RNG reseed (defaults off elsewhere; MLPerf + # reproduction must opt in for run-to-run determinism). + per_step_rng_reseed: true + + # Torch Compile — per_block: compile each transformer layer individually + torch_compile: + enable: true + strategy: "per_block" + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: false + emulate_precision_casts: true + fused_ln_modulate: true diff --git a/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_mxfp4.yaml b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_mxfp4.yaml new file mode 100644 index 000000000..10283f997 --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_mxfp4.yaml @@ -0,0 +1,198 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — DDP + Local Spec + MXFP4 (MI355X) +# +# Combines Megatron DDP + distributed optimizer with the local spec provider +# (PrimusTurboMXFP4LocalSpecProvider) for MXFP4 block-scaled training. +# +# Key configuration: +# - Megatron DDP with overlap_grad_reduce + overlap_param_gather +# - PrimusTurboMXFP4LocalSpecProvider (NO TransformerEngine dependency) +# - MXFP4 (E2M1 + E8M0 block-of-32 scales) via Primus Turbo + AITER +# - Primus Turbo attention +# - torch.compile enabled (per_block strategy, compatible with local spec) +# - Energon pre-encoded dataset with stored VAE mean/logvar (resample mode) + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_ddp_local_mxfp4} + +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + # Short LR warmup ramp over the first optimizer steps for stability. + nemo_aligned_lr_warmup: true + warmup_train_steps: 2 + + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + # VAE latent normalization (required for resample mode) + vae_scale: 0.3611 + vae_shift: 0.1159 + + # Resample mode: re-draw latents from mean+logvar each step + vae_latent_mode: resample + + # RoPE: must be interleaved to match Flux's EmbedND doubled-frequency layout + rotary_interleaved: true + + # ========================================== + # PrimusTurboMXFP4LocalSpecProvider + # ========================================== + transformer_impl: "local" + + # ========================================== + # DATASET CONFIGURATION + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 1000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # Batch configuration + micro_batch_size: 64 + global_batch_size: 512 + seq_length: 512 # 256 img tokens + 256 text tokens (schnell) + + # ========================================== + # BF16 + MXFP4 Precision + # ========================================== + bf16: true + fp16: false + params_dtype: bfloat16 + grad_reduce_in_bf16: true + + # Optimizer settings + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + # Learning rate scheduler (warmup-hold, no decay) + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader settings + num_workers: 8 + dataloader_type: external + max_samples_per_sequence: null + + # ========================================== + # Megatron DDP + Distributed Optimizer + # ========================================== + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: false + use_megatron_fsdp: false + + use_distributed_optimizer: true + overlap_grad_reduce: true + overlap_param_gather: true + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: true + main_params_dtype: fp32 + main_grads_dtype: bf16 + exp_avg_dtype: bf16 + exp_avg_sq_dtype: bf16 + + ddp_bucket_size: 256000000 + use_fsdp2_fp32_param_optimizer: false + + ckpt_format: torch_dist + + gradient_accumulation_fusion: false + + check_for_nan_in_loss_and_grad: true + + # ========================================== + # MXFP4 Configuration — Block-scaled via Primus Turbo + AITER + # ========================================== + + use_flash_attn: true + + fp4: "mxfp4" + fp4_recipe: "mxfp4" + mxfp4_backward_precision: "mxfp4" # "mxfp4" (pure) or "fp8" (hybrid) + + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 180 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_ddp_local_mxfp4 + wandb_project: flux_12b_ddp_local_mxfp4 + log_throughput: true + wall_clock_step_timer: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # PyTorch Profiler + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo Configuration + enable_primus_turbo: true + use_turbo_attention: true + + seed: 2025 + per_step_rng_reseed: false + nemo_chimera_init: false + + # Torch Compile — compatible with MXFP4 local spec (per-module FP4) + torch_compile: + enable: true + strategy: "per_block" + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: false + emulate_precision_casts: false + fused_ln_modulate: true diff --git a/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec.yaml b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec.yaml new file mode 100644 index 000000000..d4bddef34 --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec.yaml @@ -0,0 +1,184 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — DDP + TransformerEngine Spec + BF16 (MI355X) +# +# BF16 training using Megatron DDP + distributed optimizer with +# TransformerEngine modules (TEColumnParallelLinear / TERowParallelLinear / +# TEDotProductAttention / TENorm). This is the BF16 baseline on the +# TransformerEngine path; see the *_te_spec_fp8 variant for FP8. +# +# Key settings: +# bf16: true +# params_dtype: bfloat16 +# micro_batch_size: 64 / global_batch_size: 512 + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_ddp_te_bf16} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b_rope_fusion.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + # VAE latent normalization (required for resample mode) + vae_scale: 0.3611 + vae_shift: 0.1159 + + # Resample mode: re-draw latents from mean+logvar each step + vae_latent_mode: resample + + # RoPE: must be interleaved to match Flux's EmbedND doubled-frequency layout + rotary_interleaved: true + + # ========================================== + # TransformerEngine Spec (default) + # ========================================== + transformer_impl: "transformer_engine" + + # ========================================== + # ENERGON DATASET CONFIGURATION + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 1000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # Batch configuration + micro_batch_size: 64 + global_batch_size: 512 + seq_length: 512 + + # ========================================== + # BF16 Precision + # ========================================== + bf16: true + fp16: false + params_dtype: bfloat16 + grad_reduce_in_bf16: true + + # Optimizer settings (identical to BF16 baseline) + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + # Learning rate scheduler (warmup-hold, no decay) + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader settings + num_workers: 8 + dataloader_type: external + max_samples_per_sequence: null + + # ========================================== + # Megatron DDP + Distributed Optimizer + # ========================================== + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: false + use_megatron_fsdp: false + use_fsdp2_fp32_param_optimizer: false + + ckpt_format: torch_dist + + use_distributed_optimizer: true + + overlap_grad_reduce: true + overlap_param_gather: true + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: false + + ddp_bucket_size: 256000000 + + gradient_accumulation_fusion: false + + # ========================================== + # Memory Optimizations + # ========================================== + + use_flash_attn: true + + fp8: null + + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 60 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_ddp_te_bf16 + wandb_project: flux_12b_ddp_te_bf16 + log_throughput: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # PyTorch Profiler + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo Configuration + enable_primus_turbo: false + use_turbo_attention: false + + seed: 2025 + te_rng_tracker: true + + # Torch Compile — stack strategy (compiles the double/single DiT block + # stacks as inductor regions). Required to fit micro_batch_size 64 in + # BF16 on the TE path; matches the MLPerf NeMo reference (COMPILE_DIT + # strategy=stack). + torch_compile: + enable: true + strategy: "stack" + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: false diff --git a/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml new file mode 100644 index 000000000..4cb6ad043 --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml @@ -0,0 +1,209 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — DDP + TE Spec + FP8 Delayed Scaling (MI355X) +# +# FP8 training using Megatron DDP + distributed optimizer with TransformerEngine +# modules and FP8 hybrid delayed scaling: +# - Megatron DDP with overlap_grad_reduce + overlap_param_gather +# - TEColumnParallelLinear / TERowParallelLinear / TEDotProductAttention / TENorm +# - FP8 hybrid (E4M3 fwd, E5M2 bwd) with delayed scaling (amax history 1024) +# - RoPE fusion via apply_rope_fusion: true +# - Energon pre-encoded dataset +# +# Required environment variables for the TransformerEngine path (set before launch): +# export NVTE_FUSED_ATTN=1 +# export NVTE_FUSED_ATTN_CK=1 +# export NVTE_FP8_DPA_BWD=1 +# export NVTE_USE_HIPBLASLT=1 +# export USE_HIPBLASLT=1 +# export TORCH_BLAS_PREFER_HIPBLASLT=1 +# export NVTE_USE_CAST_TRANSPOSE_TRITON=1 + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_ddp_te_fp8} + +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + vae_scale: 0.3611 + vae_shift: 0.1159 + vae_latent_mode: resample + + # ========================================== + # RoPE Fusion + # ========================================== + rotary_interleaved: true + apply_rope_fusion: true + position_embedding_type: rope + + # ========================================== + # TransformerEngine Spec + # ========================================== + transformer_impl: "transformer_engine" + + # ========================================== + # FP8 — hybrid delayed scaling + # ========================================== + fp8: "hybrid" + fp8_recipe: "delayed" + fp8_margin: 0 + fp8_amax_history_len: 1024 + fp8_amax_compute_algo: "max" + fp8_wgrad: true + fp8_dot_product_attention: false + fp8_multi_head_attention: false + + # ========================================== + # Energon Dataset + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 1000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # ========================================== + # Batch Configuration + # ========================================== + # MI355X (256GB HBM3) fits the full MBS=64/GBS=512; tune to your hardware. + micro_batch_size: 64 + global_batch_size: 512 + seq_length: 512 + + # ========================================== + # BF16 + FP8 Precision + # ========================================== + bf16: true + fp16: false + params_dtype: bfloat16 + grad_reduce_in_bf16: true + + # ========================================== + # Optimizer + # ========================================== + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader + num_workers: 8 + dataloader_type: external + max_samples_per_sequence: null + + # ========================================== + # Megatron DDP + Distributed Optimizer + # ========================================== + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: false + use_megatron_fsdp: false + + use_distributed_optimizer: true + overlap_grad_reduce: true + overlap_param_gather: true + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: false + + ddp_bucket_size: 256000000 + use_fsdp2_fp32_param_optimizer: false + + ckpt_format: torch_dist + + gradient_accumulation_fusion: false + + check_for_nan_in_loss_and_grad: true + + # ========================================== + # Memory / Misc + # ========================================== + use_flash_attn: true + empty_unused_memory_level: 0 + + # Manual GC — align GC timing across ranks to avoid stragglers + manual_gc: true + manual_gc_interval: 1000 + + distributed_timeout_minutes: 60 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_ddp_te_fp8 + wandb_project: flux_12b_ddp_te_fp8 + log_throughput: true + wall_clock_step_timer: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # Profiler + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo — disabled for pure TE path + enable_primus_turbo: false + use_turbo_attention: false + + seed: 2025 + te_rng_tracker: true + + # torch.compile — selective stack compilation for TE spec + torch_compile: + enable: true + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: false + strategy: "stack" + replace_qk_rmsnorm: true + disable_inductor_cudagraphs: false diff --git a/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8_mlperf.yaml b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8_mlperf.yaml new file mode 100644 index 000000000..cd01468c8 --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8_mlperf.yaml @@ -0,0 +1,202 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — DDP + TE Spec + FP8 Delayed Scaling (MLPerf Mode) +# +# NOTE: This is a benchmark-reproduction config for the MLPerf Training Flux.1 +# benchmark. It mirrors MLPerf logging/convergence conventions and is intended +# for reproducing benchmark results, not as a general-purpose training starting +# point. For everyday FP8 training on the TransformerEngine path use +# flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml. +# +# MLPerf-compliant variant of the TE spec FP8 config: +# - mlperf_mode: true — enables MLPerf logging via mlperf_logging.mllog +# - warmup_train_steps: 2 — synthetic data warmup for torch.compile + FP8 +# - target_val_loss: 0.586 — convergence target for early stopping +# - DDP + distributed optimizer +# - TransformerEngine modules with FP8 hybrid delayed scaling + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_ddp_te_fp8_mlperf} + +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # MLPerf Mode + # ========================================== + mlperf_mode: true + warmup_train_steps: 2 + target_val_loss: 0.586 + + # ========================================== + # MLPerf Training v5.1 Alignment + # ========================================== + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + vae_scale: 0.3611 + vae_shift: 0.1159 + vae_latent_mode: resample + + # ========================================== + # RoPE Fusion + # ========================================== + rotary_interleaved: true + apply_rope_fusion: true + position_embedding_type: rope + + # ========================================== + # TransformerEngine Spec + # ========================================== + transformer_impl: "transformer_engine" + adaln_plain_ops: true + adaln_always_jit_fuser: true + + # ========================================== + # FP8 — hybrid delayed scaling + # ========================================== + fp8: "hybrid" + fp8_recipe: "delayed" + fp8_margin: 0 + fp8_amax_history_len: 1024 + fp8_amax_compute_algo: "max" + fp8_wgrad: true + fp8_dot_product_attention: false + fp8_multi_head_attention: false + + # ========================================== + # DATA — Real Energon data (not mock) + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + mock_data: false + dataloader_type: external + num_workers: 8 + max_samples_per_sequence: null + + train_iters: 5000 + eval_interval: 512 + eval_iters: 10 + log_interval: 10 + save_interval: 10000 + + # ========================================== + # Batch Configuration + # ========================================== + micro_batch_size: 64 + global_batch_size: 512 + seq_length: 512 + + # ========================================== + # BF16 + FP8 Precision + # ========================================== + bf16: true + fp16: false + params_dtype: bfloat16 + grad_reduce_in_bf16: true + + # ========================================== + # Optimizer (MLPerf v5.1) + # ========================================== + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # ========================================== + # Megatron DDP + Distributed Optimizer + # ========================================== + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: false + use_megatron_fsdp: false + + use_distributed_optimizer: true + overlap_grad_reduce: true + overlap_param_gather: true + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: false + + ddp_bucket_size: 256000000 + use_fsdp2_fp32_param_optimizer: false + + ckpt_format: torch_dist + + gradient_accumulation_fusion: false + check_for_nan_in_loss_and_grad: false + + # ========================================== + # Memory / Misc + # ========================================== + use_flash_attn: true + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 60 + distributed_backend: nccl + + # Checkpointing (disabled for MLPerf) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging — suppressed by mlperf_mode + tensorboard_dir: null + wandb_project: null + log_throughput: false + wall_clock_step_timer: true + log_timers_to_tensorboard: false + log_batch_size_to_tensorboard: false + log_learning_rate_to_tensorboard: false + log_memory_to_tensorboard: false + + # Profiler — disabled + profile: false + + # Primus Turbo — disabled for pure TE path + enable_primus_turbo: false + use_turbo_attention: false + + seed: 2025 + te_rng_tracker: true + # MLPerf-aligned per-step CUDA RNG reseed (defaults off elsewhere; MLPerf + # reproduction must opt in for run-to-run determinism). + per_step_rng_reseed: true + + # torch.compile — selective stack compilation for TE spec + torch_compile: + enable: true + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: false + strategy: "stack" + replace_qk_rmsnorm: true + disable_inductor_cudagraphs: true + emulate_precision_casts: false diff --git a/examples/megatron/configs/MI355X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec.yaml b/examples/megatron/configs/MI355X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec.yaml new file mode 100644 index 000000000..567b58aeb --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec.yaml @@ -0,0 +1,175 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — FSDP2 + Local Spec + BF16, VAE Resample Mode (MI355X) +# +# FSDP2 (ZeRO-2) BF16 training with the local spec provider and +# vae_latent_mode: resample. +# +# In resample mode, latents are re-drawn from stored mean+logvar via +# reparameterization (mean + exp(0.5*logvar) * randn) at every training step. +# This introduces per-step stochasticity in the VAE latents. +# +# Dataset must be an Energon pre-encoded dataset containing mean.pth and +# logvar.pth per sample. + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_fsdp2_local_bf16} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + # VAE latent normalization (required for resample mode) + vae_scale: 0.3611 + vae_shift: 0.1159 + + # Resample mode: re-draw latents from mean+logvar each step + vae_latent_mode: resample + + # RoPE: must be interleaved to match Flux's EmbedND doubled-frequency layout + rotary_interleaved: true + + # ========================================== + # PrimusTurboLocalSpecProvider + # ========================================== + transformer_impl: "local" + + # ========================================== + # ENERGON DATASET CONFIGURATION + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 2000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # Batch configuration + micro_batch_size: 64 + global_batch_size: 512 # 64 * 8 GPUs = 512 + seq_length: 512 # 256 img tokens + 256 text tokens (schnell) + + # Mixed precision + bf16: true + fp16: false + + # Optimizer settings + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + # Learning rate scheduler (warmup-hold, no decay) + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader settings + num_workers: 4 + dataloader_type: external + + # ========================================== + # PyTorch FSDP2 Configuration (ZeRO-2) + # ========================================== + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: true + use_megatron_fsdp: false + + torch_fsdp2_reshard_after_forward: false # ZeRO-2 + + use_fsdp2_fp32_param_optimizer: true + + ckpt_format: torch_dist + + use_distributed_optimizer: false + + overlap_grad_reduce: false + overlap_param_gather: false + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: false + + gradient_accumulation_fusion: false + + # ========================================== + # Memory Optimizations + # ========================================== + + use_flash_attn: true + + fp8: null + + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 60 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_fsdp2_local_bf16 + wandb_project: flux_12b_fsdp2_local_bf16 + log_throughput: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # PyTorch Profiler (disabled) + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo Configuration + enable_primus_turbo: true + use_turbo_attention: true + + seed: 42 + + # Torch Compile + torch_compile: + enable: true + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: true diff --git a/examples/megatron/configs/MI355X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec_fp8.yaml b/examples/megatron/configs/MI355X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec_fp8.yaml new file mode 100644 index 000000000..1ee15a5fb --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec_fp8.yaml @@ -0,0 +1,198 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 12B Schnell Training — FSDP2 + Local Spec + FP8 + FP32 Param Optimizer (MI355X) +# +# FP8 training on the FSDP2 path with: +# - FP8 training (tensorwise, local spec) +# - BF16 all-gather (use_fsdp2_fp8_all_gather: false) +# - FP32 param optimizer (FP32 params + FP32 optimizer states) +# - No overlap grad norm (overlap_grad_norm: false) +# - torch.compile enabled +# - ZeRO-2 sharding (reshard_after_forward: false) +# +# Uses an Energon pre-encoded dataset with VAE resample mode. + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_12b_fsdp2_local_fp8} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_12b.yaml + + trainer_class: FluxPretrainTrainer + + overrides: + model_type: flux_schnell + + # ========================================== + # Flux Schnell training recipe + # ========================================== + timestep_sampling_strategy: "direct_uniform" + activation_func: "openai_gelu" + cfg_dropout_prob: 0.1 + + # VAE latent normalization (required for resample mode) + vae_scale: 0.3611 + vae_shift: 0.1159 + + # Resample mode: re-draw latents from mean+logvar each step + vae_latent_mode: resample + + # RoPE: must be interleaved to match Flux's EmbedND doubled-frequency layout + rotary_interleaved: true + + # ========================================== + # PrimusTurboLocalSpecProvider + # ========================================== + transformer_impl: "local" + + # ========================================== + # DATASET CONFIGURATION + # ========================================== + data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Training iterations (example value; increase for a full training run) + train_iters: 1000 + eval_interval: 10000 + eval_iters: 0 + log_interval: 1 + save_interval: 10000 + + # Batch configuration + micro_batch_size: 64 + global_batch_size: 512 + seq_length: 512 # 256 img tokens + 256 text tokens (schnell) + + # Mixed precision + bf16: true + fp16: false + + # Optimizer settings + optimizer: adam + lr: 2.0e-4 + min_lr: 2.0e-4 + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-8 + clip_grad: 1.0 + + # Learning rate scheduler (warmup-hold, no decay) + lr_warmup_iters: 1600 + lr_decay_iters: 4000 + lr_decay_style: constant + + # DataLoader settings + num_workers: 8 + dataloader_type: external + max_samples_per_sequence: null + + # ========================================== + # PyTorch FSDP2 Configuration (ZeRO-2) + # ========================================== + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + expert_model_parallel_size: 1 + context_parallel_size: 1 + + use_torch_fsdp2: true + use_megatron_fsdp: false + + torch_fsdp2_reshard_after_forward: false # ZeRO-2 + use_fsdp2_fp8_all_gather: false + fp8_all_gather_stochastic_rounding: true + # fp8_all_gather_deq_requant: true # Dequant FP8->BF16 after AG, fresh dynamic requant downstream + use_triton_ops: true # Triton @triton_op modulate/LN+modulate — eliminates ~510us dispatch overhead per graph + fsdp_prefetch_depth: 5 + fp8_precompute_data_cache: false + optimizer_foreach: false + use_cpp_fp8_quantize: true + overlap_grad_norm: false + + # Optimizer mode: FP32 params + FP32 optimizer states + use_fsdp2_fp32_param_optimizer: true + use_fsdp2_bf16_master_weight_optimizer: false + + ckpt_format: torch_dist + + use_distributed_optimizer: false + + overlap_grad_reduce: false + overlap_param_gather: false + overlap_param_gather_with_optimizer_step: false + use_precision_aware_optimizer: false + + gradient_accumulation_fusion: false + + # ========================================== + # FP8 Configuration — Tensorwise via Primus Turbo + # ========================================== + + use_flash_attn: true + + fp8: "hybrid" + # Dynamic (tensorwise) scaling: the FSDP2 path does not yet exercise the + # delayed amax allreduce, so use tensorwise here. Switch to + # `fp8_recipe: "delayed"` once FSDP2 + delayed is wired up. + fp8_recipe: "tensorwise" + fp8_wgrad: true + fp8_dot_product_attention: false + fp8_multi_head_attention: false + + empty_unused_memory_level: 0 + + distributed_timeout_minutes: 60 + distributed_backend: nccl + + # Checkpointing (disabled) + finetune: false + save: null + load: null + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Logging + tensorboard_dir: output/tensorboard/flux_12b_fsdp2_local_fp8 + wandb_project: flux_12b_fsdp2_local_fp8 + log_throughput: true + wall_clock_step_timer: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_memory_to_tensorboard: true + + # PyTorch Profiler + profile: false + use_pytorch_profiler: true + profile_ranks: [0] + profile_step_start: 9 + profile_step_end: 11 + torch_profiler_record_shapes: true + torch_profiler_with_stack: true + torch_profiler_use_gzip: false + disable_profiler_activity_cpu: false + + # Primus Turbo Configuration + enable_primus_turbo: true + use_turbo_attention: true + use_dual_fp8_output_projection: false + + seed: 42 + + # Torch Compile — compatible with Float8 local spec (per-module FP8) + torch_compile: + enable: true + backend: "inductor" + mode: "default" + fullgraph: false + compile_optimizer: true + emulate_precision_casts: false + fused_ln_modulate: true diff --git a/examples/megatron/configs/MI355X/diffusion/flux_535m_pretrain.yaml b/examples/megatron/configs/MI355X/diffusion/flux_535m_pretrain.yaml new file mode 100644 index 000000000..0ec08c870 --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_535m_pretrain.yaml @@ -0,0 +1,170 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 535M Pretraining Configuration (Pre-encoded Data Mode) +# +# This config demonstrates Flux 535M training with pre-encoded features. +# Pre-encoded mode is faster and recommended for production training. +# +# Usage: +# EXP=examples/megatron/configs/MI355X/diffusion/flux_535m_pretrain.yaml \ +# bash examples/run_pretrain.sh + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_535m_pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_535m.yaml + + # Trainer class for diffusion models + trainer_class: FluxPretrainTrainer + + overrides: + # ============================================================================ + # Model Configuration + # ============================================================================ + + model_type: diffusion_model + + # ============================================================================ + # Dataset Configuration — Synthetic / Mock Data (default) + # ============================================================================ + # + # This 535M config defaults to in-memory synthetic data so it runs + # standalone for sanity checks, CI, and training-pipeline validation + # without a prepared Energon dataset. Samples are random tensors with + # Flux-correct shapes (latent_channels=16, T5 seq=512, CLIP pooled=768). + # + # To train on real data instead: set `mock_data: false` and point + # `data_path` at a prepared Energon dataset (see notes at the bottom). + mock_data: true + mock_dataset: + class: "primus.backends.megatron.data.synthetic.PreGeneratedMockFluxDataset" + params: + num_samples: 256 # In-memory synthetic samples (iterated cyclically) + image_size: 256 # 256 -> 32x32 latents (light/fast for testing) + # data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Batch configuration + micro_batch_size: 2 # Per-GPU batch size (adjust based on VRAM) + global_batch_size: 16 # Total batch size across all GPUs + seq_length: 4096 # Sequence length for latent features + + # DataLoader settings (used only with real Energon data, i.e. mock_data: false) + num_workers: 4 # Number of data loading workers per GPU + dataloader_type: external # Required for Energon dataloaders + + # ============================================================================ + # Training Parameters + # ============================================================================ + + # Total training steps + train_iters: 100000 # Total training iterations + eval_interval: 1000 # Evaluate every N steps + eval_iters: 50 # Number of evaluation iterations + + # Logging + log_interval: 10 # Log every N steps + tensorboard_dir: output/tensorboard/flux_535m_pretrain + log_throughput: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + + # Checkpointing (disabled) + save_interval: 5000 # Save checkpoint every N steps + save: null + load: null # Path to checkpoint for resuming (optional) + finetune: false + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Precision + bf16: true # Use bfloat16 (recommended for AMD MI355X) + fp16: false + + # ============================================================================ + # Optimizer Configuration + # ============================================================================ + + # Optimizer + optimizer: adam + lr: 1.0e-4 # Peak learning rate + min_lr: 1.0e-5 # Minimum learning rate + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.999 + adam_eps: 1.0e-8 + + # Gradient clipping + clip_grad: 1.0 + + # ============================================================================ + # Learning Rate Scheduler + # ============================================================================ + + lr_decay_style: cosine + lr_warmup_iters: 1000 # Warmup iterations + lr_decay_iters: 100000 # Total decay steps (typically = train_iters) + + # ============================================================================ + # Distributed Training Configuration + # ============================================================================ + + # Parallelism settings (Flux 535M fits on 1 GPU, but can use DP for speed) + tensor_model_parallel_size: 1 # Tensor parallelism (no need for 535M) + pipeline_model_parallel_size: 1 # Pipeline parallelism + + # Advanced settings + overlap_grad_reduce: true # Overlap gradient communication + use_flash_attn: true # Use Flash Attention 2 + distributed_timeout_minutes: 60 + + # ============================================================================ + # Seed and Reproducibility + # ============================================================================ + + seed: 42 + + # ============================================================================ + # Monitoring and Logging + # ============================================================================ + + wandb_project: flux_535m_pretrain + wandb_exp_name: flux_535m_preencoded + +# ============================================================================ +# Notes +# ============================================================================ +# +# Dataset Preparation: +# 1. Prepare pre-encoded dataset: +# tools/docker/primus data diffusion-encoded \ +# --source-type directory --input-dir /data/raw \ +# --output-dir /data/encoded --model-path black-forest-labs/FLUX.1-dev +# 2. Copy dataset template to output directory: +# cp primus/configs/data/megatron/diffusion/templates/dataset_preencoded.yaml \ +# /data/encoded/dataset.yaml +# 3. Run Energon indexing: +# energon prepare /data/encoded --num-workers 8 +# 4. Update data_path above to: /data/encoded/dataset.yaml +# +# For more information, see: +# - primus/configs/data/megatron/diffusion/README.md +# - examples/megatron/diffusion/README.md +# +# Single-node training (8 GPUs): +# EXP=examples/megatron/configs/MI355X/diffusion/flux_535m_pretrain.yaml \ +# GPUS_PER_NODE=8 bash examples/run_pretrain.sh +# +# Multi-node training (4 nodes, 8 GPUs each): +# EXP=examples/megatron/configs/MI355X/diffusion/flux_535m_pretrain.yaml \ +# NNODES=4 bash examples/run_slurm_pretrain.sh +# diff --git a/examples/megatron/configs/MI355X/diffusion/flux_535m_pretrain_fp8.yaml b/examples/megatron/configs/MI355X/diffusion/flux_535m_pretrain_fp8.yaml new file mode 100644 index 000000000..1ec0e6433 --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_535m_pretrain_fp8.yaml @@ -0,0 +1,199 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 535M FP8 Pretraining Configuration (Testing/Development) +# +# This config is for testing FP8 functionality with the minimal Flux 535M model +# before scaling to the full 12B model. Use this to validate: +# - FP8 setup and configuration +# - Numerical stability +# - Memory and speed improvements +# - Transformer Engine compatibility +# +# Target Hardware: Single AMD MI355X GPU with ROCm 6.0+ +# Requires: Transformer Engine 2.1.0+ with ROCm backend +# +# Usage: +# EXP=examples/megatron/configs/MI355X/diffusion/flux_535m_pretrain_fp8.yaml \ +# GPUS_PER_NODE=1 bash examples/run_pretrain.sh + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:flux_535m_pretrain_fp8} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_535m_fp8.yaml # Use FP8-enabled 535M config + + # Trainer class for diffusion models + trainer_class: FluxPretrainTrainer + + overrides: + # ============================================================================ + # Model Configuration + # ============================================================================ + + model_type: diffusion_model + + # ============================================================================ + # Dataset Configuration — Synthetic / Mock Data (default) + # ============================================================================ + # + # This 535M config defaults to in-memory synthetic data so it runs + # standalone for sanity checks, CI, and training-pipeline validation + # without a prepared Energon dataset. Samples are random tensors with + # Flux-correct shapes (latent_channels=16, T5 seq=512, CLIP pooled=768). + # + # To train on real data instead: set `mock_data: false` and point + # `data_path` at a prepared Energon dataset (see notes at the bottom). + mock_data: true + mock_dataset: + class: "primus.backends.megatron.data.synthetic.PreGeneratedMockFluxDataset" + params: + num_samples: 256 # In-memory synthetic samples (iterated cyclically) + image_size: 256 # 256 -> 32x32 latents (light/fast for testing) + # data_path: ${PRIMUS_DIFFUSION_DATA_PATH:/path/to/energon/dataset} + + # Batch configuration (can use larger batch with FP8) + micro_batch_size: 4 # Can increase from 2 to 4 with FP8 on 535M + global_batch_size: 32 # Small batch for quick testing + seq_length: 4096 # Sequence length for latent features + + # DataLoader settings (used only with real Energon data, i.e. mock_data: false) + num_workers: 4 # Number of data loading workers per GPU + dataloader_type: external # Required for Energon dataloaders + + # ============================================================================ + # Training Parameters (Quick Testing) + # ============================================================================ + + # Short training for validation + train_iters: 1000 # Just 1K steps for FP8 validation + eval_interval: 100 # Evaluate every 100 steps + eval_iters: 10 # Quick evaluation + + # Logging + log_interval: 10 # Log every N steps + tensorboard_dir: output/tensorboard/flux_535m_pretrain_fp8 + log_throughput: true + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + + # Checkpointing (disabled) + save_interval: 500 # Save more frequently for testing + save: null + load: null # Path to checkpoint for resuming (optional) + finetune: false + no_save_optim: true + no_save_rng: true + auto_continue_train: false + disable_last_saving: true + + # Precision + bf16: true # Use bfloat16 for non-FP8 ops + fp16: false + + # ============================================================================ + # Optimizer Configuration + # ============================================================================ + + # Optimizer + optimizer: adam + lr: 1.0e-4 # Peak learning rate + min_lr: 1.0e-5 # Minimum learning rate + weight_decay: 0.01 + adam_beta1: 0.9 + adam_beta2: 0.999 + adam_eps: 1.0e-8 + + # Gradient clipping (important for FP8 stability) + clip_grad: 1.0 + + # ============================================================================ + # Learning Rate Scheduler + # ============================================================================ + + lr_decay_style: cosine + lr_warmup_iters: 100 # Short warmup for testing + lr_decay_iters: 1000 # Match train_iters + + # ============================================================================ + # Distributed Training Configuration + # ============================================================================ + + # Single GPU configuration + tensor_model_parallel_size: 1 # No TP needed for 535M + pipeline_model_parallel_size: 1 # No PP needed + context_parallel_size: 1 # No CP needed + + # Distributed settings + use_distributed_optimizer: false # Not needed for single GPU + overlap_grad_reduce: false # Not applicable for single GPU + use_flash_attn: true # Use Flash Attention 2 + distributed_timeout_minutes: 60 + + # ============================================================================ + # Memory Optimization + # ============================================================================ + + # Activation checkpointing (not needed for 535M with FP8) + recompute_granularity: null # No recompute needed + recompute_method: null + recompute_num_layers: null + + # Sequence parallelism + sequence_parallel: false # Not needed for single GPU + + # ============================================================================ + # Seed and Reproducibility + # ============================================================================ + + seed: 42 + + # ============================================================================ + # Monitoring and Logging + # ============================================================================ + + wandb_project: flux_535m_pretrain_fp8 + wandb_exp_name: flux_535m_fp8_test + +# ============================================================================ +# Notes - FP8 Validation with 535M +# ============================================================================ +# +# Hardware Requirements (with FP8): +# - Minimum: 1× MI355X 256GB +# - Memory per GPU: ~3-5GB (vs ~7-10GB BF16) +# - Training time: Minutes +# +# Validation Checklist: +# [ ] Setup FP8 environment (see docs/04-technical-guides/diffusion-models/fp8_training.md) +# [ ] Verify TE FP8 support is available +# [ ] Run this config to validate FP8 training +# [ ] Check logs for NaN/Inf (should be none) +# [ ] Verify memory usage is ~50% of BF16 +# [ ] Verify training speed is 1.5-2x faster than BF16 +# [ ] Check loss decreases normally +# +# Expected Results: +# - Training completes 1000 steps in 5-15 minutes +# - No NaN/Inf in losses +# - Memory usage: ~3-5GB +# - Speed: ~10-50 steps/sec (depending on hardware) +# - Loss should decrease normally +# +# If validation passes, proceed to one of the 12B FP8 configs, e.g.: +# - flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml (TransformerEngine FP8) +# - flux_12b_fsdp2_energon_schnell_resample_local_spec_fp8.yaml (local-spec FP8) +# +# Troubleshooting: +# - If NaN/Inf: Check Transformer Engine FP8 support +# - If OOM: Reduce micro_batch_size +# - If slow: Verify ROCm FP8 tensor cores are being used +# - If unstable: Try fp8_wgrad: false in model config +# +# For more information: See docs/04-technical-guides/diffusion-models/fp8_training.md diff --git a/examples/megatron/configs/MI355X/diffusion/flux_535m_with_guidance_embed.yaml b/examples/megatron/configs/MI355X/diffusion/flux_535m_with_guidance_embed.yaml new file mode 100644 index 000000000..49cb41194 --- /dev/null +++ b/examples/megatron/configs/MI355X/diffusion/flux_535m_with_guidance_embed.yaml @@ -0,0 +1,58 @@ +# Copyright (c) 2026, Advanced Micro Devices, Inc. All rights reserved. +# Licensed under the Apache License, Version 2.0. + +# Flux 535M with Guidance Embedding (Advanced Configuration) +# +# This config demonstrates Flux training with guidance embedding enabled. +# This is an OPTIONAL advanced feature that allows for faster single-pass CFG +# during inference, but requires training with guidance embedding enabled. +# +# IMPORTANT: Most users should use the standard flux_535m_pretrain.yaml config. +# Only use this if you specifically need guidance embedding support. +# +# Usage: +# EXP=examples/megatron/configs/MI355X/diffusion/flux_535m_with_guidance_embed.yaml \ +# bash examples/run_pretrain.sh + +# Extend standard 535M config +extends: + - flux_535m_pretrain.yaml + +modules: + pre_trainer: + overrides: + # ============================================================================ + # Guidance Embedding Configuration (ADVANCED) + # ============================================================================ + + # Enable guidance embedding for single-pass CFG + # This adds a learned MLPEmbedder layer that conditions on guidance scale + guidance_embed: true + + # Guidance scale used during training + # Model learns to adapt its predictions based on this scale + guidance_scale: 3.5 + + # ============================================================================ + # Notes + # ============================================================================ + # + # Training with guidance embedding: + # - Adds ~1-2% more parameters (guidance MLPEmbedder) + # - Allows single-pass CFG during inference (faster) + # - Requires more training data/iterations to converge + # - Model learns guidance as a conditioning signal + # + # Inference with guidance embedding: + # - Pipeline automatically detects guidance_embed layer + # - Uses single forward pass instead of batch doubling + # - ~2x faster CFG compared to explicit CFG + # - Guidance scale can be varied at inference time + # + # Standard approach (guidance_embed: false): + # - Default for most Primus training + # - Uses explicit CFG (batch doubling) at inference + # - More compatible with existing checkpoints + # - Slightly slower but more flexible + # + # See examples/megatron/diffusion/README.md for more details. diff --git a/examples/megatron/configs/MI355X/gpt_oss_20B-BF16-pretrain.yaml b/examples/megatron/configs/MI355X/gpt_oss_20B-BF16-pretrain.yaml index 72baa5d20..731bf6fde 100644 --- a/examples/megatron/configs/MI355X/gpt_oss_20B-BF16-pretrain.yaml +++ b/examples/megatron/configs/MI355X/gpt_oss_20B-BF16-pretrain.yaml @@ -46,7 +46,7 @@ modules: profile_step_start: 6 # hyper parameters - train_iters: 10 + train_iters: 50 micro_batch_size: 8 global_batch_size: 512 seq_length: ${PRIMUS_SEQ_LENGTH:4096} diff --git a/examples/megatron/configs/MI355X/gpt_oss_20B-FP8-mlperf-pretrain.yaml b/examples/megatron/configs/MI355X/gpt_oss_20B-FP8-mlperf-pretrain.yaml new file mode 100644 index 000000000..5b62b5265 --- /dev/null +++ b/examples/megatron/configs/MI355X/gpt_oss_20B-FP8-mlperf-pretrain.yaml @@ -0,0 +1,222 @@ +work_group: ${TEAM:amd} +user_name: ${USER:root} +exp_name: ${EXP_NAME:gpt_oss_20b} +workspace: ./output + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + # model to run + model: ${PRIMUS_MODEL:gpt_oss_20B}.yaml + overrides: + + # Activate the migrated MLPerf pretrain trainer (mllog + MLPerf hooks). + stage: mlperf_pretrain + + # tokenizer + tokenizer_type: Llama3Tokenizer + tokenizer_model: ${MODEL:meta-llama/Llama-3.1-8B} + + # model + num_layers: 24 + hidden_size: 2880 + ffn_hidden_size: 2880 + num_attention_heads: 64 + num_query_groups: 8 # Group Query Attention (GQA) - matches HF num_key_value_heads + num_experts: 32 + activation_func: swiglu # SiLU activation (matches HF hidden_act: "silu") + + # rotary + position_embedding_type: rope + rotary_base: 150000 + + # mixed-precision + attention_softmax_in_fp32: false + grad_reduce_in_bf16: ${PRIMUS_GRAD_REDUCE_IN_BF16:true} + + # log + wandb_project: "Primus_GPT_OSS_20B" + stderr_sink_level: DEBUG + log_interval: ${LOG_INTERVAL:10} + + # debug + # moe_router_force_load_balancing: true + # log_avg_skip_iterations: 2 + # log_avg_reset_interval: 50 + + # profile + profile: ${PRIMUS_PROFILE:false} + use_pytorch_profiler: ${PRIMUS_PROFILE:false} + profile_step_end: ${PRIMUS_PROFILE_STEP_END:32} + profile_step_start: ${PRIMUS_PROFILE_STEP_START:16} + profile_ranks: [0,1,2,3,4,5,6,7] + + # enable fp8 training + fp8: e4m3 + fp8_recipe: tensorwise + clip_grad: 1.0 # Gradient clipping (already default, but explicit) + check_for_nan_in_loss_and_grad: false + + # hyper parameters + train_iters: ${PRIMUS_TRAIN_ITERS:1200000} + micro_batch_size: ${PRIMUS_MICRO_BATCH_SIZE:2} + global_batch_size: ${PRIMUS_GLOBAL_BATCH_SIZE:16} + seq_length: ${PRIMUS_SEQ_LENGTH:8192} + max_position_embeddings: ${PRIMUS_MAX_POSITION_EMBEDDINGS:131072} + seed: ${SEED:1234} # Random seed for reproducibility + lr: ${PRIMUS_LR:8.0e-4} # Reduced from 8e-4 for FP8 stability + min_lr: ${PRIMUS_MIN_LR:8.0e-5} # Set to 10% of max LR + lr_warmup_iters: ${PRIMUS_LR_WARMUP_ITERS:128} + lr_decay_iters: ${PRIMUS_LR_DECAY_ITERS:1199872} + lr_decay_style: cosine + weight_decay: 0.1 + optimizer: adam + use_distributed_optimizer: true # use distributed optimizer + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-5 + eod_mask_loss: true + init_method_std: 0.008 + norm_epsilon: 1.0e-6 + layernorm_epsilon: 1.0e-05 # RMSNorm epsilon (matches HF rms_norm_eps) + + # Dropout (disabled for training) + hidden_dropout: 0.0 + attention_dropout: 0.0 + + # parallel + tensor_model_parallel_size: ${PRIMUS_TP:1} + pipeline_model_parallel_size: ${PRIMUS_PP:1} + expert_model_parallel_size: ${PRIMUS_EP:8} + overlap_grad_reduce: true + overlap_param_gather: true + ddp_num_buckets: 8 + ddp_average_in_collective: true + + # data + mock_data: false + num_workers: ${PRIMUS_NUM_WORKERS:0} + train_data_path: "10 /data/c4-train.en_6_text_document" + valid_data_path: "/data/c4-validation-91205-samples.en_text_document" + test_data_path: "/data/c4-validation-91205-samples.en_text_document" + # Avoid copying a dense (B, 1, S, S) CPU attention mask every step. + # TE receives causal/sliding-window metadata from attn_mask_type + window_size. + # Use numeric 0/1 because Primus env expansion only type-casts numbers. + create_attention_mask_in_dataloader: ${PRIMUS_CREATE_ATTENTION_MASK_IN_DATALOADER:0} + + # fusion + moe_permute_fusion: true + gradient_accumulation_fusion: true + moe_use_legacy_grouped_gemm: false # Sync-Free MoE stage 2 or 3 require PrimusTurboGroupedMLP, please set `moe_use_legacy_grouped_gemm=True + moe_use_fused_router_with_aux_score: true + multi_latent_attention: false # Flag config.ENABLE_EXPERIMENTAL not enabled + apply_rope_fusion: true + + + # sliding window attention (GPT-OSS-20B model definition; matches HF sliding_window: 128) + # use_turbo_attention is false so non-turbo attention (which supports sliding window) is used. + # Pattern: alternating sliding_attention (1) and full_attention (0) for 24 layers + # window_size must be a tuple (left_window, right_window) for Transformer Engine + # For causal attention: left = past tokens, right = 0 (no future tokens) + # HF sliding_window: 128 means 128 past tokens, so use (128, 0) + window_size: [128, 0] # Left window: 128 past tokens, Right: 0 (causal) + window_attn_skip_freq: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] + + # MoE settings + moe_apply_probs_on_input: false + moe_aux_loss_coeff: 0.0 #0.9 + moe_deepep_num_sms: 20 + moe_enable_deepep: false + moe_expert_capacity_factor: null + moe_extended_tp: false + moe_ffn_hidden_size: 2880 + moe_flex_dispatcher_backend: deepep + moe_grouped_gemm: true + moe_hybridep_num_sms: 16 + moe_input_jitter_eps: null + moe_latent_size: null + moe_layer_freq: 1 + moe_layer_recompute: false + moe_pad_expert_input_to_capacity: false + moe_per_layer_logging: false + moe_router_bias_update_rate: 0.001 + moe_router_dtype: fp32 # DeepEP only supports float32 probs + moe_router_enable_expert_bias: false + moe_router_force_load_balancing: false + moe_router_fusion: true + moe_router_group_topk: null + moe_router_load_balancing_type: none + moe_router_num_groups: null + moe_router_padding_for_fp8: false + moe_router_padding_for_quantization: false + moe_router_pre_softmax: false + moe_router_score_function: softmax + moe_router_topk: 4 + moe_router_topk_limited_devices: null + moe_router_topk_scaling_factor: null + moe_shared_expert_gate: false + moe_shared_expert_intermediate_size: null + moe_shared_expert_overlap: false + moe_token_dispatcher_type: alltoall + moe_token_drop_policy: probs + moe_token_dropping: false + moe_z_loss_coeff: null + + # ckpt + finetune: false + auto_continue_train: false + load: null + no_load_optim: null + no_load_rng: null + save: null + save_interval: 100000 + no_save_optim: null + no_save_rng: null + disable_last_saving: true + exit_on_missing_checkpoint: false + ckpt_format: torch + eval_iters: ${EVAL_ITERS:64} # eval_samples = eval_iters * GBS = 1024; set EVAL_ITERS in config shell (1024/GBS). + eval_interval: ${PRIMUS_EVAL_INTERVAL:768} + + # Turbo + enable_primus_turbo: true + use_turbo_attention: false + use_turbo_grouped_gemm: true + use_turbo_rms_norm: ${USE_TURBO_RMS_NORM:true} + use_turbo_fused_act_with_probs : true + # Pad tokens-per-expert so the fp8 grouped GEMM path skips the buggy + # quantization_padding branch in PrimusGroupedMLP.forward (experts.py:97-109), + # which yields NaN with recompute. Not auto-enabled here because + # turbo_sync_free_moe_stage=0 (it is only auto-set for sync-free stages 1-3). + use_turbo_permute_padding: true + + # deepep + use_turbo_deepep: false + + # 64 or 80 for ep8, 32 for ep16-64 is best practice + turbo_deepep_num_cu: 64 + turbo_deepep_use_comm_stream: false + + # sync-free moe support stage 0-3, 0 means not use sync-free moe + # stage 3 is completely no gpu-cpu sync in MoE, but cost more memory + # stage 2 is recommended for better performance + turbo_sync_free_moe_stage: 0 + + # Cross entropy flags + cross_entropy_fusion_impl: "te" + cross_entropy_loss_fusion: true + + # tensorboard logging, set 'disable_tensorboard: false' to enable tensorboard logging + disable_tensorboard: true + tensorboard_dir: /workspace/code/tensorboard + tensorboard_log_interval: 1 + tensorboard_queue_size: 1000 + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_validation_ppl_to_tensorboard: true + log_memory_to_tensorboard: true + log_world_size_to_tensorboard: true + log_loss_scale_to_tensorboard: true diff --git a/examples/megatron/configs/MI355X/gpt_oss_20B-FP8-pretrain.yaml b/examples/megatron/configs/MI355X/gpt_oss_20B-FP8-pretrain.yaml index 9315ccc55..8d614c817 100644 --- a/examples/megatron/configs/MI355X/gpt_oss_20B-FP8-pretrain.yaml +++ b/examples/megatron/configs/MI355X/gpt_oss_20B-FP8-pretrain.yaml @@ -46,9 +46,9 @@ modules: profile_step_start: 6 # hyper parameters - train_iters: 10 - micro_batch_size: 8 - global_batch_size: 512 + train_iters: 50 + micro_batch_size: 6 + global_batch_size: 48 seq_length: ${PRIMUS_SEQ_LENGTH:4096} max_position_embeddings: ${PRIMUS_MAX_POSITION_EMBEDDINGS:4096} lr: 1.0e-5 @@ -106,6 +106,7 @@ modules: eval_iters: 0 cross_entropy_loss_fusion: true + cross_entropy_fusion_impl: "te" fp8: hybrid # enable_primus_turbo: true diff --git a/examples/megatron/configs/MI355X/llama2_70B-BF16-pretrain.yaml b/examples/megatron/configs/MI355X/llama2_70B-BF16-pretrain.yaml index ff4f67060..0be54d697 100755 --- a/examples/megatron/configs/MI355X/llama2_70B-BF16-pretrain.yaml +++ b/examples/megatron/configs/MI355X/llama2_70B-BF16-pretrain.yaml @@ -17,8 +17,8 @@ modules: log_avg_reset_interval: 50 train_iters: 50 - micro_batch_size: 14 - global_batch_size: 224 + micro_batch_size: 13 + global_batch_size: 104 seq_length: 4096 max_position_embeddings: 4096 diff --git a/examples/megatron/configs/MI355X/llama3.1_70B-FP8-pretrain.yaml b/examples/megatron/configs/MI355X/llama3.1_70B-FP8-pretrain.yaml index 9975256f7..2c2fa0b68 100644 --- a/examples/megatron/configs/MI355X/llama3.1_70B-FP8-pretrain.yaml +++ b/examples/megatron/configs/MI355X/llama3.1_70B-FP8-pretrain.yaml @@ -17,8 +17,8 @@ modules: log_avg_reset_interval: 50 train_iters: 50 - micro_batch_size: 4 - global_batch_size: 32 + micro_batch_size: 3 + global_batch_size: 24 seq_length: 8192 max_position_embeddings: 8192 diff --git a/examples/megatron/configs/MI355X/llama3.1_8B-MXFP4-pretrain.yaml b/examples/megatron/configs/MI355X/llama3.1_8B-MXFP4-pretrain.yaml index f5072cb46..e5e920603 100644 --- a/examples/megatron/configs/MI355X/llama3.1_8B-MXFP4-pretrain.yaml +++ b/examples/megatron/configs/MI355X/llama3.1_8B-MXFP4-pretrain.yaml @@ -110,7 +110,6 @@ modules: # --- Primus Turbo Config --- enable_primus_turbo: true use_turbo_attention: true - use_turbo_fp4_autocast: false # TE mxfp4 recipe should set it to false use_turbo_gemm: false # can't use together with delayed recipe use_turbo_grouped_gemm: false moe_use_fused_router_with_aux_score: false diff --git a/examples/megatron/configs/MI355X/qwen14B-odc-dn.yaml b/examples/megatron/configs/MI355X/qwen14B-odc-dn.yaml new file mode 100644 index 000000000..88d3242cd --- /dev/null +++ b/examples/megatron/configs/MI355X/qwen14B-odc-dn.yaml @@ -0,0 +1,125 @@ +### DeepSeek-R1-Distill-Qwen-14B ODC dual-node SFT example. +### Sequence-length load balancing (LB-Mini) with the "fit" cost model on the +### torch-FSDP2 + ODC path; packed (thd) attention enabled. + +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:qwen14B-odc-dn} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + post_trainer: + framework: megatron + config: sft_trainer.yaml + + model: qwen2.5_14B.yaml + + overrides: + stage: sft + + hf_path: deepseek-ai/DeepSeek-R1-Distill-Qwen-14B + tokenizer_model: deepseek-ai/DeepSeek-R1-Distill-Qwen-14B + + sft_dataset_name: "zai-org/LongAlign-10k" + sft_conversation_format: "messages" + + # ODC on-demand communication (rocSHMEM/XGMI P2P gradient reduction) on the + # torch-FSDP2 path. Master switch (was the ODC_ENABLE env var). + enable_odc: true + odc_phase: 2 + + # thd segmented (packed) attention. + enable_packed_sequences: true + use_packed_attention: true + # LB-Mini sequence-length load balancing (was the ODC_LB_MINI env var). + enable_odc_lb_mini: true + # LB-Mini cost model: fit (attention-aware a*s^2 + b*s). + lb_mini_cost_model: fit + + # ODC P2P backend + dual-node GDA (validated). trainer_base defaults are mori / false. + odc_p2p_backend: rocshmem + odc_rocshmem_gda: true + + wandb_project: "Primus_ODC_Qwen14B" + stderr_sink_level: DEBUG + log_avg_skip_iterations: 2 + log_avg_reset_interval: 100 + + train_iters: 50 + micro_batch_size: 1 + global_batch_size: 16 + + seq_length: 65536 + max_position_embeddings: 65536 + + lr: 2.0e-6 + min_lr: 0.0 + lr_warmup_iters: 2 + lr_decay_iters: 100 + lr_decay_style: cosine + weight_decay: 0.1 + adam_beta1: 0.9 + adam_beta2: 0.95 + + seed: 1234 + eod_mask_loss: false + init_method_std: 0.008 + norm_epsilon: 1.0e-5 + add_qkv_bias: true + apply_rope_fusion: false + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + context_parallel_size: 1 + sequence_parallel: false + recompute_granularity: full + recompute_method: uniform + recompute_num_layers: 1 + + enable_fused_linear_ce: false + + use_torch_fsdp2: true + use_megatron_fsdp: false + use_distributed_optimizer: false + overlap_grad_reduce: false + overlap_param_gather: false + gradient_accumulation_fusion: false + + finetune: true + # Megatron checkpoint to warm-start from. Set PRETRAINED_CKPT to your own + # converted DeepSeek-R1-Distill-Qwen-14B checkpoint dir; null trains from + # the HF weights only. + pretrained_checkpoint: ${PRETRAINED_CKPT:null} + load: null + save: null + save_interval: 1000 + eval_interval: 1000 + no_save_optim: null + no_save_rng: null + disable_last_saving: true + ckpt_format: torch_dist + + bf16: true + + enable_primus_turbo: false + use_turbo_attention: false + use_turbo_grouped_mlp: false + use_turbo_rms_norm: false + + eval_iters: 0 + + profile: false + use_pytorch_profiler: true + use_nsys_profiler: false + profile_step_start: 8 + profile_step_end: 9 + profile_ranks: [0] + pytorch_profiler_collect_shapes: true + pytorch_profiler_collect_callstack: false + pytorch_profiler_collect_chakra: false + record_shapes: false + record_memory_history: false + nvtx_ranges: false + + lora: + enabled: false diff --git a/examples/megatron/configs/MI355X/qwen3_30B_A3B-BF16-pretrain.yaml b/examples/megatron/configs/MI355X/qwen3_30B_A3B-BF16-pretrain.yaml index a9c45a614..1510d90f1 100644 --- a/examples/megatron/configs/MI355X/qwen3_30B_A3B-BF16-pretrain.yaml +++ b/examples/megatron/configs/MI355X/qwen3_30B_A3B-BF16-pretrain.yaml @@ -21,7 +21,7 @@ modules: log_avg_reset_interval: 50 # hyper parameters - train_iters: 10 + train_iters: 50 micro_batch_size: 8 global_batch_size: 512 seq_length: ${PRIMUS_SEQ_LENGTH:4096} diff --git a/examples/megatron/configs/MI355X/qwen3_30B_A3B-FP8-pretrain.yaml b/examples/megatron/configs/MI355X/qwen3_30B_A3B-FP8-pretrain.yaml index ef8965ae6..057fb0b6c 100644 --- a/examples/megatron/configs/MI355X/qwen3_30B_A3B-FP8-pretrain.yaml +++ b/examples/megatron/configs/MI355X/qwen3_30B_A3B-FP8-pretrain.yaml @@ -21,9 +21,9 @@ modules: log_avg_reset_interval: 50 # hyper parameters - train_iters: 10 - micro_batch_size: 8 - global_batch_size: 512 + train_iters: 50 + micro_batch_size: 6 + global_batch_size: 48 seq_length: ${PRIMUS_SEQ_LENGTH:4096} max_position_embeddings: ${PRIMUS_MAX_POSITION_EMBEDDINGS:4096} lr: 1.0e-5 diff --git a/examples/megatron/diffusion/README.md b/examples/megatron/diffusion/README.md new file mode 100644 index 000000000..bfc6ada27 --- /dev/null +++ b/examples/megatron/diffusion/README.md @@ -0,0 +1,317 @@ +# Flux Diffusion Model Training Examples + +Training examples for Flux diffusion models with Primus-Megatron on AMD GPUs. + +## Related Documentation + +- **Architecture & Developer Guide:** [docs/04-technical-guides/diffusion-models/README.md](../../../docs/04-technical-guides/diffusion-models/README.md) +- **API Reference:** [docs/04-technical-guides/diffusion-models/api_reference.md](../../../docs/04-technical-guides/diffusion-models/api_reference.md) +- **FP8 Training Guide:** [docs/04-technical-guides/diffusion-models/fp8_training.md](../../../docs/04-technical-guides/diffusion-models/fp8_training.md) +- **MXFP4 Training Guide:** [docs/04-technical-guides/diffusion-models/mxfp4_training.md](../../../docs/04-technical-guides/diffusion-models/mxfp4_training.md) +- **Dataset Preparation:** [primus/configs/data/megatron/diffusion/README.md](../../../primus/configs/data/megatron/diffusion/README.md) +- **Tests:** [tests/unit_tests/backends/megatron/diffusion/](../../../tests/unit_tests/backends/megatron/diffusion/) + +--- + +## Quick Start + +### Prerequisites + +- AMD Instinct GPU(s) (MI300X, MI325X, MI355X) +- Docker or Podman with ROCm support +- Primus Docker image: `docker.io/rocm/primus:v26.1` +- Prepared dataset (see [Dataset Preparation](../../../primus/configs/data/megatron/diffusion/README.md)) + +### 5-Minute Test Run + +1. **Prepare a small test dataset:** + +```bash +mkdir -p /tmp/flux_test_data/raw && cd /tmp/flux_test_data/raw + +for i in {000..099}; do + convert -size 512x512 xc:blue sample_${i}.jpg + echo "A blue square" > sample_${i}.txt +done + +tar -cf train-000000.tar sample_*.jpg sample_*.txt + +cat > dataset.yaml << 'EOF' +__module__: megatron.energon +__class__: CrudeWebdataset +subflavors: + encoding: raw +EOF + +energon prepare . --num-workers 4 +``` + +2. **Launch training:** + +```bash +EXP=examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain.yaml \ +DATA_PATH=/tmp/flux_test_data \ +GPUS_PER_NODE=1 \ +bash examples/run_pretrain.sh +``` + +--- + +## Model Variants + +| Feature | Flux 535M | Flux 12B | +|---------|-----------|----------| +| Parameters | 535M | 12B | +| Joint Layers | 1 | 19 | +| Single Layers | 1 | 38 | +| Min GPUs | 1 | 8 (FSDP2 / DDP) | +| Recommended GPUs | 1-8 | 8-64 | +| Sharding | DP | FSDP2 (ZeRO-2/3) or DDP + distributed optimizer | +| Best For | Testing | Production | + +--- + +## Available Configurations + +The same configs are provided for both MI300X (`examples/megatron/configs/MI300X/diffusion/`) +and MI355X (`examples/megatron/configs/MI355X/diffusion/`). The only differences +are hardware-tuned batch sizes (MI300X has 192GB HBM3, MI355X has 256GB), so MI300X +uses smaller default micro/global batch sizes on the 12B DDP configs. + +### Shared (MI300X and MI355X) + +| Config | Description | +|--------|-------------| +| `flux_535m_pretrain.yaml` | Flux 535M, BF16, single/multi-GPU | +| `flux_535m_pretrain_fp8.yaml` | Flux 535M with FP8 precision | +| `flux_535m_with_guidance_embed.yaml` | Flux 535M with guidance embedding | +| `flux_12b_fsdp2_energon_schnell_resample_local_spec.yaml` | Flux 12B, FSDP2, BF16, local spec | +| `flux_12b_fsdp2_energon_schnell_resample_local_spec_fp8.yaml` | Flux 12B, FSDP2, FP8, local spec | +| `flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml` | Flux 12B, DDP, FP8, local spec (delayed scaling) | +| `flux_12b_ddp_energon_schnell_resample_te_spec.yaml` | Flux 12B, DDP, BF16, TransformerEngine spec | +| `flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml` | Flux 12B, DDP, FP8, TransformerEngine spec | + +### MI355X only + +| Config | Description | +|--------|-------------| +| `flux_12b_ddp_energon_schnell_resample_local_spec_mxfp4.yaml` | Flux 12B, DDP, MXFP4, local spec | +| `flux_12b_ddp_energon_schnell_resample_local_spec_fp8_mlperf.yaml` | MLPerf benchmark reproduction (local spec FP8) | +| `flux_12b_ddp_energon_schnell_resample_te_spec_fp8_mlperf.yaml` | MLPerf benchmark reproduction (TE spec FP8) | + +> The `*_mlperf.yaml` configs reproduce the MLPerf Training Flux.1 benchmark +> (MLPerf logging + convergence target). Use the non-MLPerf configs above for +> general training. + +--- + +## Training Modes + +### Single-Node Training + +```bash +# Flux 535M (1-8 GPUs) +EXP=examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain.yaml \ +GPUS_PER_NODE=8 \ +bash examples/run_pretrain.sh + +# Flux 12B (FSDP2, BF16, 8 GPUs) +EXP=examples/megatron/configs/MI300X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec.yaml \ +GPUS_PER_NODE=8 \ +bash examples/run_pretrain.sh +``` + +### Multi-Node Training (SLURM) + +```bash +export DOCKER_IMAGE="docker.io/rocm/primus:v26.1" +export NNODES=8 +export GPUS_PER_NODE=8 + +EXP=examples/megatron/configs/MI300X/diffusion/flux_12b_fsdp2_energon_schnell_resample_local_spec.yaml \ +bash examples/run_slurm_pretrain.sh +``` + +### MLPerf Benchmark Reproduction (MI355X) + +The `*_mlperf.yaml` configs reproduce the MLPerf Training Flux.1 benchmark and are +intended for benchmark reproduction rather than general training. + +```bash +# Step 1: Ingest MLPerf data +primus-cli direct -- data diffusion-ingest \ + --config primus/configs/data/megatron/diffusion/preprocessing/mlperf_flux1.yaml + +# Step 2: Train (configs already set vae_latent_mode: resample, vae_scale: 0.3611, vae_shift: 0.1159) +EXP=examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8_mlperf.yaml \ +GPUS_PER_NODE=8 \ +bash examples/run_pretrain.sh +``` + +--- + +## FP8 Training + +FP8 provides ~2x memory reduction and 1.5-2x training speedup on AMD MI300X/MI355X GPUs. + +```bash +# Quick validation with 535M +EXP=examples/megatron/configs/MI300X/diffusion/flux_535m_pretrain_fp8.yaml \ +GPUS_PER_NODE=1 \ +bash examples/run_pretrain.sh + +# Production with 12B (TransformerEngine FP8) +EXP=examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_te_spec_fp8.yaml \ +GPUS_PER_NODE=8 NNODES=4 \ +bash examples/run_slurm_pretrain.sh + +# Production with 12B (local-spec FP8, no TransformerEngine dependency) +EXP=examples/megatron/configs/MI300X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_fp8.yaml \ +GPUS_PER_NODE=8 NNODES=4 \ +bash examples/run_slurm_pretrain.sh +``` + +| Model | Precision | Memory/GPU | Batch Size | Speed | +|-------|-----------|------------|------------|-------| +| Flux 535M | BF16 | ~7-10GB | 2 | 1.0x | +| Flux 535M | FP8 | ~3-5GB | 4 | 1.5-2x | +| Flux 12B | BF16 | ~40-50GB | 1 | 1.0x | +| Flux 12B | FP8 | ~20-25GB | 2 | 1.5-2x | + +For configuration details, tuning recipes, benchmarks, and troubleshooting, see the [FP8 Training Guide](../../../docs/04-technical-guides/diffusion-models/fp8_training.md). + +--- + +## MXFP4 Training + +MXFP4 (E2M1 + E8M0 block-of-32 scales) Flux 12B training on MI355X is supported via the local-spec provider (`PrimusTurboMXFP4LocalSpecProvider`, no TransformerEngine dependency). Forward and weight GEMMs run in FP4 through Primus-Turbo + AITER; attention, the optimizer state, and inter-rank communication stay in BF16. + +```bash +# Path to a checkout of the `tuned_gemm_configs` directory. +# Set TUNED_GEMM_DIR to wherever you have the tuned configs available. +export TUNED_GEMM_DIR=${TUNED_GEMM_DIR:-/path/to/tuned_gemm_configs} + +EXP=examples/megatron/configs/MI355X/diffusion/flux_12b_ddp_energon_schnell_resample_local_spec_mxfp4.yaml \ +PRIMUS_TURBO_GEMM_BACKEND=FP4:AITER \ +AITER_CONFIG_GEMM_A4W4=$TUNED_GEMM_DIR/mi355x/flux_12b.csv \ +AITER_LOG_TUNED_CONFIG=1 \ +bash examples/run_pretrain.sh +``` + +For configuration knobs, backend-selector semantics, tuned-GEMM verification, and troubleshooting, see the [MXFP4 Training Guide](../../../docs/04-technical-guides/diffusion-models/mxfp4_training.md). + +--- + +## Converting HuggingFace Checkpoints + +Convert pre-trained HuggingFace Flux checkpoints to Primus/Megatron-Core format: + +```bash +python tools/checkpoint_conversion/convert_flux_hf_to_primus.py \ + --input black-forest-labs/FLUX.1-dev \ + --output checkpoints/primus_flux_12b.safetensors \ + --variant flux_12b +``` + +Supported variants: `flux_535m`, `flux_12b`, `custom` (with `--num-joint-layers` / `--num-single-layers`). + +For gated models (FLUX.1-dev), set `export HF_TOKEN="your_token"` or run `huggingface-cli login`. + +Primus also auto-detects tokens from `.hf_token` at the project root or `~/.cache/huggingface/token`. + +--- + +## Configuration Reference + +### Key Training Parameters + +```yaml +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + model: diffusion/flux_535m.yaml + trainer_class: FluxPretrainTrainer + + overrides: + train_iters: 100000 + micro_batch_size: 2 + global_batch_size: 16 + lr: 1.0e-4 + min_lr: 1.0e-5 + weight_decay: 0.01 + clip_grad: 1.0 + lr_decay_style: cosine + lr_warmup_iters: 1000 +``` + +### Parallelism + +The Flux 12B configs scale with FSDP2 (ZeRO-2/3) or DDP + distributed optimizer +rather than tensor/pipeline parallelism. + +| Setting | Flux 535M | Flux 12B | +|---------|-----------|----------| +| `tensor_model_parallel_size` | 1 | 1 | +| `pipeline_model_parallel_size` | 1 | 1 | +| `context_parallel_size` | 1 | 1 | +| Sharding | DP | FSDP2 (`use_torch_fsdp2: true`) or DDP (`use_distributed_optimizer: true`) | + +### Memory Optimization + +```yaml +modules: + pre_trainer: + overrides: + recompute_granularity: selective # or 'full' + recompute_method: block + sequence_parallel: false +``` + +--- + +## Troubleshooting + +### Dataset Not Found + +Verify `data_path` in your config, ensure `energon prepare` was run, and check that `dataset.yaml` exists. + +### Out of Memory (OOM) + +Reduce `micro_batch_size`, increase `tensor_model_parallel_size`, enable `recompute_granularity: full`, or switch to pre-encoded data mode. + +### NaN Loss + +Reduce learning rate to `1.0e-5`, ensure `clip_grad: 1.0`, increase `lr_warmup_iters`, check dataset for corruption. + +### NaN Loss with MLPerf Data + +Ensure config includes the required normalization constants: + +```yaml +vae_latent_mode: resample +vae_scale: 0.3611 +vae_shift: 0.1159 +``` + +### Encoder Download Fails + +Set `export HF_TOKEN=your_token` or use a local model path via `encoder_model_path` in config overrides. + +### Slow Training + +Use pre-encoded data (2-3x faster), increase `num_workers`, enable `use_flash_attn: true`. + +--- + +## Source Code Pointers + +- **Model Architecture:** `primus/backends/megatron/core/models/diffusion/flux/` +- **Trainer:** `primus/modules/trainer/megatron/diffusion/flux_pretrain_trainer.py` +- **Data Pipeline:** `primus/backends/megatron/data/diffusion/` +- **Model Configs:** `primus/configs/models/megatron/diffusion/` + +## Getting Help + +- [GitHub Issues](https://github.com/AMD-AGI/Primus/issues) +- [GitHub Discussions](https://github.com/AMD-AGI/Primus/discussions) diff --git a/examples/megatron/prepare.py b/examples/megatron/prepare.py index bdad391c3..86725f311 100644 --- a/examples/megatron/prepare.py +++ b/examples/megatron/prepare.py @@ -282,6 +282,15 @@ def build_megatron_helper(primus_path: Path, patch_args: Path, backend_path: str f"if you actually need it). SFT does not require this dependency." ) return + # This prepare step may run once PER RANK (e.g. 8-16 concurrent processes on a + # node under torchrun), and concurrent `pip install -e` writes race on the shared + # venv .pth file (OSError). If the package is already importable (one-time + # pre-install), take a fast, write-free path so all ranks agree without racing. + import importlib.util + + if importlib.util.find_spec("emerging_optimizers") is not None: + log_info("Emerging-Optimizers already installed; skipping editable reinstall.") + return log_info(f"Building Emerging Optimizers in {emerging_optimizers_path}") ret = subprocess.run( ["pip", "install", "--no-build-isolation", "-e", str(emerging_optimizers_path)], check=True diff --git a/examples/megatron/prepare_fineweb_edu.py b/examples/megatron/prepare_fineweb_edu.py new file mode 100644 index 000000000..54afdd185 --- /dev/null +++ b/examples/megatron/prepare_fineweb_edu.py @@ -0,0 +1,180 @@ +############################################################################### +# End-to-End FineWeb-Edu Preparation Script +############################################################################### + +import argparse +import os +import subprocess +import time +from pathlib import Path + + +def prepare_fineweb_edu_dataset( + primus_path: Path, + data_path: Path, + tokenizer_type: str, + tokenizer_model: str, + sample_size: str = "10BT", + workers: int = None, + overwrite: bool = False, +): + """Prepare FineWeb-Edu dataset for Megatron training.""" + + dataset_name = f"fineweb-edu-{sample_size}" + dataset_path = data_path / dataset_name + output_path = dataset_path / tokenizer_type + + # Set HuggingFace cache + hf_home = Path(os.environ.get("HF_HOME", data_path / "huggingface")) + os.environ["HF_HOME"] = str(hf_home) + + # Output tokenized files + tokenized_prefix = output_path / f"fineweb_edu_{sample_size}" + tokenized_bin = tokenized_prefix.with_name(f"{tokenized_prefix.name}_text_sentence.bin") + tokenized_idx = tokenized_prefix.with_name(f"{tokenized_prefix.name}_text_sentence.idx") + + # Check if already processed + if tokenized_bin.exists() and tokenized_idx.exists(): + if overwrite: + print(f"[Info] Overwriting existing tokenized files.") + tokenized_bin.unlink() + tokenized_idx.unlink() + else: + print(f"[Info] Tokenized files exist, skipping preprocessing.") + print(f" - {tokenized_bin}") + print(f" - {tokenized_idx}") + print(f"[Hint] Use --overwrite to force re-tokenization with a different tokenizer.") + return tokenized_prefix.with_name(f"{tokenized_prefix.name}_text_sentence") + + output_path.mkdir(parents=True, exist_ok=True) + dataset_json = dataset_path / f"fineweb_edu_{sample_size}_megatron.json" + + # Step 1: Download dataset if not exists + if dataset_json.exists(): + print(f"[Info] Found dataset file: {dataset_json}, skipping download.") + else: + print(f"[Info] Downloading FineWeb-Edu ({sample_size}) dataset...") + subprocess.run( + [ + "python3", + str(primus_path / "examples/megatron/prepare_fineweb_edu_megatron_dataset.py"), + "--out-dir", + str(dataset_path), + "--sample-size", + sample_size, + ], + check=True, + ) + print("[Info] Download completed.") + + # Step 2: Tokenize and create binary files + print(f"[Info] Preprocessing dataset with tokenizer {tokenizer_type} / {tokenizer_model}") + start = time.time() + + if workers is None: + workers = os.cpu_count() + + env = os.environ.copy() + megatron_path = str(primus_path / "third_party" / "Megatron-LM") + env["PYTHONPATH"] = f"{primus_path}:{megatron_path}:{env.get('PYTHONPATH', '')}" + + subprocess.run( + [ + "python3", + str(primus_path / "examples/megatron/preprocess_data.py"), + "--input", + str(dataset_json), + "--tokenizer-type", + tokenizer_type, + "--tokenizer-model", + tokenizer_model, + "--output-prefix", + str(tokenized_prefix), + "--workers", + str(workers), + "--split-sentences", + "--append-eod", + "--partitions", + "4", # Increase for larger datasets + "--log-interval", + "10000", + ], + env=env, + check=True, + ) + + elapsed = int(time.time() - start) + print(f"[Info] Preprocessing completed in {elapsed} seconds ({elapsed/60:.1f} minutes)") + + return tokenized_prefix.with_name(f"{tokenized_prefix.name}_text_sentence") + + +def main(): + parser = argparse.ArgumentParser(description="Prepare FineWeb-Edu dataset for Megatron-LM training") + parser.add_argument("--primus-path", type=str, required=True, help="Root path to the Primus project") + parser.add_argument("--data-path", type=str, required=True, help="Path to data directory") + parser.add_argument( + "--tokenizer-type", + type=str, + default="HuggingFaceTokenizer", + help="Tokenizer type (HuggingFaceTokenizer, GPT2BPETokenizer, etc.)", + ) + parser.add_argument( + "--tokenizer-model", type=str, default="meta-llama/Llama-3.2-1B", help="Tokenizer model name or path" + ) + parser.add_argument( + "--sample-size", + type=str, + default="10BT", + choices=["10BT", "100BT", "350BT", "sample-10BT", "sample-100BT", "sample-350BT"], + help="FineWeb-Edu dataset size", + ) + parser.add_argument( + "--workers", type=int, default=None, help="Number of workers for preprocessing (default: CPU count)" + ) + parser.add_argument( + "--overwrite", + action="store_true", + help="Overwrite existing tokenized files (use when switching tokenizers)", + ) + + args = parser.parse_args() + + primus_path = Path(args.primus_path).resolve() + data_path = Path(args.data_path).resolve() + + print("=" * 70) + print("FineWeb-Edu Dataset Preparation for Megatron-LM") + print("=" * 70) + print(f"Primus Path: {primus_path}") + print(f"Data Path: {data_path}") + print(f"Tokenizer: {args.tokenizer_type} / {args.tokenizer_model}") + print(f"Sample Size: {args.sample_size}") + print(f"Workers: {args.workers or os.cpu_count()}") + print("=" * 70) + + # Check HF_TOKEN + if not os.environ.get("HF_TOKEN"): + print("[Warning] HF_TOKEN not set. You may need it for gated datasets.") + + output_prefix = prepare_fineweb_edu_dataset( + primus_path=primus_path, + data_path=data_path, + tokenizer_type=args.tokenizer_type, + tokenizer_model=args.tokenizer_model, + sample_size=args.sample_size, + workers=args.workers, + overwrite=args.overwrite, + ) + + print("\n" + "=" * 70) + print("SUCCESS! Dataset is ready for training.") + print("=" * 70) + print(f"\nAdd this to your training config:\n") + print(f" train_data_path: {output_prefix}") + print(f" mock_data: false") + print("\n" + "=" * 70) + + +if __name__ == "__main__": + main() diff --git a/examples/megatron/prepare_fineweb_edu.sh b/examples/megatron/prepare_fineweb_edu.sh new file mode 100644 index 000000000..8070b0c4d --- /dev/null +++ b/examples/megatron/prepare_fineweb_edu.sh @@ -0,0 +1,15 @@ +#!/bin/bash +# Set Python path +megatron_path="$(pwd)/third_party/Megatron-LM" +export PYTHONPATH="${megatron_path}:${PYTHONPATH}" + +# Verify the import works +python3 -c "from megatron.core.datasets import indexed_dataset; print('Import successful')" + +# Then run your preparation script +python examples/megatron/prepare_fineweb_edu.py \ + --primus-path . \ + --data-path ./data \ + --tokenizer-type HuggingFaceTokenizer \ + --tokenizer-model meta-llama/Llama-3.2-1B \ + --sample-size 10BT diff --git a/examples/megatron/prepare_fineweb_edu_megatron_dataset.py b/examples/megatron/prepare_fineweb_edu_megatron_dataset.py new file mode 100644 index 000000000..d7a591716 --- /dev/null +++ b/examples/megatron/prepare_fineweb_edu_megatron_dataset.py @@ -0,0 +1,65 @@ +############################################################################### +# Prepare FineWeb-Edu Dataset for Megatron-LM +############################################################################### + +import argparse +from pathlib import Path + +from datasets import load_dataset + + +def prepare_fineweb_edu_dataset(out_dir: Path, sample_size: str = "10BT"): + """ + Download FineWeb-Edu dataset and convert to JSON format. + + Args: + out_dir: Output directory for JSON file + sample_size: Size of dataset to download + - "10BT" (10B tokens, ~13GB) - Recommended for testing + - "100BT" (100B tokens, ~130GB) + - "350BT" (350B tokens, ~450GB) + - "sample-10BT", "sample-100BT", "sample-350BT" for samples + """ + out_dir.mkdir(parents=True, exist_ok=True) + + # FineWeb-Edu dataset name format + dataset_name = f"HuggingFaceFW/fineweb-edu" + config_name = f"sample-{sample_size}" if not sample_size.startswith("sample-") else sample_size + + print(f"[Info] Loading fineweb-edu dataset ({sample_size}) from Hugging Face...") + print(f"[Info] This may take a while depending on dataset size...") + + # Load dataset - you can specify num_proc for faster loading + dataset = load_dataset( + dataset_name, + name=config_name, + split="train", + trust_remote_code=True, + # streaming=True, # Uncomment for very large datasets + ) + + output_file = out_dir / f"fineweb_edu_{sample_size}_megatron.json" + print(f"[Info] Saving dataset to {output_file} ...") + + # Convert to JSON format that Megatron expects + dataset.to_json(str(output_file)) + + print(f"[Info] Dataset preparation completed: {output_file}") + print(f"[Info] Total samples: {len(dataset)}") + + return output_file + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="Download and prepare FineWeb-Edu dataset for Megatron") + parser.add_argument("--out-dir", type=str, default="./data/fineweb-edu", help="Path to output directory") + parser.add_argument( + "--sample-size", + type=str, + default="10BT", + choices=["10BT", "100BT", "350BT", "sample-10BT", "sample-100BT", "sample-350BT"], + help="Size of FineWeb-Edu dataset to download", + ) + args = parser.parse_args() + + prepare_fineweb_edu_dataset(Path(args.out_dir), args.sample_size) diff --git a/examples/megatron_bridge/configs/MI300X/mamba_130M_pretrain.yaml b/examples/megatron_bridge/configs/MI300X/mamba_130M_pretrain.yaml new file mode 100644 index 000000000..5ba988890 --- /dev/null +++ b/examples/megatron_bridge/configs/MI300X/mamba_130M_pretrain.yaml @@ -0,0 +1,54 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:mamba_130M_pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron_bridge + config: pretrain_trainer.yaml + + # Model to run + model: mamba_130M.yaml + + overrides: + stderr_sink_level: DEBUG + + # Recipe flavor: upstream Megatron-Bridge Mamba2 130M pretrain config + flavor: mamba2_130m_pretrain_config + + # --- Parameters accepted by _mamba2_common() --- + + # Training configuration + train_iters: 50 + global_batch_size: 32 + micro_batch_size: 4 + seq_length: ${PRIMUS_SEQ_LENGTH:2048} + + # Nested overrides (applied by _apply_nested_overrides) + log_interval: 1 + eval_interval: 500 + eval_iters: 0 + skip_save: true + + # Optimizer + lr: 3.0e-4 + min_lr: 3.0e-5 + lr_warmup_iters: 2 + lr_decay_iters: null + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + context_parallel_size: 1 + sequence_parallel: false + use_megatron_fsdp: false + enable_primus_turbo: false + + # Data + mock: true + # Real data path should point to a Megatron indexed dataset prefix: + # .bin + .idx + data_paths: ${PRIMUS_TOKENIZED_DATA_PATH:null} + train_data_path: null + valid_data_path: null + test_data_path: null diff --git a/examples/megatron_bridge/configs/MI300X/qwen3_32b_lora_posttrain.yaml b/examples/megatron_bridge/configs/MI300X/qwen3_32b_lora_posttrain.yaml index 330c9e382..b1493908c 100644 --- a/examples/megatron_bridge/configs/MI300X/qwen3_32b_lora_posttrain.yaml +++ b/examples/megatron_bridge/configs/MI300X/qwen3_32b_lora_posttrain.yaml @@ -31,7 +31,7 @@ modules: # Training configuration train_iters: 200 global_batch_size: 32 - micro_batch_size: 2 + micro_batch_size: 1 seq_length: 8192 eval_interval: 30 save_interval: 50 diff --git a/examples/megatron_bridge/configs/MI300X/qwen3_32b_sft_posttrain.yaml b/examples/megatron_bridge/configs/MI300X/qwen3_32b_sft_posttrain.yaml index 75c721ed2..f05718454 100644 --- a/examples/megatron_bridge/configs/MI300X/qwen3_32b_sft_posttrain.yaml +++ b/examples/megatron_bridge/configs/MI300X/qwen3_32b_sft_posttrain.yaml @@ -31,7 +31,7 @@ modules: # Training configuration train_iters: 200 global_batch_size: 8 - micro_batch_size: 2 + micro_batch_size: 1 seq_length: 8192 eval_interval: 30 save_interval: 50 diff --git a/examples/megatron_bridge/configs/MI355X/mamba_130M_pretrain.yaml b/examples/megatron_bridge/configs/MI355X/mamba_130M_pretrain.yaml new file mode 100644 index 000000000..5ba988890 --- /dev/null +++ b/examples/megatron_bridge/configs/MI355X/mamba_130M_pretrain.yaml @@ -0,0 +1,54 @@ +work_group: ${PRIMUS_TEAM:amd} +user_name: ${PRIMUS_USER:root} +exp_name: ${PRIMUS_EXP_NAME:mamba_130M_pretrain} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron_bridge + config: pretrain_trainer.yaml + + # Model to run + model: mamba_130M.yaml + + overrides: + stderr_sink_level: DEBUG + + # Recipe flavor: upstream Megatron-Bridge Mamba2 130M pretrain config + flavor: mamba2_130m_pretrain_config + + # --- Parameters accepted by _mamba2_common() --- + + # Training configuration + train_iters: 50 + global_batch_size: 32 + micro_batch_size: 4 + seq_length: ${PRIMUS_SEQ_LENGTH:2048} + + # Nested overrides (applied by _apply_nested_overrides) + log_interval: 1 + eval_interval: 500 + eval_iters: 0 + skip_save: true + + # Optimizer + lr: 3.0e-4 + min_lr: 3.0e-5 + lr_warmup_iters: 2 + lr_decay_iters: null + + tensor_model_parallel_size: 1 + pipeline_model_parallel_size: 1 + context_parallel_size: 1 + sequence_parallel: false + use_megatron_fsdp: false + enable_primus_turbo: false + + # Data + mock: true + # Real data path should point to a Megatron indexed dataset prefix: + # .bin + .idx + data_paths: ${PRIMUS_TOKENIZED_DATA_PATH:null} + train_data_path: null + valid_data_path: null + test_data_path: null diff --git a/examples/mlperf/gpt_oss_20b/.dockerignore b/examples/mlperf/gpt_oss_20b/.dockerignore new file mode 100644 index 000000000..e0def13df --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/.dockerignore @@ -0,0 +1,5 @@ +** +!Dockerfile.runtime-v26.3 +!Dockerfile.runtime-v26.5 +!prewarm_attention.py +!aiter_hd64_asm_override.py diff --git a/examples/mlperf/gpt_oss_20b/Dockerfile.runtime-v26.3 b/examples/mlperf/gpt_oss_20b/Dockerfile.runtime-v26.3 new file mode 100644 index 000000000..5400df304 --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/Dockerfile.runtime-v26.3 @@ -0,0 +1,88 @@ +# syntax=docker/dockerfile:1.7 + +ARG BASE_IMAGE=rocm/primus:v26.3@sha256:1a02b74a94d82131f3f119a94ccbed45bb5b4cd77a0616cb4e6a29e64a048482 +FROM ${BASE_IMAGE} + +ARG TRITON_REF=09500db9f0fe66fd176d1f080e2017b37e7e995d +ARG PRIMUS_TURBO_REF=e160182d6a7967592579be2640e85106bc8f421e +ARG TE_REF=5235bae2cc683a0ad4bf15221c746ab3c1e229e7 +ARG AITER_REF=c4b33df03faae1c4e470420d950f8a9589e9634d +ARG FWD_ATTN_ASM_REF=53d3dadc3f3b0ac35ae536f2d1d7864a3e07ba22 +ARG BWD_ATTN_ASM_REF=9b9fb6444f3fee388617f62432c3faea74079377 +ARG BWD_ATTN_SYMBOL=_ZN5aiter43fmha_bwd_hd64_bf16_causal_a16_rtz_recompileE +ARG BWD_ATTN_SLOT=bwd_hd64_bf16_causal_a16_rtz.co +ARG MAX_JOBS=96 + +ENV MLPERF_RUNTIME_SERIES=v26.3 \ + MLPERF_ENABLE_FWD_ATTN_ASM=1 \ + FMHA_HD64_ASM_CO=/opt/venv/lib/python3.12/site-packages/aiter_hd64_asm_fwd_d64_opt128.co \ + FMHA_HD64_ASM_LOG=0 + +WORKDIR /workspace/deps + +# The v26.3 base ships Triton 3.6, while this Turbo branch requires the 3.7 API. +RUN python3 -m pip install --upgrade --force-reinstall --no-deps flydsl==0.2.4 && \ + git clone https://github.com/triton-lang/triton.git && \ + cd triton && git checkout "${TRITON_REF}" && \ + python3 -m pip install ninja cmake && \ + MAX_JOBS="${MAX_JOBS}" python3 -m pip install \ + --no-build-isolation --no-deps -v . && \ + cd .. && rm -rf triton + +RUN git clone --recursive https://github.com/AMD-AGI/Primus-Turbo.git && \ + cd Primus-Turbo && git checkout "${PRIMUS_TURBO_REF}" && \ + git submodule update --init --recursive && \ + python3 -m pip install -r requirements.txt && \ + PRIMUS_TURBO_FRAMEWORK=PYTORCH \ + GPU_ARCHS=gfx950 \ + MAX_JOBS="${MAX_JOBS}" \ + python3 -m pip install --no-build-isolation --no-deps -v . && \ + cd .. && rm -rf Primus-Turbo + +RUN git clone https://github.com/mawad-amd/fwd-attn-asm.git && \ + cd fwd-attn-asm && git checkout "${FWD_ATTN_ASM_REF}" && \ + amdclang -x assembler -target amdgcn-amd-amdhsa -mcpu=gfx950 \ + -o /tmp/fwd_attn.co kernels/fwd_d64_opt128.s && \ + cd .. && \ + git clone https://github.com/mawad-amd/bwd-attn-asm.git && \ + cd bwd-attn-asm && git checkout "${BWD_ATTN_ASM_REF}" && \ + amdclang -x assembler -target amdgcn-amd-amdhsa -mcpu=gfx950 \ + -o /tmp/bwd_attn.co kernels/bwd_d64_v3_causal_opt_16x32.s && \ + cd .. && rm -rf fwd-attn-asm bwd-attn-asm + +RUN git clone --recursive https://github.com/ROCm/TransformerEngine.git && \ + cd TransformerEngine && git checkout "${TE_REF}" && \ + git submodule update --init --recursive && \ + AITER_DIR=3rdparty/QoLA/3rdparty/aiter && \ + git -C "${AITER_DIR}" fetch origin "${AITER_REF}" && \ + git -C "${AITER_DIR}" checkout "${AITER_REF}" && \ + git -C "${AITER_DIR}" submodule update --init --recursive && \ + sed -i "s|^aiter_commit = .*|aiter_commit = \"${AITER_REF}\"|" \ + transformer_engine/common/ck_fused_attn/qola_manifest.toml && \ + install -m 0644 /tmp/bwd_attn.co \ + "${AITER_DIR}/hsa/gfx950/fmha_v3_bwd/${BWD_ATTN_SLOT}" && \ + python3 -m pip uninstall -y \ + transformer_engine transformer_engine_rocm7 transformer_engine_rocm_torch && \ + NVTE_FUSED_ATTN_AOTRITON=0 \ + NVTE_CK_FUSED_ATTN_PATH="" \ + NVTE_BUILD_MAX_JOBS="${MAX_JOBS}" \ + NVTE_FRAMEWORK=pytorch \ + NVTE_ROCM_ARCH=gfx950 \ + NVTE_USE_HIPBLASLT=1 \ + PYTORCH_ROCM_ARCH=gfx950 \ + CU_NUM=304 \ + NVTE_SKIP_SUBMODULE_CHECKS_DURING_BUILD=1 \ + MAX_JOBS="${MAX_JOBS}" \ + python3 -m pip install --no-build-isolation --no-deps -v . && \ + cd .. && rm -rf TransformerEngine + +COPY aiter_hd64_asm_override.py \ + /opt/venv/lib/python3.12/site-packages/aiter_hd64_asm_override.py +COPY prewarm_attention.py /opt/mlperf-gpt-oss-20b/prewarm_attention.py +RUN printf 'import aiter_hd64_asm_override\n' \ + > /opt/venv/lib/python3.12/site-packages/aiter_hd64_asm_override.pth && \ + install -m 0644 /tmp/fwd_attn.co \ + /opt/venv/lib/python3.12/site-packages/aiter_hd64_asm_fwd_d64_opt128.co && \ + rm -f /tmp/fwd_attn.co /tmp/bwd_attn.co + +WORKDIR /workspace diff --git a/examples/mlperf/gpt_oss_20b/Dockerfile.runtime-v26.5 b/examples/mlperf/gpt_oss_20b/Dockerfile.runtime-v26.5 new file mode 100644 index 000000000..913dc14e0 --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/Dockerfile.runtime-v26.5 @@ -0,0 +1,89 @@ +# syntax=docker/dockerfile:1.7 + +ARG BASE_IMAGE=rocm/primus:v26.5@sha256:3040bf42974d791dd42de2e36b3c919a00869a5754cfc57a06b96d004c55eed1 +FROM ${BASE_IMAGE} + +ARG PRIMUS_TURBO_REF=e160182d6a7967592579be2640e85106bc8f421e +ARG TE_REF=a07e607f14a5330807ffdafeeb6224f2d7dffacc +ARG AITER_REF=d32b0cb62ecc32bb1a858e8437d58eb9b3856af6 +ARG FWD_ATTN_ASM_REF=53d3dadc3f3b0ac35ae536f2d1d7864a3e07ba22 +ARG BWD_ATTN_ASM_REF=9b9fb6444f3fee388617f62432c3faea74079377 +ARG BWD_ATTN_SOURCE_SYMBOL=_ZN5aiter43fmha_bwd_hd64_bf16_causal_a16_rtz_recompileE +ARG BWD_ATTN_SYMBOL=_ZN5aiter44fmha_bwd_hd64_bf16_causal_a16_rtne_recompileE +ARG BWD_ATTN_SLOT=bwd_hd64_bf16_causal_a16_rtne.co +ARG MAX_JOBS=96 + +ENV MLPERF_RUNTIME_SERIES=v26.5 \ + MLPERF_ENABLE_FWD_ATTN_ASM=1 \ + FMHA_HD64_ASM_CO=/opt/venv/lib/python3.12/site-packages/aiter_hd64_asm_fwd_d64_opt128.co \ + FMHA_HD64_ASM_LOG=0 + +WORKDIR /workspace/deps + +# The v26.5 base already contains the tested Triton 3.7 compiler. +RUN git clone --recursive https://github.com/AMD-AGI/Primus-Turbo.git && \ + cd Primus-Turbo && git checkout "${PRIMUS_TURBO_REF}" && \ + git submodule update --init --recursive && \ + python3 -m pip install -r requirements.txt && \ + PRIMUS_TURBO_FRAMEWORK=PYTORCH \ + GPU_ARCHS=gfx950 \ + MAX_JOBS="${MAX_JOBS}" \ + python3 -m pip install --no-build-isolation --no-deps -v . && \ + cd .. && rm -rf Primus-Turbo + +# Compile the hand-tuned forward and numerically validated backward payloads. +RUN git clone https://github.com/mawad-amd/fwd-attn-asm.git && \ + cd fwd-attn-asm && git checkout "${FWD_ATTN_ASM_REF}" && \ + amdclang -x assembler -target amdgcn-amd-amdhsa -mcpu=gfx950 \ + -o /tmp/fwd_attn.co kernels/fwd_d64_opt128.s && \ + cd .. && \ + git clone https://github.com/mawad-amd/bwd-attn-asm.git && \ + cd bwd-attn-asm && git checkout "${BWD_ATTN_ASM_REF}" && \ + sed "s/${BWD_ATTN_SOURCE_SYMBOL}/${BWD_ATTN_SYMBOL}/g" \ + kernels/bwd_d64_v3_causal_opt_16x32.s > /tmp/bwd_attn.s && \ + amdclang -x assembler -target amdgcn-amd-amdhsa -mcpu=gfx950 \ + -o /tmp/bwd_attn.co /tmp/bwd_attn.s && \ + cd .. && rm -rf fwd-attn-asm bwd-attn-asm /tmp/bwd_attn.s + +# The base image already carries the matching TE/AITER Python stack. Rebuild +# TE only because the validated backward ASM must be embedded in libmha_bwd. +RUN git clone --recursive https://github.com/ROCm/TransformerEngine.git && \ + cd TransformerEngine && git checkout "${TE_REF}" && \ + git submodule update --init --recursive && \ + sed -i "s|^aiter_commit = .*|aiter_commit = \"${AITER_REF}\"|" \ + transformer_engine/common/ck_fused_attn/qola_manifest.toml && \ + PYTHONPATH="$PWD/3rdparty/QoLA:${PYTHONPATH}" \ + python3 -m qola.cli checkout \ + --manifest transformer_engine/common/ck_fused_attn/qola_manifest.toml \ + --aiter-root /workspace/deps/te-aiter && \ + install -m 0644 /tmp/bwd_attn.co \ + "/workspace/deps/te-aiter/hsa/gfx950/fmha_v3_bwd/${BWD_ATTN_SLOT}" && \ + python3 -m pip uninstall -y \ + transformer_engine transformer_engine_rocm7 transformer_engine_rocm_torch && \ + NVTE_FUSED_ATTN_AOTRITON=0 \ + NVTE_FUSED_ATTN_CK=1 \ + NVTE_CK_JIT=1 \ + NVTE_CK_FUSED_ATTN_PATH="" \ + NVTE_AITER_SOURCE_DIR=/workspace/deps/te-aiter \ + NVTE_BUILD_MAX_JOBS="${MAX_JOBS}" \ + NVTE_FRAMEWORK=pytorch \ + NVTE_ROCM_ARCH=gfx950 \ + NVTE_USE_HIPBLASLT=1 \ + PYTORCH_ROCM_ARCH=gfx950 \ + CU_NUM=304 \ + NVTE_SKIP_SUBMODULE_CHECKS_DURING_BUILD=1 \ + CMAKE_BUILD_PARALLEL_LEVEL="${MAX_JOBS}" \ + MAX_JOBS="${MAX_JOBS}" \ + python3 -m pip install --no-build-isolation --no-deps -v . && \ + cd .. && rm -rf TransformerEngine te-aiter + +COPY aiter_hd64_asm_override.py \ + /opt/venv/lib/python3.12/site-packages/aiter_hd64_asm_override.py +COPY prewarm_attention.py /opt/mlperf-gpt-oss-20b/prewarm_attention.py +RUN printf 'import aiter_hd64_asm_override\n' \ + > /opt/venv/lib/python3.12/site-packages/aiter_hd64_asm_override.pth && \ + install -m 0644 /tmp/fwd_attn.co \ + /opt/venv/lib/python3.12/site-packages/aiter_hd64_asm_fwd_d64_opt128.co && \ + rm -f /tmp/fwd_attn.co /tmp/bwd_attn.co + +WORKDIR /workspace diff --git a/examples/mlperf/gpt_oss_20b/README.md b/examples/mlperf/gpt_oss_20b/README.md new file mode 100644 index 000000000..0d979ce69 --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/README.md @@ -0,0 +1,77 @@ +# GPT-OSS-20B MLPerf pretraining + +GPT-OSS 20B on one MI355X node with 8 GPUs and global batch size 32. + +## Build + +The Dockerfile builds the complete runtime, including the GPT-OSS +Primus-Turbo test branch, TransformerEngine, and the attention ASM kernels. +The v26.5 image reuses the base image's Triton 3.7 compiler; v26.3 upgrades its +older base Triton for compatibility with the same Turbo branch. + +```bash +cd examples/mlperf/gpt_oss_20b +docker build --network host \ + -f Dockerfile.runtime-v26.5 \ + -t primus:gpt-oss-20b-mlperf-v26.5 . +``` + +Use `Dockerfile.runtime-v26.3` and a v26.3 tag for the compatibility stack. +Push a shared tag with `docker push `. + +## Data + +```bash +mkdir -p /data/gpt_oss_20b +cd /data/gpt_oss_20b +bash <(curl -s https://raw.githubusercontent.com/mlcommons/r2-downloader/refs/heads/main/mlc-r2-downloader.sh) \ + -d data \ + https://training.mlcommons-storage.org/metadata/llama-3-1-8b-preprocessed-c4-dataset.uri +``` + +Training uses the `c4-train.en_6_text_document` prefix and validation uses +`c4-validation-91205-samples.en_text_document`. + +## Run + +```bash +docker run -it --rm \ + --privileged --network host --ipc host --shm-size 128g \ + --cap-add SYS_PTRACE --security-opt seccomp=unconfined \ + --device /dev/dri --device /dev/kfd --device /dev/infiniband \ + -v /path/to/Primus:/workspace/Primus \ + -v /path/to/data:/data \ + -v /path/to/model:/model \ + -v /path/to/results:/results \ + primus:gpt-oss-20b-mlperf-v26.5 bash +``` + +Inside the container: + +```bash +cd /workspace/Primus/examples/mlperf/gpt_oss_20b +source config_MI355X_1x8x1_tp1pp1ep1_gbs32.sh +./run_and_time.sh +``` + +That is the complete long-run entry. The config is the single source of +submission defaults: MLPerf trainer, 1.2M iteration ceiling, 128-step warmup, +FP8 Triton grouped GEMM, fused wgrad accumulation, and disabled profiling. +Short diagnostics and backend ablations should override environment variables +outside the checked-in submission config. + +## v26.5 attention prewarm + +The v26.5 TE stack lazily compiles two attention variants. Starting eight +torchrun ranks against an empty cache can race while writing the same blobs. +`run_and_time.sh` therefore runs `prewarm_attention.py` once before timing; the +helper only populates the sliding-window and full-attention cache entries. +The v26.3 TE/AITER stack does not exhibit this cache race, so the prewarm is +skipped automatically for v26.3. + +## Key files + +- `Dockerfile.runtime-v26.3`, `Dockerfile.runtime-v26.5`: complete runtime builds +- `config_MI355X_1x8x1_tp1pp1ep1_gbs32.sh`: submission defaults +- `run_and_time.sh`: benchmark entry +- `prewarm_attention.py`: v26.5-only attention cache prewarm diff --git a/examples/mlperf/gpt_oss_20b/aiter_hd64_asm_override.py b/examples/mlperf/gpt_oss_20b/aiter_hd64_asm_override.py new file mode 100644 index 000000000..2ab198eb3 --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/aiter_hd64_asm_override.py @@ -0,0 +1,802 @@ +"""Route eligible TransformerEngine FMHA forward calls to the pinned gfx950 +HD64 BF16 assembly kernel. + +The module is imported at Python startup through ``aiter_hd64_asm_override.pth`` +and remains inactive unless ``MLPERF_ENABLE_FWD_ATTN_ASM=1``. +""" + +from __future__ import annotations + +import ctypes +import inspect +import logging +import math +import os +import struct +import sys +from typing import Tuple + +logger = logging.getLogger("fwd_attn_asm_override") +# Per-dispatch logging is opt-in only: it fires once per attention call, so an +# inherited INFO root level (verbose runs) must not switch it on. +_DISPATCH_LOG = os.environ.get("FMHA_HD64_ASM_LOG", "0") == "1" +if _DISPATCH_LOG: + logging.basicConfig(level=logging.INFO) + logger.setLevel(logging.INFO) + + +_ENABLED = os.environ.get("MLPERF_ENABLE_FWD_ATTN_ASM", "0") == "1" +_AITER_ROPE_ENABLED = os.environ.get("NVTE_USE_AITER_ROPE", "0") == "1" +_DEFAULT_CO_PATH = os.path.join( + os.path.dirname(__file__), + "aiter_hd64_asm_fwd_d64_opt128.co", +) +_CO_PATH = os.environ.get("FMHA_HD64_ASM_CO", _DEFAULT_CO_PATH) +_KERNEL_NAME = b"fmha_fwd_d64_bf16_causal" + +# Tile shape baked into the kernel (BlockFmhaPipelineQRKSVSAsync<128,64,...>). +_BLOCK_M = 128 +_LDS_BYTES = 13056 +_BLOCK_THREADS = 256 + +_HIP_LIB = None +_CO_DATA: bytes | None = None +# HIP modules are bound to a device's primary context, so each rank/device +# needs its own handle. +_KFUNC_BY_DEV: dict = {} +_KMODULE_BY_DEV: dict = {} +_DISPATCH_COUNT = 0 + +# CK aux tensors are needed by TE's backward wrapper. Capture a compatible +# template on the first eligible call and then replace only its LSE tensor. +_AUX_CTX_TEMPLATES: dict = {} + + +def get_dispatch_count() -> int: + """Return successful hand-tuned kernel launches in this process.""" + return _DISPATCH_COUNT + + +def _ensure_hip_lib(): + global _HIP_LIB + if _HIP_LIB is not None: + return _HIP_LIB + # ROCm Python wheels ship runtime and development copies of libamdhip64. + # PyTorch is linked against the versioned runtime SONAME; opening the + # unversioned development symlink can create a second HIP runtime with a + # separate module/context registry, causing hipModuleGetFunction to return + # hipErrorNotFound even though the code object contains the symbol. + lib = ctypes.CDLL("libamdhip64.so.7") + lib.hipModuleLoadData.restype = ctypes.c_int + lib.hipModuleLoadData.argtypes = [ctypes.c_void_p, ctypes.c_void_p] + lib.hipModuleGetFunction.restype = ctypes.c_int + lib.hipModuleGetFunction.argtypes = [ + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_char_p, + ] + lib.hipModuleLaunchKernel.restype = ctypes.c_int + lib.hipModuleLaunchKernel.argtypes = [ + ctypes.c_void_p, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_uint, + ctypes.c_void_p, + ctypes.c_void_p, + ctypes.c_void_p, + ] + _HIP_LIB = lib + return _HIP_LIB + + +def _try_load_kernel() -> bool: + """Stage the code object; bind it to a HIP context on first launch.""" + global _CO_DATA + try: + _ensure_hip_lib() + if _CO_DATA is None: + with open(_CO_PATH, "rb") as file: + _CO_DATA = file.read() + return True + except Exception as error: # noqa: BLE001 + logger.warning( + "could not stage hand-tuned hd64 kernel path=%s exists=%s: %r", + _CO_PATH, + os.path.exists(_CO_PATH), + error, + ) + return False + + +def _get_kfunc_for_device(device) -> ctypes.c_void_p: + import torch + + dev_id = ( + device.index if hasattr(device, "index") and device.index is not None else torch.cuda.current_device() + ) + cached = _KFUNC_BY_DEV.get(dev_id) + if cached is not None: + return cached + + hip = _ensure_hip_lib() + if _CO_DATA is None: + with open(_CO_PATH, "rb") as file: + globals()["_CO_DATA"] = file.read() + + with torch.cuda.device(dev_id): + module = ctypes.c_void_p() + rc = hip.hipModuleLoadData( + ctypes.byref(module), + ctypes.create_string_buffer(_CO_DATA), + ) + if rc != 0: + raise RuntimeError(f"hipModuleLoadData failed for {_CO_PATH} on device {dev_id} " f"(rc={rc})") + func = ctypes.c_void_p() + rc = hip.hipModuleGetFunction( + ctypes.byref(func), + module, + _KERNEL_NAME, + ) + if rc != 0: + raise RuntimeError(f"hipModuleGetFunction failed on device {dev_id} (rc={rc})") + + _KMODULE_BY_DEV[dev_id] = module + _KFUNC_BY_DEV[dev_id] = func + logger.info( + "loaded hand-tuned hd64 kernel from %s on device %d", + _CO_PATH, + dev_id, + ) + return func + + +def _is_gfx950(device) -> bool: + import torch + + try: + return torch.cuda.get_device_properties(device).gcnArchName.startswith("gfx950") + except Exception: + return False + + +def _ck_window_args(window_size, attn_mask_type: str) -> Tuple[int, int]: + if window_size is None: + window_left, window_right = -1, -1 + else: + window_left, window_right = int(window_size[0]), int(window_size[1]) + if "causal" in (attn_mask_type or ""): + window_right = 0 + return window_left, window_right + + +def _eligible( + *, + max_seqlen_q, + max_seqlen_kv, + q, + k, + v, + attn_scale, + attn_bias_type, + attn_mask_type, + softmax_type, + window_size, + bottom_right_diagonal, + qkv_layout, + dropout, + attn_bias, + softmax_offset, + s_quantizer, + o_quantizer, + fp8, +) -> bool: + import torch + + if not _ENABLED or fp8: + return False + if q.dtype != torch.bfloat16 or k.dtype != torch.bfloat16 or v.dtype != torch.bfloat16: + return False + # TE 2.15 passes quantizer objects through its BF16 fused-attention API even + # when fp8=False. They are unused by this BF16 output path and must not make + # an otherwise eligible call fall back to CK. + if attn_bias is not None or softmax_offset is not None: + return False + if attn_bias_type != "no_bias" or softmax_type != "vanilla": + return False + if bottom_right_diagonal not in (None, False): + return False + if qkv_layout not in ("bshd_bshd_bshd", "sbhd_sbhd_sbhd"): + return False + if q.dim() != 4 or k.dim() != 4 or v.dim() != 4: + return False + if q.shape[-1] != 64 or v.shape[-1] != 64: + return False + if "causal" not in (attn_mask_type or ""): + return False + if dropout != 0.0: + return False + if not _is_gfx950(q.device): + return False + + if qkv_layout.startswith("bshd"): + batch, sequence_length, query_heads, _ = q.shape + key_value_sequence_length = k.shape[1] + key_value_heads = k.shape[2] + else: + sequence_length, batch, query_heads, _ = q.shape + key_value_sequence_length = k.shape[0] + key_value_heads = k.shape[2] + + if sequence_length != key_value_sequence_length: + return False + if max_seqlen_q != sequence_length or max_seqlen_kv != sequence_length: + return False + if q.shape[:2] != k.shape[:2] or k.shape[:2] != v.shape[:2]: + return False + if query_heads % key_value_heads != 0: + return False + if sequence_length % _BLOCK_M != 0: + return False + # TE uses 0.0 as a sentinel for the default 1/sqrt(head_dim) scale. + if attn_scale not in (None, 0.0) and not math.isclose( + float(attn_scale), + 1.0 / math.sqrt(q.shape[-1]), + rel_tol=1e-6, + abs_tol=0.0, + ): + return False + if batch <= 0: + return False + return True + + +def _launch( + q, + k, + v, + qkv_layout: str, + *, + attn_scale: float, + attn_mask_type: str, + window_size, + lse_out, +): + import torch + + if qkv_layout.startswith("bshd"): + batch, sequence_length, query_heads, head_dim = q.shape + key_value_heads = k.shape[2] + stride_q_s, stride_q_h, stride_q_b = ( + q.stride(1), + q.stride(2), + q.stride(0), + ) + stride_k_s, stride_k_h, stride_k_b = ( + k.stride(1), + k.stride(2), + k.stride(0), + ) + stride_v_s, stride_v_h, stride_v_b = ( + v.stride(1), + v.stride(2), + v.stride(0), + ) + else: + sequence_length, batch, query_heads, head_dim = q.shape + key_value_heads = k.shape[2] + stride_q_s, stride_q_h, stride_q_b = ( + q.stride(0), + q.stride(2), + q.stride(1), + ) + stride_k_s, stride_k_h, stride_k_b = ( + k.stride(0), + k.stride(2), + k.stride(1), + ) + stride_v_s, stride_v_h, stride_v_b = ( + v.stride(0), + v.stride(2), + v.stride(1), + ) + + output = torch.empty_like(q) + if qkv_layout.startswith("bshd"): + stride_o_s, stride_o_h, stride_o_b = ( + output.stride(1), + output.stride(2), + output.stride(0), + ) + else: + stride_o_s, stride_o_h, stride_o_b = ( + output.stride(0), + output.stride(2), + output.stride(1), + ) + + # The assembly kernel expects the CK log2(e)-scaled convention. + del attn_scale + scale_s = (1.0 / math.sqrt(head_dim)) * math.log2(math.e) + window_left, window_right = _ck_window_args( + window_size, + attn_mask_type, + ) + + kargs = struct.pack( + " 4 else None, + args[4].stride() if len(args) > 4 else None, + args[5].stride() if len(args) > 5 else None, + args[6].stride() if len(args) > 6 else None, + args[7].stride() if len(args) > 7 else None, + args[8].stride() if len(args) > 8 else None, + ) + try: + return original_fused_attn_bwd(*args, **kwargs) + except RuntimeError: + aux = args[11] if len(args) > 11 else kwargs.get("aux_ctx_tensors") + logger.error( + "fused-attn-bwd rejected config: max_q=%s max_kv=%s " + "q=%s backend=%s aux=%s layout=%s mask=%s window=%s " + "bottom_right=%s deterministic=%s", + args[0] if len(args) > 0 else None, + args[1] if len(args) > 1 else None, + getattr(args[4], "shape", None) if len(args) > 4 else None, + args[12] if len(args) > 12 else None, + [(tuple(t.shape), str(t.dtype)) for t in (aux or [])], + args[21] if len(args) > 21 else None, + args[23] if len(args) > 23 else None, + args[25] if len(args) > 25 else None, + args[26] if len(args) > 26 else None, + args[27] if len(args) > 27 else None, + ) + raise + + diagnostic_fused_attn_bwd._fwd_attn_asm_bwd_diagnostic = True + fused_attn_module.fused_attn_bwd = diagnostic_fused_attn_bwd + + try: + from transformer_engine.pytorch.attention.dot_product_attention import backends + + backends.fused_attn_fwd = patched_fused_attn_fwd + backends.fused_attn_bwd = diagnostic_fused_attn_bwd + except Exception: + pass + + logger.info("fused_attn_fwd patched " "(D=64 BF16 [SWA-]causal -> hand-tuned hd64 kernel)") + return True + + +def _install_aiter_rope_override(rope_module): + """Restore the AITER RoPE route removed by newer ROCm TE revisions.""" + if not _AITER_ROPE_ENABLED: + return False + + fused_rope = rope_module.FusedRoPEFunc + if getattr(fused_rope, "_mlperf_aiter_rope_patched", False): + return True + # Older TE revisions already provide the same dispatch natively. + if hasattr(fused_rope, "_can_use_aiter"): + return True + + from aiter.ops.rope import rope_bwd as aiter_rope_bwd + from aiter.ops.rope import rope_fwd as aiter_rope_fwd + + original_forward = fused_rope.forward + original_backward = fused_rope.backward + + def aiter_aware_forward( + ctx, + tensor, + freqs, + start_positions=None, + tensor_format="sbhd", + interleaved=False, + cu_seqlens=None, + cp_size=1, + cp_rank=0, + ): + use_aiter = ( + tensor_format == "sbhd" + and not interleaved + and cu_seqlens is None + and cp_size == 1 + and start_positions is None + ) + ctx._mlperf_use_aiter_rope = use_aiter + if not use_aiter: + return original_forward( + ctx, + tensor, + freqs, + start_positions, + tensor_format, + interleaved, + cu_seqlens, + cp_size, + cp_rank, + ) + + if freqs.dtype != torch.float32: + freqs = freqs.float() + output = aiter_rope_fwd(tensor, freqs, 0, False, False) + ctx.save_for_backward(freqs, cu_seqlens, start_positions) + return output + + def aiter_aware_backward(ctx, grad_output): + if not getattr(ctx, "_mlperf_use_aiter_rope", False): + return original_backward(ctx, grad_output) + freqs, _, _ = ctx.saved_tensors + grad_input = aiter_rope_bwd(grad_output, freqs, 0, False, False) + return grad_input, None, None, None, None, None, None, None, None + + import torch + + fused_rope.forward = staticmethod(aiter_aware_forward) + fused_rope.backward = staticmethod(aiter_aware_backward) + fused_rope._mlperf_aiter_rope_patched = True + logger.info("restored AITER fused RoPE dispatch for this TE revision") + return True + + +class _DeferredInstaller: + _TARGETS = { + "transformer_engine.pytorch.attention.rope", + "transformer_engine.pytorch.cpp_extensions.fused_attn", + "transformer_engine.pytorch.cpp_extensions", + } + + def find_spec(self, fullname, path, target=None): + try: + if fullname not in self._TARGETS: + return None + for finder in sys.meta_path: + if finder is self: + continue + try: + spec = finder.find_spec(fullname, path, target) + except (AttributeError, ImportError): + spec = None + if spec is None: + continue + original_loader = spec.loader + + class _WrappedLoader: + def create_module(self, module_spec): + if hasattr(original_loader, "create_module"): + return original_loader.create_module(module_spec) + return None + + def exec_module(self, module): + original_loader.exec_module(module) + if fullname == "transformer_engine.pytorch.cpp_extensions.fused_attn": + try: + _install_fused_attn_override() + except Exception as error: # noqa: BLE001 + logger.warning( + "deferred install failed: %r", + error, + ) + elif fullname == "transformer_engine.pytorch.attention.rope": + try: + _install_aiter_rope_override(module) + except Exception as error: # noqa: BLE001 + logger.warning( + "deferred AITER RoPE install failed: %r", + error, + ) + + spec.loader = _WrappedLoader() + return spec + return None + except Exception as error: # noqa: BLE001 + logger.warning( + "fwd-attn-asm find_spec error for %s: %r", + fullname, + error, + ) + return None + + +def _register_deferred_install(): + if any(isinstance(finder, _DeferredInstaller) for finder in sys.meta_path): + return + sys.meta_path.insert(0, _DeferredInstaller()) + logger.info("deferred installer registered; will patch on TE load") + + +if _ENABLED or _AITER_ROPE_ENABLED: + try: + _register_deferred_install() + except Exception as error: # noqa: BLE001 + logger.warning( + "fwd-attn-asm deferred install failed at startup: %r", + error, + ) diff --git a/examples/mlperf/gpt_oss_20b/config_MI355X_1x8x1_tp1pp1ep1_gbs32.sh b/examples/mlperf/gpt_oss_20b/config_MI355X_1x8x1_tp1pp1ep1_gbs32.sh new file mode 100644 index 000000000..2e7e1a556 --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/config_MI355X_1x8x1_tp1pp1ep1_gbs32.sh @@ -0,0 +1,248 @@ +#!/bin/bash +# ============================================================================= +# MLPerf GPT-OSS-20B Configuration for MI355X (1 node, 8 GPUs) +# ============================================================================= + +# ----------------------------------------------------------------------------- +# System Configuration +# ----------------------------------------------------------------------------- +export DGXSYSTEM=MI355X_1x8x1 +export GPUS_PER_NODE=8 +export NNODES=1 +export NODE_RANK=0 +export MASTER_ADDR=localhost +export MASTER_PORT=29501 + +# ----------------------------------------------------------------------------- +# Paths +# ----------------------------------------------------------------------------- +export PRIMUS_PATH=/workspace/Primus +export PYTHONPATH="${PRIMUS_PATH}:${PRIMUS_PATH}/third_party/Megatron-LM:${PYTHONPATH}" +export EXP=${PRIMUS_PATH}/examples/mlperf/gpt_oss_20b/configs/MI355/gpt_oss_20B-FP8-mlperf-pretrain.yaml +export DATA_PATH=/data + +# ----------------------------------------------------------------------------- +# Training Hyperparameters +# ----------------------------------------------------------------------------- +export PRIMUS_MICRO_BATCH_SIZE=4 +export PRIMUS_GLOBAL_BATCH_SIZE=32 +export EVAL_ITERS=$((1024 / PRIMUS_GLOBAL_BATCH_SIZE)) # MLPerf closed: eval_iters * GBS = 1024 eval samples +export PRIMUS_LR=8.0e-4 +export PRIMUS_MIN_LR=8.0e-5 # Set to 10% of max LR +export PRIMUS_TRAIN_ITERS=1200000 +export PRIMUS_LR_WARMUP_ITERS=128 +export PRIMUS_LR_DECAY_ITERS=$((PRIMUS_TRAIN_ITERS-PRIMUS_LR_WARMUP_ITERS)) # 1200000 - 128 = 1199872 +# export SEED=30279 + +# Evaluation frequency (sample-based, adjusts automatically with GBS) +export EVAL_SAMPLES_INTERVAL=12288 # Evaluate every 12,288 samples +export PRIMUS_EVAL_INTERVAL=$((EVAL_SAMPLES_INTERVAL / PRIMUS_GLOBAL_BATCH_SIZE)) # Auto-computed + +# ----------------------------------------------------------------------------- +# Parallelism +# ----------------------------------------------------------------------------- +export PRIMUS_TP=1 +export PRIMUS_PP=1 +export PRIMUS_EP=1 + +# ----------------------------------------------------------------------------- +# Primus Configuration +# ----------------------------------------------------------------------------- +export PRIMUS_TURBO_GROUPED_GEMM_BACKEND="${PRIMUS_TURBO_GROUPED_GEMM_BACKEND:-triton}" +export PRIMUS_TURBO_GEMM_BACKEND=triton +export PRIMUS_TURBO_FUSED_WGRAD_ACCUM="${PRIMUS_TURBO_FUSED_WGRAD_ACCUM:-1}" +export PRIMUS_NUM_WORKERS="${PRIMUS_NUM_WORKERS:-2}" +export PRIMUS_GRAD_REDUCE_IN_BF16=true +export USE_TURBO_RMS_NORM=true + +# ----------------------------------------------------------------------------- +# ROCm / System Runtime +# ----------------------------------------------------------------------------- +export GPU_MAX_HW_QUEUES=2 +export HIP_FORCE_DEV_KERNARG=1 +export HSA_FORCE_FINE_GRAIN_PCIE=1 +export HSA_KERNARG_POOL_SIZE=12582912 +export TORCH_NCCL_HIGH_PRIORITY=1 +export ENABLE_NUMA_BINDING=1 +export PYTORCH_ALLOC_CONF=expandable_segments:False +export HSA_NO_SCRATCH_RECLAIM=1 +export HSA_ENABLE_SDMA=1 +export HSA_ENABLE_INTERRUPT=0 +export CUDA_DEVICE_MAX_CONNECTIONS=1 +export OMP_NUM_THREADS=1 +export PYTHONWARNINGS=ignore +export TOKENIZERS_PARALLELISM=false + +# ----------------------------------------------------------------------------- +# RCCL / NCCL Tuning +# ----------------------------------------------------------------------------- +export NCCL_MIN_P2P_NCHANNELS=32 +export NCCL_MIN_CTAS=32 +export NCCL_NCHANNELS_PER_NET_PEER=32 +export NCCL_NVLS_ENABLE=0 +export NCCL_CHECKS_DISABLE=1 + +# ----------------------------------------------------------------------------- +# hipBLASLt +# ----------------------------------------------------------------------------- +export USE_HIPBLASLT=1 +export TORCH_BLAS_PREFER_HIPBLASLT=1 + +# Solution indices are runtime-specific. For normal v26.3 runs, replay the +# Stage 0 cache validated with this runtime instead of the legacy override table. +# Setting TE_HIPBLASLT_ALGO_SAVE selects online tuning and suppresses the default +# replay cache. HIPBLASLT_TUNING_OVERRIDE_FILE remains available as an explicit +# diagnostic override. +if [ -n "${TE_HIPBLASLT_ALGO_SAVE:-}" ]; then + export TE_HIPBLASLT_ALGO_SAVE + unset TE_HIPBLASLT_ALGO_LOAD + unset HIPBLASLT_TUNING_OVERRIDE_FILE +elif [ -n "${TE_HIPBLASLT_ALGO_LOAD:-}" ]; then + if [ -f "${TE_HIPBLASLT_ALGO_LOAD}" ]; then + export TE_HIPBLASLT_ALGO_LOAD + unset HIPBLASLT_TUNING_OVERRIDE_FILE + else + unset TE_HIPBLASLT_ALGO_LOAD + fi +elif [ -n "${HIPBLASLT_TUNING_OVERRIDE_FILE:-}" ]; then + if [ -f "${HIPBLASLT_TUNING_OVERRIDE_FILE}" ]; then + export HIPBLASLT_TUNING_OVERRIDE_FILE + else + unset HIPBLASLT_TUNING_OVERRIDE_FILE + fi +else + if [ "${MLPERF_RUNTIME_SERIES:-v26.3}" = "v26.3" ]; then + TE_HIPBLASLT_ALGO_LOAD="${PRIMUS_PATH}/examples/mlperf/gpt_oss_20b/te_hipblaslt_algo-v26.3.csv" + if [ -f "${TE_HIPBLASLT_ALGO_LOAD}" ]; then + export TE_HIPBLASLT_ALGO_LOAD + else + unset TE_HIPBLASLT_ALGO_LOAD + fi + else + HIPBLASLT_TUNING_OVERRIDE_FILE="${PRIMUS_PATH}/examples/mlperf/gpt_oss_20b/tune_gemm_results-${MLPERF_RUNTIME_SERIES}.txt" + if [ -f "${HIPBLASLT_TUNING_OVERRIDE_FILE}" ]; then + export HIPBLASLT_TUNING_OVERRIDE_FILE + else + unset HIPBLASLT_TUNING_OVERRIDE_FILE + fi + fi +fi + +# ----------------------------------------------------------------------------- +# NVTE — FP8 & Cast Transpose +# ----------------------------------------------------------------------------- +export NVTE_ROCM_ENABLE_MXFP8=0 +export NVTE_USE_CAST_TRANSPOSE_TRITON=0 +export NVTE_USE_OPTIMIZED_HIPIFIED_CAST_TRANSPOSE=1 + +# ----------------------------------------------------------------------------- +# NVTE — FMHA / CK Backend +# ----------------------------------------------------------------------------- +export NVTE_FLASH_ATTN=0 # Disable FlashAttention so FusedAttention (CK/ASM) is used +export NVTE_CK_USES_FWD_V3=1 # Globally on; aiter selects v3 vs CK-tile internally +export NVTE_CK_USES_BWD_V3=1 # Globally on; aiter selects v3 vs CK-tile internally +export NVTE_USE_AITER_ROPE=1 # Route RoPE through aiter's fused kernel instead of TE's own CK kernel +export NVTE_FMHA_USE_BSHD=0 # Use the native SBHD path provided by the v26.5 AITER stack +export NVTE_CK_HOW_V3_BF16_CVT=2 # 0=RTNE, 1=RTNA, 2=RTZ (gfx942 selector; gfx950 is fixed) + +# Route eligible D=64 BF16 causal forward calls to the hand-tuned gfx950 +# kernel. FMHA_HD64_ASM_LOG=1 prints one line per successful launch. +export MLPERF_ENABLE_FWD_ATTN_ASM="${MLPERF_ENABLE_FWD_ATTN_ASM:-1}" +export FMHA_HD64_ASM_LOG="${FMHA_HD64_ASM_LOG:-0}" + +# The RTZ custom backward code object is registered under the RTNE-named slot +# that v26.5 AITER actually selects on gfx950. Unlike the bundled v26.5 a16 +# kernel that overflowed at sequence length 8192, this injected kernel passed a +# 200-step FP8 C4 convergence run. Set the flag to 0 for the a32 PSSK fallback. +export MLPERF_ENABLE_BWD_ATTN_ASM="${MLPERF_ENABLE_BWD_ATTN_ASM:-1}" +if [ "${MLPERF_ENABLE_BWD_ATTN_ASM}" = "1" ]; then + export NVTE_CK_IS_V3_ATOMIC_FP32=0 +elif [ "${MLPERF_ENABLE_BWD_ATTN_ASM}" = "0" ]; then + export NVTE_CK_IS_V3_ATOMIC_FP32=1 +else + echo "MLPERF_ENABLE_BWD_ATTN_ASM must be 0 or 1" >&2 + if [ "${BASH_SOURCE[0]}" != "$0" ]; then + return 2 + fi + exit 2 +fi + +# ----------------------------------------------------------------------------- +# NVTE — Debug +# ----------------------------------------------------------------------------- +export NVTE_DEBUG=0 +export NVTE_DEBUG_LEVEL=0 +export NVTE_LOG_FUSED_ATTN_CONFIG=0 +export NVTE_LOG_CK_CONFIG=0 +export CK_FUSED_ATTN_LOG_CONFIG=0 +# export NVTE_FMHA_DEBUG=1 # keep commented; debug-only knob + +# ----------------------------------------------------------------------------- +# MLPerf Logging +# ----------------------------------------------------------------------------- +export LOG_INTERVAL=999999 +export MLLOG_TRAIN_LOSS_LOG_FREQ=0 +export MLLOG_TARGET_EVAL_LOSS=3.34 +export MLLOG_OUTPUT_FILE=/results/mlperf_logging.out +export MLLOG_SAVE_TO_FILE=0 +export MLLOG_SUBMISSION_BENCHMARK=gpt_oss_20b +export MLLOG_SUBMISSION_DIVISION=closed +export MLLOG_SUBMISSION_ORG=AMD +export MLLOG_SUBMISSION_PLATFORM=MI355X + +export MLLOG_TENSOR_PARALLELISM=1 +export MLLOG_PIPELINE_PARALLELISM=1 +export MLLOG_CONTEXT_PARALLELISM=1 +export MLLOG_EXPERT_PARALLELISM=1 +export MLLOG_MICRO_BATCH_SIZE=4 +MLLOG_CONFIG_FILENAME=$(basename "${BASH_SOURCE[0]}") +export MLLOG_CONFIG_FILENAME +export MLLOG_LOWEST_NUMERICAL_PRECISION_LINEAR='fp8' + +# ----------------------------------------------------------------------------- +# Synthetic Warmup (kernel pre-compilation) +# ----------------------------------------------------------------------------- +export SYNTH_WARMUP_STEPS=3 + +# ----------------------------------------------------------------------------- +# MoE Token Dispatcher +# ----------------------------------------------------------------------------- +# Skip sort_chunks_by_idxs when the per-local-expert index is an identity +# permutation (fires at EP=1/TP=1). Set to 0 to run the original path; useful +# for A/B measurements. Implemented by skip_identity_sort_patches.py. +export MOE_SKIP_IDENTITY_SORT=1 + +# ----------------------------------------------------------------------------- +# DDP Parameter All-Gather (SDMA) +# ----------------------------------------------------------------------------- +# v26.5 turns five SDMA workspace barriers into ~5 ms waits each. A same-node +# A/B with the latest Turbo main measured 1004 ms/step with SDMA and 988 +# ms/step with RCCL, matching the v26.3 control at 989 ms/step. Keep SDMA +# opt-in on v26.5 until its barrier regression is fixed; retain the validated +# v26.3 default. +if [ "${MLPERF_RUNTIME_SERIES:-v26.5}" = "v26.3" ]; then + DEFAULT_ENABLE_SDMA_ALLGATHER=1 +else + DEFAULT_ENABLE_SDMA_ALLGATHER=0 +fi +export ENABLE_SDMA_ALLGATHER="${ENABLE_SDMA_ALLGATHER:-${DEFAULT_ENABLE_SDMA_ALLGATHER}}" +# Optional: cap the per-call peer-copy stream count. Default is +# min(world_size-1, 8); lower values reduce SDMA / memory-system pressure. +# export MEGATRON_SDMA_PEER_COPY_STREAMS=8 +# When SDMA is explicitly enabled, keep the source-patch behavior: the first +# two parameter bucket groups use RCCL and later groups use SDMA. +export MEGATRON_SDMA_RCCL_FALLBACK_BUCKETS=${MEGATRON_SDMA_RCCL_FALLBACK_BUCKETS:-2} + +# ----------------------------------------------------------------------------- +# Run-log verbosity +# ----------------------------------------------------------------------------- +# MLPerf run-log verbosity. Default 0 keeps only :::MLLOG + ``run_and_time.sh`` +# banners on stdout. Set to 1 to restore the full framework output (Primus +# loguru banners / Megatron / TE / aiter / Gloo / torchrun / hipify / ...) +# when debugging. See src/_log_suppression.py for the full strategy. +export MLPERF_VERBOSE_LOGS=${MLPERF_VERBOSE_LOGS:-0} + +# fused rms and swiglu no cat +export PRIMUS_FUSED_RESIDUAL_NORM=1 +export PRIMUS_MOE_SWIGLU_NOCAT=1 +export MLLOG_BLOCK_TPUT_LOG=0 diff --git a/examples/mlperf/gpt_oss_20b/configs/MI355/gpt_oss_20B-FP8-mlperf-pretrain.yaml b/examples/mlperf/gpt_oss_20b/configs/MI355/gpt_oss_20B-FP8-mlperf-pretrain.yaml new file mode 100644 index 000000000..00efb9ebb --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/configs/MI355/gpt_oss_20B-FP8-mlperf-pretrain.yaml @@ -0,0 +1,223 @@ +work_group: ${TEAM:amd} +user_name: ${USER:root} +exp_name: ${EXP_NAME:gpt_oss_20b} +workspace: ${PRIMUS_WORKSPACE:./output} + +modules: + pre_trainer: + framework: megatron + config: pre_trainer.yaml + + # model to run + model: ${PRIMUS_MODEL:gpt_oss_20B}.yaml + overrides: + + # Activate the migrated MLPerf pretrain trainer (mllog + MLPerf hooks). + stage: ${PRIMUS_STAGE:mlperf_pretrain} + + # tokenizer + tokenizer_type: Llama3Tokenizer + tokenizer_model: ${MODEL:meta-llama/Llama-3.1-8B} + + # model + num_layers: 24 + hidden_size: 2880 + ffn_hidden_size: 2880 + num_attention_heads: 64 + num_query_groups: 8 # Group Query Attention (GQA) - matches HF num_key_value_heads + num_experts: 32 + activation_func: swiglu # SiLU activation (matches HF hidden_act: "silu") + + # rotary + position_embedding_type: rope + rotary_base: 150000 + + # mixed-precision + attention_softmax_in_fp32: false + grad_reduce_in_bf16: ${PRIMUS_GRAD_REDUCE_IN_BF16:true} + + # log + wandb_project: "Primus_GPT_OSS_20B" + stderr_sink_level: ERROR + log_interval: ${LOG_INTERVAL:999999} + + # debug + # moe_router_force_load_balancing: true + # log_avg_skip_iterations: 2 + # log_avg_reset_interval: 50 + + # profile + profile: ${PRIMUS_PROFILE:False} + use_pytorch_profiler: ${PRIMUS_PROFILE:False} + profile_step_end: ${PRIMUS_PROFILE_STEP_END:32} + profile_step_start: ${PRIMUS_PROFILE_STEP_START:16} + profile_ranks: [0] + + # enable fp8 training + fp8: ${PRIMUS_FP8:e4m3} + fp8_recipe: tensorwise + clip_grad: 1.0 # Gradient clipping (already default, but explicit) + check_for_nan_in_loss_and_grad: false + + # hyper parameters + train_iters: ${PRIMUS_TRAIN_ITERS:1200000} + micro_batch_size: ${PRIMUS_MICRO_BATCH_SIZE:2} + global_batch_size: ${PRIMUS_GLOBAL_BATCH_SIZE:16} + seq_length: ${PRIMUS_SEQ_LENGTH:8192} + max_position_embeddings: ${PRIMUS_MAX_POSITION_EMBEDDINGS:131072} + seed: ${SEED:1234} # Random seed for reproducibility + lr: ${PRIMUS_LR:8.0e-4} # Reduced from 8e-4 for FP8 stability + min_lr: ${PRIMUS_MIN_LR:8.0e-5} # Set to 10% of max LR + lr_warmup_iters: ${PRIMUS_LR_WARMUP_ITERS:128} + lr_decay_iters: ${PRIMUS_LR_DECAY_ITERS:1199872} + lr_decay_style: cosine + weight_decay: 0.1 + optimizer: adam + use_distributed_optimizer: true # use distributed optimizer + adam_beta1: 0.9 + adam_beta2: 0.95 + adam_eps: 1.0e-5 + eod_mask_loss: true + init_method_std: 0.008 + norm_epsilon: 1.0e-6 + layernorm_epsilon: 1.0e-05 # RMSNorm epsilon (matches HF rms_norm_eps) + + # Dropout (disabled for training) + hidden_dropout: 0.0 + attention_dropout: 0.0 + + # parallel + tensor_model_parallel_size: ${PRIMUS_TP:1} + pipeline_model_parallel_size: ${PRIMUS_PP:1} + expert_model_parallel_size: ${PRIMUS_EP:8} + overlap_grad_reduce: true + overlap_param_gather: true + ddp_num_buckets: 8 + ddp_average_in_collective: true + + # data + mock_data: false + num_workers: ${PRIMUS_NUM_WORKERS:0} + train_data_path: "10 /data/c4-train.en_6_text_document" + valid_data_path: "/data/c4-validation-91205-samples.en_text_document" + test_data_path: "/data/c4-validation-91205-samples.en_text_document" + # Avoid copying a dense (B, 1, S, S) CPU attention mask every step. + # TE receives causal/sliding-window metadata from attn_mask_type + window_size. + # Use numeric 0/1 because Primus env expansion only type-casts numbers. + create_attention_mask_in_dataloader: ${PRIMUS_CREATE_ATTENTION_MASK_IN_DATALOADER:0} + + # fusion + moe_permute_fusion: true + gradient_accumulation_fusion: true + moe_use_legacy_grouped_gemm: false # Sync-Free MoE stage 2 or 3 require PrimusTurboGroupedMLP, please set `moe_use_legacy_grouped_gemm=True + moe_use_fused_router_with_aux_score: true + multi_latent_attention: false # Flag config.ENABLE_EXPERIMENTAL not enabled + apply_rope_fusion: true + + + # sliding window attention (GPT-OSS-20B model definition; matches HF sliding_window: 128) + # use_turbo_attention is false so non-turbo attention (which supports sliding window) is used. + # Pattern: alternating sliding_attention (1) and full_attention (0) for 24 layers + # window_size must be a tuple (left_window, right_window) for Transformer Engine + # For causal attention: left = past tokens, right = 0 (no future tokens) + # HF sliding_window: 128 means 128 past tokens, so use (128, 0) + window_size: [128, 0] # Left window: 128 past tokens, Right: 0 (causal) + window_attn_skip_freq: [1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0] + + # MoE settings + moe_apply_probs_on_input: false + moe_aux_loss_coeff: 0.0 #0.9 + moe_deepep_num_sms: 20 + moe_enable_deepep: false + moe_expert_capacity_factor: null + moe_extended_tp: false + moe_ffn_hidden_size: 2880 + moe_flex_dispatcher_backend: deepep + moe_grouped_gemm: true + moe_hybridep_num_sms: 16 + moe_input_jitter_eps: null + moe_latent_size: null + moe_layer_freq: 1 + moe_layer_recompute: false + moe_pad_expert_input_to_capacity: false + moe_per_layer_logging: false + moe_router_bias_update_rate: 0.001 + moe_router_dtype: fp32 # DeepEP only supports float32 probs + moe_router_enable_expert_bias: false + moe_router_force_load_balancing: false + moe_router_fusion: true + moe_router_group_topk: null + moe_router_load_balancing_type: none + moe_router_num_groups: null + moe_router_padding_for_fp8: false + # Keep routing exact. PrimusTurbo consumes the ragged expert token counts + # directly, so no fake routes or TE multi_padding/unpadding are needed. + moe_router_padding_for_quantization: false + moe_router_pre_softmax: false + moe_router_score_function: softmax + moe_router_topk: 4 + moe_router_topk_limited_devices: null + moe_router_topk_scaling_factor: null + moe_shared_expert_gate: false + moe_shared_expert_intermediate_size: null + moe_shared_expert_overlap: false + moe_token_dispatcher_type: alltoall + moe_token_drop_policy: probs + moe_token_dropping: false + moe_z_loss_coeff: null + + # ckpt + finetune: false + auto_continue_train: false + load: null + no_load_optim: null + no_load_rng: null + save: null + save_interval: 100000 + no_save_optim: null + no_save_rng: null + disable_last_saving: true + exit_on_missing_checkpoint: false + ckpt_format: torch + eval_iters: ${EVAL_ITERS:64} # eval_samples = eval_iters * GBS = 1024; set EVAL_ITERS in config shell (1024/GBS). + eval_interval: ${PRIMUS_EVAL_INTERVAL:768} + + # Turbo + enable_primus_turbo: true + use_turbo_attention: false + use_turbo_grouped_gemm: true + use_turbo_ragged_grouped_gemm: ${USE_TURBO_RAGGED_GROUPED_GEMM:true} + use_turbo_rms_norm: ${USE_TURBO_RMS_NORM:true} + # Keeps the fused layernorm+QKV site's norm on Turbo and its GEMM on + # hipBLASLt, which is where each backend is faster on these shapes. + use_turbo_norm_te_linear: ${USE_TURBO_NORM_TE_LINEAR:true} + use_turbo_fused_act_with_probs : true + + # deepep + use_turbo_deepep: false + + # 64 or 80 for ep8, 32 for ep16-64 is best practice + turbo_deepep_num_cu: 64 + turbo_deepep_use_comm_stream: false + + # sync-free moe support stage 0-3, 0 means not use sync-free moe + # stage 3 is completely no gpu-cpu sync in MoE, but cost more memory + # stage 2 is recommended for better performance + turbo_sync_free_moe_stage: 0 + + # Cross entropy flags + cross_entropy_fusion_impl: "te" + cross_entropy_loss_fusion: true + + # tensorboard logging, set 'disable_tensorboard: false' to enable tensorboard logging + disable_tensorboard: ${PRIMUS_DISABLE_TENSORBOARD:true} + tensorboard_dir: ${PRIMUS_WORKSPACE:./output}/tensorboard + tensorboard_log_interval: 1 + tensorboard_queue_size: 1000 + log_timers_to_tensorboard: true + log_batch_size_to_tensorboard: true + log_learning_rate_to_tensorboard: true + log_validation_ppl_to_tensorboard: true + log_memory_to_tensorboard: true + log_world_size_to_tensorboard: true + log_loss_scale_to_tensorboard: true diff --git a/examples/mlperf/gpt_oss_20b/prewarm_attention.py b/examples/mlperf/gpt_oss_20b/prewarm_attention.py new file mode 100644 index 000000000..dc0cfcf47 --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/prewarm_attention.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 +"""Populate the two GPT-OSS attention JIT cache entries before torchrun.""" + +import argparse + +import aiter_hd64_asm_override +import torch +from transformer_engine.pytorch.attention import DotProductAttention + + +def prewarm(sequence_length: int, micro_batch_size: int, window_left: int) -> None: + attention = DotProductAttention( + num_attention_heads=64, + kv_channels=64, + num_gqa_groups=8, + attention_dropout=0.0, + qkv_format="sbhd", + attn_mask_type="causal", + window_size=(window_left, 0), + ).cuda() + shapes = ( + (sequence_length, micro_batch_size, 64, 64), + (sequence_length, micro_batch_size, 8, 64), + (sequence_length, micro_batch_size, 8, 64), + ) + query, key, value = ( + torch.randn(shape, device="cuda", dtype=torch.bfloat16, requires_grad=True) for shape in shapes + ) + output = attention(query, key, value) + if isinstance(output, tuple): + output = output[0] + output.float().square().mean().backward() + torch.cuda.synchronize() + print(f"attention_prewarm=PASS window_left={window_left}") + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--sequence-length", type=int, default=8192) + parser.add_argument("--micro-batch-size", type=int, default=4) + parser.add_argument("--window-left", type=int, action="append") + args = parser.parse_args() + + if not torch.cuda.is_available(): + raise RuntimeError("ROCm GPU is not available") + torch.cuda.set_device(0) + torch.manual_seed(1234) + + windows = args.window_left or [128, -1] + for window_left in windows: + prewarm(args.sequence_length, args.micro_batch_size, window_left) + if aiter_hd64_asm_override.get_dispatch_count() < len(windows): + raise RuntimeError("Forward ASM override was not dispatched") + + +if __name__ == "__main__": + main() diff --git a/examples/mlperf/gpt_oss_20b/run_and_time.sh b/examples/mlperf/gpt_oss_20b/run_and_time.sh new file mode 100755 index 000000000..1ef73b1ad --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/run_and_time.sh @@ -0,0 +1,67 @@ +#!/bin/bash + +set -e + +# Create results directory +mkdir -p /results + +cd "${PRIMUS_PATH}/examples/mlperf/gpt_oss_20b" +TRAIN_LOG_FILE="${TRAIN_LOG_FILE:-train.mlperfpretrain.exp.log}" + +# Under a multi-node scheduler wrapper, inherit rendezvous + node sizing from +# SLURM so the same benchmark config scales without edits. Single-node jobs +# fall through to the config defaults. +if [[ -n "${SLURM_NNODES:-}" && "${SLURM_NNODES}" -gt 1 ]]; then + NNODES="${SLURM_NNODES}" + NODE_RANK="${SLURM_NODEID:-0}" +fi + +# TE 2.15 lazily compiles CK attention blobs. Populate both GPT-OSS windows +# once before torchrun so eight ranks do not race while writing the cache. +if [ "${MLPERF_RUNTIME_SERIES:-v26.3}" = "v26.5" ] \ + && [ "${MLPERF_SKIP_ATTENTION_PREWARM:-0}" != "1" ]; then + python3 /opt/mlperf-gpt-oss-20b/prewarm_attention.py +fi + +echo "============================================" +echo "MLPerf GPT-OSS-20B Training" +echo "============================================" +echo "Config: ${EXP}" +echo "Data: ${DATA_PATH}" +echo "GPUs: ${GPUS_PER_NODE}" +echo "Nodes: ${NNODES}" +echo "Rank: ${NODE_RANK}" +echo "Master: ${MASTER_ADDR}:${MASTER_PORT}" +echo "============================================" + +# Start timing +start=$(date +%s) +start_fmt=$(date +%Y-%m-%d\ %r) +echo "STARTING TIMING RUN AT $start_fmt" + +# Launch through Primus CLI and keep the real exit code even though output is +# piped through tee. +set +e +"${PRIMUS_PATH}/primus-cli" direct -- \ + train pretrain \ + --config "${EXP}" \ + 2>&1 | tee "${TRAIN_LOG_FILE}" +ret_code=${PIPESTATUS[0]} +set -e + +# End timing +end=$(date +%s) +end_fmt=$(date +%Y-%m-%d\ %r) +echo "ENDING TIMING RUN AT $end_fmt" + +# Report result +result=$(( end - start )) +result_name="GPT_OSS_20B" +echo "RESULT,$result_name,,$result,AMD,$start_fmt" + +if [[ $ret_code != 0 ]]; then + echo "Training failed with exit code: $ret_code" + exit "$ret_code" +fi + +exit 0 diff --git a/examples/mlperf/gpt_oss_20b/te_hipblaslt_algo-v26.3.csv b/examples/mlperf/gpt_oss_20b/te_hipblaslt_algo-v26.3.csv new file mode 100644 index 000000000..1191c4552 --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/te_hipblaslt_algo-v26.3.csv @@ -0,0 +1,10 @@ +dev_cap,m,n,k,trans_a,trans_b,type_a,type_b,type_d,bias_type,aux_type,lda,ldb,ldd,scale_mode,epi,comp,scale_type,ws_min,ws_max,algo_id,aidx +905,5120,32768,2880,T,N,float8e4m3,float8e4m3,bfloat16,float32,-,2880,2880,5120,0,-,f32,float32,0,67108864,349458,30 +905,2880,32768,4096,T,N,float8e4m3,float8e4m3,bfloat16,float32,-,4096,4096,2880,0,-,f32,float32,0,67108864,349558,7 +905,32,32768,2880,T,N,bfloat16,bfloat16,float32,-,-,2880,2880,32,0,-,f32,float32,0,67108864,337773,36 +905,2880,32768,32,N,N,float32,float32,float32,-,-,2880,32,2880,0,-,f32,float32,0,67108864,384788,31 +905,2880,32,32768,N,T,float32,float32,float32,-,-,2880,32,2880,0,-,f32,float32,5160960,67108864,383709,1 +905,4096,32768,2880,T,N,float8e4m3,float8e4m3,bfloat16,float32,-,2880,2880,4096,0,-,f32,float32,0,67108864,349495,40 +905,4096,2880,32768,T,N,float8e4m3,float8e4m3,bfloat16,float32,-,32768,32768,4096,0,-,f32,float32,0,67108864,349625,0 +905,2880,32768,5120,T,N,float8e4m3,float8e4m3,bfloat16,float32,-,5120,5120,2880,0,-,f32,float32,0,67108864,349570,4 +905,2880,5120,32768,T,N,float8e4m3,float8e4m3,bfloat16,float32,-,32768,32768,2880,0,-,f32,float32,0,67108864,349555,1 diff --git a/examples/mlperf/gpt_oss_20b/tune_gemm_results-v26.3.txt b/examples/mlperf/gpt_oss_20b/tune_gemm_results-v26.3.txt new file mode 100644 index 000000000..9d987b63d --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/tune_gemm_results-v26.3.txt @@ -0,0 +1,25 @@ +Git Version: de5c1aebb6-dirty + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,32,32768,2880,1,2880,92160,0,2880,94371840,32,1048576,32,1048576,bf16_r,bf16_r,f32_r,f32_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,f32_r,512,1,0,151492,4511.27,39.8689,303082,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,T,0,1,2880,32,32768,1,2880,94371840,0,32,1048576,2880,92160,2880,92160,f32_r,f32_r,f32_r,f32_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,f32_r,512,1,0,81491.9,4800.79,74.1153,311532,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,2880,32768,4096,1,4096,11796480,0,4096,134217728,2880,94371840,2880,94371840,f8_r,f8_r,bf16_r,bf16_r,f32_r,1,1,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.69535e+06,1086.96,286.825,306398,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,4096,2880,32768,1,32768,134217728,0,32768,94371840,4096,11796480,4096,11796480,f8_r,f8_r,bf16_r,bf16_r,f32_r,1,1,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.59496e+06,788.341,297.921,306251,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,4096,32768,2880,1,2880,11796480,0,2880,94371840,4096,134217728,4096,134217728,f8_r,f8_r,bf16_r,bf16_r,f32_r,1,1,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.42444e+06,1094.09,318.875,306237,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,5120,32768,2880,1,2880,14745600,0,2880,94371840,5120,167772160,5120,167772160,f8_r,f8_r,bf16_r,bf16_r,f32_r,1,1,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.39063e+06,1024.47,404.231,306248,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,N,0,1,2880,32768,32,1,2880,92160,0,32,1048576,2880,94371840,2880,94371840,f32_r,f32_r,f32_r,f32_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,f32_r,512,1,0,64447.9,3796.71,93.716,311699,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,2880,32768,5120,1,5120,14745600,0,5120,167772160,2880,94371840,2880,94371840,f8_r,f8_r,bf16_r,bf16_r,f32_r,1,1,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.81411e+06,1006.88,343.401,306396,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,2880,5120,32768,1,32768,94371840,0,32768,167772160,2880,14745600,2880,14745600,f8_r,f8_r,bf16_r,bf16_r,f32_r,1,1,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.96197e+06,832.489,326.259,306396,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,N,0,1,2880,32768,128256,1,2880,369377280,0,128256,4202692608,2880,94371840,2880,94371840,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.32172e+06,474.576,18315.1,300658,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,128256,32768,2880,1,2880,369377280,0,2880,94371840,128256,4202692608,128256,4202692608,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.45958e+06,524.074,16585.3,302286,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,T,0,1,2880,128256,32768,1,2880,94371840,1,128256,4202692608,2880,369377280,2880,369377280,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.31028e+06,544.947,18475.1,300155,gfx950:sramecc+:xnack-,256 diff --git a/examples/mlperf/gpt_oss_20b/tune_gemm_results-v26.5.txt b/examples/mlperf/gpt_oss_20b/tune_gemm_results-v26.5.txt new file mode 100644 index 000000000..4e3711ba3 --- /dev/null +++ b/examples/mlperf/gpt_oss_20b/tune_gemm_results-v26.5.txt @@ -0,0 +1,41 @@ +Git Version: fa9cdd18 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,32,32768,2880,1,2880,92160,0,2880,94371840,32,1048576,32,1048576,bf16_r,bf16_r,f32_r,f32_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,f32_r,512,1,0,182699,5440.59,33.0588,24641,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,N,0,1,2880,32768,32,1,2880,92160,0,32,1048576,2880,94371840,2880,94371840,f32_r,f32_r,f32_r,f32_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,f32_r,512,1,0,68985.3,4064.01,87.552,73624,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,T,0,1,4096,2880,32768,1,4096,134217728,0,2880,94371840,4096,11796480,4096,11796480,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.4418e+06,835.047,536.202,12468,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,T,0,1,4096,2880,32768,1,4096,134217728,1,2880,94371840,4096,11796480,4096,11796480,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.4024e+06,891.947,551.265,11292,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,T,0,1,2880,32,32768,1,2880,94371840,0,32,1048576,2880,92160,2880,92160,f32_r,f32_r,f32_r,f32_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,f32_r,512,1,0,86893.9,5119.03,69.5077,72656,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,T,0,1,2880,5120,32768,1,2880,94371840,0,5120,167772160,2880,14745600,2880,14745600,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.42455e+06,760.278,678.366,11258,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,T,0,1,2880,5120,32768,1,2880,94371840,1,5120,167772160,2880,14745600,2880,14745600,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.38626e+06,818.644,697.102,12554,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,2880,32768,4096,1,4096,11796480,0,4096,134217728,2880,94371840,2880,94371840,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.43674e+06,832.117,538.09,19081,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,N,0,1,4096,32768,2880,1,4096,11796480,0,2880,94371840,4096,134217728,4096,134217728,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.47155e+06,852.281,525.36,14593,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,N,0,1,2880,32768,5120,1,2880,14745600,0,5120,167772160,2880,94371840,2880,94371840,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.46461e+06,781.659,659.811,14734,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,5120,32768,2880,1,2880,14745600,0,2880,94371840,5120,167772160,5120,167772160,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.53294e+06,818.125,630.401,21406,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,N,0,1,2880,32768,128256,1,2880,369377280,0,128256,4202692608,2880,94371840,2880,94371840,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.53955e+06,552.788,15723.8,13283,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + N,T,0,1,2880,128256,32768,1,2880,94371840,1,128256,4202692608,2880,369377280,2880,369377280,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.49105e+06,620.132,16235.2,12111,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,128256,32768,2880,1,2880,369377280,0,2880,94371840,128256,4202692608,128256,4202692608,bf16_r,bf16_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,1.46089e+06,524.544,16570.4,22416,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,4096,2880,32768,1,32768,134217728,0,32768,94371840,4096,11796480,4096,11796480,f8_r,f8_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.79595e+06,849.399,276.505,37702,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,2880,32768,4096,1,4096,11796480,0,4096,134217728,2880,94371840,2880,94371840,f8_r,f8_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.81692e+06,1135.99,274.447,38550,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,2880,5120,32768,1,32768,94371840,0,32768,167772160,2880,14745600,2880,14745600,f8_r,f8_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.97207e+06,835.326,325.15,36173,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,2880,32768,5120,1,5120,14745600,0,5120,167772160,2880,94371840,2880,94371840,f8_r,f8_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.87346e+06,1028.12,336.308,38461,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,4096,32768,2880,1,2880,11796480,0,2880,94371840,4096,134217728,4096,134217728,f8_r,f8_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.56755e+06,1158.67,301.102,36093,gfx950:sramecc+:xnack-,256 + transA,transB,grouped_gemm,batch_count,m,n,k,alpha,lda,stride_a,beta,ldb,stride_b,ldc,stride_c,ldd,stride_d,a_type,b_type,c_type,d_type,compute_type,scaleA,scaleB,scaleC,scaleD,amaxD,swizzle_a,swizzle_b,activation_type,bias_vector,bias_type,aux_type,rotating_buffer,flush,use_gpu_timer,hipblaslt-Gflops,hipblaslt-GB/s,us,solution_index,gcnArchName,CUs + T,N,0,1,5120,32768,2880,1,2880,14745600,0,2880,94371840,5120,167772160,5120,167772160,f8_r,f8_r,bf16_r,bf16_r,f32_r,0,0,0,0,0,0,0,none,0,f32_r,bf16_r,512,1,0,2.54105e+06,1088.93,380.303,36094,gfx950:sramecc+:xnack-,256 diff --git a/examples/mlperf/llama2_70b/README.md b/examples/mlperf/llama2_70b/README.md new file mode 100644 index 000000000..46ede190a --- /dev/null +++ b/examples/mlperf/llama2_70b/README.md @@ -0,0 +1,377 @@ +# Llama2-70B LoRA MLPerf on MI355X (Primus) + +MLPerf Training 6.0 Llama2-70B LoRA on **MI355X** (8× GPU, 1 node) via Megatron-Bridge and `primus-cli`. + +Dataset: [GovReport](https://gov-report-data.github.io/) (SCROLLS `gov_report`), packed to **8192** tokens. +Model: **meta-llama/Llama-2-70b-hf** with LoRA (rank 16, alpha 32). +Precision: **MXFP4** + BF16; **FP8 delayed scaling** after healing at step 340. + +## Key files + +- `configs/MI355X/llama2_70b_lora_mlperf_posttrain.yaml` — post-train overrides +- `config_MI355X_1x8x1.sh` — system config and env vars (set `PRIMUS_PATH` to your Primus clone) +- `run_with_docker.sh` — **recommended**: host-side Docker orchestration (N timed trials) +- `runtime_tunables.sh` — host CPU/THP/cache tuning before each trial +- `run_and_time.sh` — timed MLPerf run inside the container (called by `run_with_docker.sh`) +- `a4w4_tuned_gemms.csv` — tuned AITER A4W4 GEMM configs + +--- + +## Prerequisites + +- 8× MI355X GPUs on one node +- Hugging Face access to `meta-llama/Llama-2-70b-hf` (`HF_TOKEN`) +- ~300 GB disk for packed data + Megatron checkpoint +- Docker with ROCm (`/dev/kfd`, `/dev/dri`) + +--- + +## 1. Manual container (optional) + +Use this only if you are **not** using **`run_with_docker.sh`** and want an interactive shell inside the image. + +```bash +docker pull rocm/primus:v26.5 + +docker run -it \ + --device=/dev/kfd \ + --device=/dev/dri \ + --security-opt seccomp=unconfined \ + --group-add 44 \ + --group-add 109 \ + --cap-add=SYS_PTRACE \ + --ipc=host \ + --shm-size=32g \ + --ulimit memlock=-1 \ + --ulimit stack=67108864 \ + --memory=0 \ + --memory-swap=0 \ + --privileged \ + --ulimit nofile=65535:65535 \ + -v /path/to/Primus:/workspace/Primus \ + -v /path/on/host/data:/data \ + rocm/primus:v26.5 +``` + +Repo is at **`/workspace/Primus`** inside the container. Then follow [§4](#4-run-training-inside-an-existing-container). + +## 2. Data layout (host `DATADIR` / container `/data`) + +```text +${DATADIR}/ +├── train.npy +├── validation.npy +├── packed_metadata.jsonl +├── megatron_checkpoints/Llama-2-70b-hf/ # checkpoint root (not iter_0000000/) +└── .cache/huggingface/ # optional HF cache +``` + +**`run_with_docker.sh`** sets **`PACKED_DATA_DIR=/data`**, **`PRETRAINED_CHECKPOINT=/data/megatron_checkpoints/Llama-2-70b-hf`**, and **`HF_HOME=/data/.cache/huggingface`** automatically. On first run, posttrain hooks populate missing data/checkpoints when **`HF_TOKEN`** is set. + +For a **manual** container (§1), export the same paths inside the shell before **`run_and_time.sh`**: + +```bash +export HF_TOKEN=hf_... +export PACKED_DATA_DIR=/data +export PRETRAINED_CHECKPOINT=/data/megatron_checkpoints/Llama-2-70b-hf +source /workspace/Primus/examples/mlperf/llama2_70b/config_MI355X_1x8x1.sh +bash /workspace/Primus/examples/mlperf/llama2_70b/run_and_time.sh +``` + +Checkpoint root must contain **`latest_train_state.pt`**, not only **`iter_0000000/`**. + +--- + +## 3. Run with `run_with_docker.sh` (recommended) + +Use this when you want the **MLPerf submission-style flow** from the **host**: one command pulls up Docker, mounts data and results, applies host runtime tunables, runs **N** timed trials, and collects logs under **`LOGDIR`**. You do **not** need to `docker run -it` manually unless you are debugging inside the container (see [§4](#4-run-training-inside-an-existing-container)). + +### 3.1 Quick start (non-interactive) + +From your **Primus repo root** on an 8× MI355X node with Docker + ROCm devices: + +```bash +cd /path/to/Primus + +export HF_TOKEN=hf_... # required: Llama-2-70b + GovReport hooks +export DATADIR=/data/mlperf_llama2 # writable: dataset + checkpoint (host) +export LOGDIR=/data/mlperf_llama2/results # writable: trial logs + MLLOG (host) +export CONT=rocm/primus:v26.5 # Primus ROCm image +export DGXSYSTEM=MI355X_1x8x1 # selects config_${DGXSYSTEM}.sh +export NEXP=1 # use 10 for MLPerf submission runs + +# Optional: quiet stdout (:::MLLOG + timing banners only) +export SUBMISSION_QUIET=1 + +bash examples/mlperf/llama2_70b/run_with_docker.sh +``` + +`PRIMUS_HOST` defaults to the repo root (three levels above `examples/mlperf/llama2_70b/`). Override if your checkout is elsewhere: + +```bash +export PRIMUS_HOST=/other/path/Primus +bash examples/mlperf/llama2_70b/run_with_docker.sh +``` + +### 3.2 Interactive mode + +Prompts for **`HF_TOKEN`**, image, **`NEXP`**, **`DATADIR`**, **`LOGDIR`**, and **`DGXSYSTEM`** when values are missing: + +```bash +cd /path/to/Primus +export SUBMISSION_QUIET=1 # optional +INTERACTIVE=1 bash examples/mlperf/llama2_70b/run_with_docker.sh +``` + +### 3.3 What the script does + +1. **Validates** `config_${DGXSYSTEM}.sh` exists and **`HF_TOKEN`** is set. +2. **Removes** any existing container named **`CONT_NAME`** (default `mlperf_llama2_70b_lora_primus`), then starts a **detached** container (`sleep infinity`) with ROCm devices and mounts below. +3. **Installs** editable Primus from the mount (`pip install -e /workspace/Primus --no-deps`); Python/torchrun come from the image venv **`/opt/venv/bin`** (not the host `PATH`). +4. For each trial **`1..NEXP`**: + - Runs **`runtime_tunables.sh`** on the **host** (unless disabled). + - Optionally drops host page cache if **`CLEAR_CACHES=1`**. + - **`docker exec`** → **`bash /workspace/code/run_and_time.sh`** with a new **`SEED=$RANDOM`** and env from **`config_MI355X_1x8x1.sh`** (+ MLLOG paths under **`/results`**). + - Streams container stdout to **`${LOGDIR}/${DATESTAMP}_.log`** on the host. + - Copies **`mlperf_logging.out`** and **`train.mlperfposttrain.exp.log`** into **`${LOGDIR}/artifacts/`** after each successful trial. +5. **Removes** the container on exit (success or failure). + +First run can take a long time: posttrain hooks may download the HF model, convert checkpoints, and pack GovReport under **`DATADIR`**. Later runs reuse **`${DATADIR}/train.npy`**, **`validation.npy`**, and **`megatron_checkpoints/`**. + +### 3.4 Volume and path map + +| Host | Container | Purpose | +|------|-----------|---------| +| **`PRIMUS_HOST`** (Primus repo) | `/workspace/Primus` | Code; editable install; `PRIMUS_PATH` | +| **`examples/mlperf/llama2_70b/`** (this example dir) | `/workspace/code` | `run_and_time.sh`, `config_*.sh` | +| **`DATADIR`** | `/data` | Packed `.npy`, HF cache, Megatron checkpoint | +| **`LOGDIR`** | `/results` | MLLOG, primus-cli logs, timed run artifacts | + +Inside the container, **`run_and_time.sh`** uses **`/results`** for: + +| File | Description | +|------|-------------| +| `mlperf_logging.out` | `:::MLLOG` log (`ENABLE_MLLOG=1`) | +| `train.mlperfposttrain.exp.log` | Full timed-run stdout | +| `logs/log_*.txt` | `primus-cli direct` log | +| `RESULT,LLAMA2_70B_LORA,,,AMD,