diff --git a/.ci/community-jenkins/Jenkinsfile b/.ci/community-jenkins/Jenkinsfile index 2c20d630ac1..603b3a379db 100644 --- a/.ci/community-jenkins/Jenkinsfile +++ b/.ci/community-jenkins/Jenkinsfile @@ -59,21 +59,28 @@ def prepare_check_stages() { "--disable-dlopen", "--disable-oshmem", "--enable-builtin-atomic", - "--enable-ipv6" + "--enable-ipv6", + "--enable-mca-dso" ] + // explicitly list the newest and oldest versions of GCC that are included + // in the distros listed below to try and give us good version coverage. def compilers = [ - "gcc14", - "clang18" + "gcc9", + "gcc15", + "clang11", + "clang21" ] def platforms = [ - "amazon_linux_2", - "amazon_linux_2-arm64", "rhel8", + "rhel9", + "rhel10", "amazon_linux_2023-arm64", "amazon_linux_2023-x86_64", - "ubuntu_20.04", + "freebsd_15", "ubuntu_24.04-arm64", - "ubuntu_24.04-x86_64" + "ubuntu_24.04-x86_64", + "ubuntu_26.04-arm64", + "ubuntu_26.04-x86_64" ] def check_stages_list = [] diff --git a/.ci/community-jenkins/pr-builder.sh b/.ci/community-jenkins/pr-builder.sh index 88426859bf0..badd4cf670a 100755 --- a/.ci/community-jenkins/pr-builder.sh +++ b/.ci/community-jenkins/pr-builder.sh @@ -3,6 +3,7 @@ # Copyright (c) 2022-2023 Amazon.com, Inc. or its affiliates. All rights # reserved. # Copyright (c) 2022-2023 Joe Downs. All rights reserved. +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -17,7 +18,7 @@ COMPILER= DISTCHECK=0 AUTOGEN_ARGS= CONFIGURE_ARGS= -MAKE_ARGS= +MAKE_ARGS="VERBOSE=1" MAKE_J="-j 8" PREFIX="${WORKSPACE}/install" MPIRUN_MODE=${MPIRUN_MODE:-runall} @@ -145,13 +146,16 @@ if test "${COMPILER}" != "" ; then exit 1 fi + set +u . ${HOME}/ompi-compiler-setup.sh activate_compiler ${COMPILER} + set -u - CONFIGURE_ARGS="${CONFIGURE_ARGS} CC=${CC} CPP=${CPP} CXX=${CXX} FC=${FC}" - if test "$FC" = "" ; then + CONFIGURE_ARGS="${CONFIGURE_ARGS} CC=${CC} CPP=${CPP} CXX=${CXX}" + if [ -z "${FC:-}" ] ; then CONFIGURE_ARGS="${CONFIGURE_ARGS} --disable-mpi-fortran" else + CONFIGURE_ARGS="${CONFIGURE_ARGS} FC=${FC}" # Flang doesn't seem good enough (yet) to compile our Fortran bindings, # so skip for now. case "${COMPILER}" in @@ -164,6 +168,12 @@ fi CONFIGURE_ARGS="$CONFIGURE_ARGS --disable-silent-rules" +# Work around the fact that FreeBSD's hwloc package installs Ze and +# that breaks something in the cudasm path on FreeBSD. +if test "${PLATFORM_ID}" = "FreeBSD" ; then + CONFIGURE_ARGS="${CONFIGURE_ARGS} --without-ze" +fi + echo "--> Compiler setup: $CONFIGURE_ARGS" # @@ -241,10 +251,13 @@ fi echo "--> running make ${MAKE_J} ${MAKE_ARGS} all" make ${MAKE_J} ${MAKE_ARGS} all -echo "--> running make check" -make ${MAKE_ARGS} check +# while backwards, it is important to run "make install" before "make check", +# because many of the tests call opal_init(), which will fail unless it can find +# components. echo "--> running make install" make ${MAKE_ARGS} install +echo "--> running make check" +make ${MAKE_ARGS} check export PATH="${PREFIX}/bin":${PATH} diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 00000000000..b002ea3373b --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Open MPI Community Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the Open MPI project or its community. Examples of representing the project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of the project may be further defined and clarified by Open MPI maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at maintainers@open-mpi.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml new file mode 100644 index 00000000000..af9a473663f --- /dev/null +++ b/.github/codeql/codeql-config.yml @@ -0,0 +1,19 @@ +# +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# +# CodeQL configuration for Open MPI. Push / PR scans load this file +# locally from the checked-out branch. The scheduled cross-branch scan +# loads this same file from the default branch via CodeQL's remote +# config-file syntax, so the release-branch scans do not require a local +# copy of this file to already exist. + +name: "Open MPI CodeQL config" + +paths-ignore: + - '3rd-party' + - '**/test' diff --git a/.github/workflows/backport-command.yaml b/.github/workflows/backport-command.yaml new file mode 100644 index 00000000000..719c55d62ae --- /dev/null +++ b/.github/workflows/backport-command.yaml @@ -0,0 +1,172 @@ +# Slash-command handler for /backport. +# +# Posting a comment on a merged PR with: +# +# /backport v5.0.x v4.1.x +# +# is equivalent to manually triggering the "Backport" workflow from the +# GitHub Actions UI with those branch names. Multiple branches may be +# supplied as space- or comma-separated values on the same line. +# +# Only repository owners, organisation members, and collaborators may trigger +# the command. If an unauthorised user attempts /backport, the bot replies +# with an explanatory comment. For valid commands it acknowledges with a 👀 +# reaction; invalid or unrecognised commands get a usage hint as a comment. + +name: Backport slash command + +on: + issue_comment: + types: [created] + +permissions: {} + +jobs: + dispatch: + name: Handle /backport comment + runs-on: ubuntu-latest + # Only act on PR comments (issue_comment fires for both issues and PRs). + if: github.event.issue.pull_request != null + permissions: + actions: write # trigger workflow_dispatch + issues: write # post comments + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Parse command and validate PR + id: parse + uses: actions/github-script@v8 + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const body = context.payload.comment.body; + const commentId = context.payload.comment.id; + const issueNumber = context.payload.issue.number; + const login = context.payload.comment.user.login; + + // Best-effort comment helper — if Issues are disabled on + // the repo (common for forks) the call returns 403 and we + // log a warning rather than aborting the workflow. + async function tryComment(text) { + try { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + body: text, + }); + } catch (err) { + core.warning(`Could not post comment: ${err.message}`); + } + } + + // Detect a bare /backport with no arguments and reply helpfully. + const bareMatch = /^\/backport\s*$/m.test(body); + // Look for /backport with arguments at the start of any line. + const match = body.match(/^\/backport\s+([^\r\n]+)/m); + + // If the comment doesn't contain any /backport command at all, + // do nothing — no need to check permissions. + if (!bareMatch && !match) { + core.setOutput('triggered', 'false'); + return; + } + + // Use author_association from the webhook payload — no extra + // API call required. getCollaboratorPermissionLevel requires + // org-level "Members" read permission, which is not available + // to either GITHUB_TOKEN or a GitHub App without explicit + // org-level permission grants. + const assoc = context.payload.comment.author_association; + if (!['OWNER', 'MEMBER', 'COLLABORATOR'].includes(assoc)) { + await tryComment(`âš ī¸ @${login} Backports can only be triggered by repository owners, organization members, or collaborators.`); + core.setOutput('triggered', 'false'); + return; + } + + if (bareMatch && !match) { + core.setOutput('triggered', 'false'); + await tryComment('âš ī¸ `/backport` requires at least one target branch, e.g. `/backport v5.0.x`.'); + return; + } + if (!match) { + core.setOutput('triggered', 'false'); + return; + } + + // Parse branch list (space- or comma-separated). + const branches = match[1].trim().split(/[\s,]+/).filter(Boolean); + if (branches.length === 0) { + // e.g. "/backport ,,," — separators only, no real branch names + core.setOutput('triggered', 'false'); + await tryComment('âš ī¸ `/backport` requires at least one target branch, e.g. `/backport v5.0.x`.'); + return; + } + + // Validate branch names with the same allow-list used in + // backport.yaml so the user gets immediate feedback rather + // than a silent dispatch failure. + const validBranchRe = /^[a-zA-Z0-9][a-zA-Z0-9._\-/]*$/; + const invalidBranches = branches.filter(b => !validBranchRe.test(b)); + if (invalidBranches.length > 0) { + core.setOutput('triggered', 'false'); + await tryComment(`âš ī¸ Invalid branch name(s): ${invalidBranches.map(b => `\`${b}\``).join(', ')}. Branch names may only contain alphanumeric characters, dots, hyphens, underscores, and slashes.`); + return; + } + + // Confirm the PR is actually merged. + // merged_at is present in the issue_comment webhook payload + // for PRs, so no extra API call is needed. + if (!context.payload.issue.pull_request.merged_at) { + await tryComment('âš ī¸ Cannot backport: this PR has not been merged yet.'); + core.setOutput('triggered', 'false'); + return; + } + + // Acknowledge the command with a 👀 reaction on the triggering comment. + // Ignore 422 (reaction already exists) so re-runs don't fail. + try { + await github.rest.reactions.createForIssueComment({ + owner: context.repo.owner, + repo: context.repo.repo, + comment_id: commentId, + content: 'eyes', + }); + } catch (err) { + if (err.status !== 422) throw err; + } + + core.setOutput('triggered', 'true'); + core.setOutput('pr_number', String(issueNumber)); + core.setOutput('branches', branches.join(',')); + core.notice(`Dispatching backport of PR #${issueNumber} to: ${branches.join(', ')}`); + + - name: Trigger backport workflow + if: steps.parse.outputs.triggered == 'true' + uses: actions/github-script@v8 + env: + PR_NUMBER: ${{ steps.parse.outputs.pr_number }} + BRANCHES: ${{ steps.parse.outputs.branches }} + with: + script: | + // workflow_dispatch requires a ref; use the default branch. + const { data: repo } = await github.rest.repos.get({ + owner: context.repo.owner, + repo: context.repo.repo, + }); + + await github.rest.actions.createWorkflowDispatch({ + owner: context.repo.owner, + repo: context.repo.repo, + workflow_id: 'backport.yaml', + ref: repo.default_branch, + inputs: { + pr_number: process.env.PR_NUMBER, + branches: process.env.BRANCHES, + }, + }); diff --git a/.github/workflows/backport.yaml b/.github/workflows/backport.yaml new file mode 100644 index 00000000000..38fb7917d49 --- /dev/null +++ b/.github/workflows/backport.yaml @@ -0,0 +1,391 @@ +# Backport merged PRs to release branches. +# +# This workflow supports two modes: +# +# 1. Automatic (label-based): Apply one or more "backport:vX.Y.z" labels to a +# PR before merging. Once the PR is merged, this workflow fires and creates +# a cherry-pick PR for each labelled target branch. +# +# 2. Manual (workflow_dispatch): After a PR has already been merged, trigger +# this workflow manually via the GitHub Actions UI, providing the PR number +# and a comma-separated list of target branches. +# +# For every successful cherry-pick, a new PR is opened against the target +# branch and tagged with a "target:vX.Y.z" label. If the cherry-pick +# produces conflicts, a comment is posted on the original PR instead so a +# developer can handle it manually. + +name: Backport + +on: + pull_request_target: + types: [closed] + workflow_dispatch: + inputs: + pr_number: + description: 'Number of the merged PR to backport' + required: true + type: number + branches: + description: 'Target release branches (comma-separated, e.g. v5.0.x,v4.1.x)' + required: true + type: string + +permissions: {} + +jobs: + # ------------------------------------------------------------------------- + # Determine which branches need a backport and expose them as a matrix. + # ------------------------------------------------------------------------- + prepare: + name: Prepare backport targets + runs-on: ubuntu-latest + # For pull_request_target: only act when the PR was actually merged AND + # carries at least one "backport:" label (avoids a spurious job run on + # every other merge). For workflow_dispatch: always proceed. + if: > + github.event_name == 'workflow_dispatch' || + (github.event.pull_request.merged == true && + contains(toJson(github.event.pull_request.labels.*.name), '"backport:')) + outputs: + matrix: ${{ steps.targets.outputs.matrix }} + has_targets: ${{ steps.targets.outputs.has_targets }} + pr_number: ${{ steps.targets.outputs.pr_number }} + steps: + - name: Determine backport targets + id: targets + uses: actions/github-script@v8 + with: + script: | + let branches = []; + let prNumber; + + if (context.eventName === 'workflow_dispatch') { + prNumber = Number(context.payload.inputs.pr_number); + if (!Number.isFinite(prNumber) || prNumber <= 0 || !Number.isInteger(prNumber)) { + core.setFailed(`Invalid pr_number: "${context.payload.inputs.pr_number}"`); + return; + } + branches = context.payload.inputs.branches + .split(',') + .map(b => b.trim()) + .filter(Boolean); + } else { + prNumber = context.payload.pull_request.number; + const labels = context.payload.pull_request.labels.map(l => l.name); + for (const label of labels) { + const match = label.match(/^backport:(.+)$/); + if (match) { + branches.push(match[1].trim()); + } + } + } + + // Validate branch names with a strict allow-list: must start + // with alphanumeric and contain only alphanumeric, dot, + // hyphen, underscore, or slash. De-duplicate preserving order. + const validBranchRe = /^[a-zA-Z0-9][a-zA-Z0-9._\-/]*$/; + const invalid = branches.filter(b => !validBranchRe.test(b)); + if (invalid.length > 0) { + core.setFailed(`Invalid branch name(s): ${invalid.join(', ')}`); + return; + } + branches = [...new Set(branches)]; + + core.setOutput('pr_number', String(prNumber)); + core.setOutput('has_targets', branches.length > 0 ? 'true' : 'false'); + core.setOutput('matrix', JSON.stringify({ branch: branches })); + + if (branches.length === 0) { + core.notice('No backport targets found — nothing to do.'); + } else { + core.notice(`Will backport PR #${prNumber} to: ${branches.join(', ')}`); + } + + # ------------------------------------------------------------------------- + # One job per target branch. All branches run in parallel; a failure on + # one branch does not cancel the others. + # ------------------------------------------------------------------------- + backport: + name: Backport to ${{ matrix.branch }} + needs: prepare + if: needs.prepare.outputs.has_targets == 'true' + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + issues: write + strategy: + matrix: ${{ fromJson(needs.prepare.outputs.matrix) }} + fail-fast: false + env: + PR_NUMBER: ${{ needs.prepare.outputs.pr_number }} + TARGET_BRANCH: ${{ matrix.branch }} + steps: + - name: Generate GitHub App token + id: app-token + uses: actions/create-github-app-token@v1 + with: + app-id: ${{ secrets.APP_ID }} + private-key: ${{ secrets.APP_PRIVATE_KEY }} + + - name: Checkout repository (full history) + uses: actions/checkout@v6 + with: + fetch-depth: 0 + token: ${{ steps.app-token.outputs.token }} + + - name: Configure git identity + run: | + git config user.name "${{ steps.app-token.outputs.app-slug }}[bot]" + git config user.email "${{ steps.app-token.outputs.app-slug }}[bot]@users.noreply.github.com" + + # Retrieve PR metadata (title, body, commit list) via the API. + # Use paginate() so PRs with more than 100 commits are handled correctly. + - name: Fetch PR metadata + id: pr_meta + uses: actions/github-script@v8 + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const pr = await github.rest.pulls.get({ + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: Number(process.env.PR_NUMBER), + }); + core.setOutput('title', pr.data.title); + // Body may be empty/null — default to empty string. + core.setOutput('body', pr.data.body ?? ''); + + // Collect all commit SHAs in merge order, paginating as needed. + const commits = await github.paginate(github.rest.pulls.listCommits, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number: Number(process.env.PR_NUMBER), + per_page: 100, + }); + const shas = commits.map(c => c.sha); + core.setOutput('commits', shas.join(' ')); + + # Verify the target release branch actually exists before doing any + # work. Post a comment and skip if it does not. + - name: Validate target branch exists + id: validate + uses: actions/github-script@v8 + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + try { + await github.rest.repos.getBranch({ + owner: context.repo.owner, + repo: context.repo.repo, + branch: process.env.TARGET_BRANCH, + }); + core.setOutput('branch_exists', 'true'); + } catch (err) { + if (err.status !== 404) throw err; + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: Number(process.env.PR_NUMBER), + body: `âš ī¸ Cannot backport to \`${process.env.TARGET_BRANCH}\`: branch does not exist in this repository.`, + }); + core.setOutput('branch_exists', 'false'); + } + + # Cherry-pick every commit from the PR onto a new branch based on + # the target release branch. Push the branch on success; set a + # flag on conflict so the next step can report the failure. + - name: Cherry-pick commits onto backport branch + id: cherry_pick + if: steps.validate.outputs.branch_exists == 'true' + env: + COMMITS: ${{ steps.pr_meta.outputs.commits }} + run: | + set -euo pipefail + + # Resolve a unique branch name. The counter handles the common + # case of re-running a backport; the push-retry below handles the + # rare race where two concurrent runs pick the same name. + git fetch --prune origin + # Fetch the PR's original commits so they are available locally + # regardless of how the PR was merged (squash, rebase, merge commit). + git fetch origin "refs/pull/${PR_NUMBER}/head" + BASE_BRANCH="backport/pr-${PR_NUMBER}-to-${TARGET_BRANCH}" + BACKPORT_BRANCH="${BASE_BRANCH}" + counter=1 + while git ls-remote --exit-code --heads origin "${BACKPORT_BRANCH}" > /dev/null 2>&1; do + counter=$((counter + 1)) + BACKPORT_BRANCH="${BASE_BRANCH}-${counter}" + done + echo "backport_branch=${BACKPORT_BRANCH}" >> "$GITHUB_OUTPUT" + + git fetch origin "${TARGET_BRANCH}" + git checkout -b "${BACKPORT_BRANCH}" "origin/${TARGET_BRANCH}" + + cherry_pick_failed=false + failed_sha="" + for sha in $COMMITS; do + echo "Cherry-picking ${sha} ..." + + # Detect merge commits (more than one parent) and cherry-pick + # relative to the first parent with -m 1. + parent_count=$(git cat-file -p "${sha}" | grep -c '^parent ' || true) + if [ "${parent_count}" -gt 1 ]; then + echo " Merge commit detected, using -m 1" + cherry_flags="-m 1" + else + cherry_flags="" + fi + + # --empty=drop silently skips commits already applied to the + # target branch rather than recording a no-op empty commit. + if ! git cherry-pick --empty=drop -x ${cherry_flags} "${sha}"; then + cherry_pick_failed=true + failed_sha="${sha}" + git cherry-pick --abort 2>/dev/null || true + break + fi + done + + echo "cherry_pick_failed=${cherry_pick_failed}" >> "$GITHUB_OUTPUT" + echo "failed_sha=${failed_sha}" >> "$GITHUB_OUTPUT" + + if [ "${cherry_pick_failed}" = "false" ]; then + # If every commit was already present in the target branch, + # cherry-pick dropped them all and HEAD hasn't moved. + new_commits=$(git rev-list --count "origin/${TARGET_BRANCH}..HEAD") + if [ "${new_commits}" -eq 0 ]; then + echo "nothing_to_backport=true" >> "$GITHUB_OUTPUT" + else + echo "nothing_to_backport=false" >> "$GITHUB_OUTPUT" + # Push; on a naming collision from a concurrent run, fall back + # to a name that includes the unique run ID. + if ! git push origin "${BACKPORT_BRANCH}" 2>/dev/null; then + BACKPORT_BRANCH="${BASE_BRANCH}-${GITHUB_RUN_ID}" + git branch -m "${BACKPORT_BRANCH}" + git push origin "${BACKPORT_BRANCH}" + echo "backport_branch=${BACKPORT_BRANCH}" >> "$GITHUB_OUTPUT" + fi + fi + fi + + # All commits were already present in the target branch — no PR needed. + - name: Comment when nothing to backport + if: steps.cherry_pick.outputs.nothing_to_backport == 'true' + uses: actions/github-script@v8 + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: Number(process.env.PR_NUMBER), + body: `â„šī¸ All commits from this PR are already present in \`${process.env.TARGET_BRANCH}\` — no backport needed.`, + }); + core.notice(`Nothing to backport to ${process.env.TARGET_BRANCH} — all commits already present.`); + + # Open a PR against the target branch and attach the target:* label. + - name: Create backport PR + if: >- + steps.cherry_pick.outputs.cherry_pick_failed == 'false' && + steps.cherry_pick.outputs.nothing_to_backport == 'false' + uses: actions/github-script@v8 + env: + ORIGINAL_TITLE: ${{ steps.pr_meta.outputs.title }} + ORIGINAL_BODY: ${{ steps.pr_meta.outputs.body }} + BACKPORT_BRANCH: ${{ steps.cherry_pick.outputs.backport_branch }} + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const prNumber = Number(process.env.PR_NUMBER); + const targetBranch = process.env.TARGET_BRANCH; + const labelName = `target:${targetBranch}`; + + // Ensure the target:* label exists in this repo. + try { + await github.rest.issues.getLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName, + }); + } catch (err) { + if (err.status === 404) { + try { + await github.rest.issues.createLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: labelName, + color: '0075ca', + description: `Backport targeting the ${targetBranch} branch`, + }); + } catch (createErr) { + // 422 = another concurrent job created the label first; safe to ignore. + if (createErr.status !== 422) throw createErr; + } + } else { + throw err; + } + } + + const title = `[${targetBranch}] ${process.env.ORIGINAL_TITLE}`; + const body = [ + `Backport of #${prNumber} to \`${targetBranch}\`.`, + '', + '---', + '', + process.env.ORIGINAL_BODY, + ].join('\n'); + + const { data: newPR } = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title, + body, + head: process.env.BACKPORT_BRANCH, + base: targetBranch, + }); + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: newPR.number, + labels: [labelName], + }); + + core.notice(`Opened backport PR #${newPR.number}: ${newPR.html_url}`); + + # If cherry-pick failed, leave a comment on the original PR so a + # developer knows to create the backport manually. + - name: Comment on cherry-pick failure + if: steps.cherry_pick.outputs.cherry_pick_failed == 'true' + uses: actions/github-script@v8 + env: + FAILED_SHA: ${{ steps.cherry_pick.outputs.failed_sha }} + with: + github-token: ${{ steps.app-token.outputs.token }} + script: | + const prNumber = Number(process.env.PR_NUMBER); + const targetBranch = process.env.TARGET_BRANCH; + const failedSha = process.env.FAILED_SHA; + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body: [ + `âš ī¸ **Automatic backport to \`${targetBranch}\` failed.**`, + '', + `Cherry-pick of commit ${failedSha} produced conflicts.`, + 'Please create the backport manually:', + '', + '```bash', + `git fetch origin ${targetBranch}`, + `git checkout -b backport/pr-${prNumber}-to-${targetBranch} origin/${targetBranch}`, + `git cherry-pick -x `, + `git push origin backport/pr-${prNumber}-to-${targetBranch}`, + '```', + ].join('\n'), + }); + + core.warning(`Cherry-pick to ${targetBranch} failed at ${failedSha} — manual backport required.`); diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000000..d6b7a123565 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,409 @@ +# +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# +# CodeQL (GitHub code scanning / static analysis) configuration for +# Open MPI. +# +# What this workflow does: +# +# 1. Runs CodeQL analysis on every pull request and on every push +# (i.e., merge) to main and the supported release branches. This +# catches newly-introduced problems at the moment code changes. +# +# 2. Runs a periodic (weekly, Monday morning UTC) CodeQL scan of main +# *and* the active release branches. +# +# Why run periodic scans in addition to the per-PR / per-merge scans? +# +# The PR/merge scans only ever examine code at the moment it changes. +# But CodeQL's query packs and analysis engine are continually +# updated as new classes of vulnerabilities (and new CVEs) are +# discovered. The weekly scan re-analyzes the *existing* code base +# with the latest queries, so a vulnerability pattern that was +# unknown when a piece of code was merged can still be found later -- +# even though that code has not changed and therefore would never be +# re-examined by a push/PR scan. Release branches in particular tend +# to go quiet between point releases, so the periodic scan is often +# the only thing that keeps their results current. +# +# A note on branches and where this file must live: +# +# - push and pull_request workflows always run from the copy of this +# file on the branch receiving the push / targeted by the PR (not +# from main). So to get PR and merge analysis on a release branch +# (e.g., v5.0.x, v6.0.x), this file must also be committed on that +# branch. +# +# - The schedule (cron) event is special: it only ever fires from the +# default branch (main). To periodically scan the release branches +# as well, the scheduled job below explicitly checks out each +# branch and tells the analyze action which ref/sha to attribute +# the results to. The scheduled job is therefore inert on the +# release branches (it never fires there); only the copy on main +# drives the weekly scans. +# +# Why mostly inline (and not a composite action or reusable workflow): +# the two jobs below intentionally duplicate the init / build / analyze +# steps. +# Because per-branch CI requires this file to be cherry-picked onto each +# release branch anyway, keeping the workflow logic here is simpler than +# factoring the shared steps out. Sharing them would not buy a +# cross-branch single source of truth, and local workflow/action paths +# would actually break the weekly scan, since they resolve from the +# checked-out branch rather than from main. The CodeQL config sidecar is +# the exception: the scheduled job uses CodeQL's remote config-file +# syntax to load the sidecar file from main, so the config stays +# single-sourced while the analyzed worktree is a release branch. +# +# We started running CodeQL on the main, v5.0.x, and v6.0.x branches in +# June 2026. Subsequent release branches are matched by the branch +# globs below (for PR/merge scans) and should be added to the scheduled +# job's branch list on main (for the weekly scans). + +name: "CodeQL Advanced" + +on: + push: + # Defined once here (&scan_branches) and reused by pull_request + # below via a YAML alias, so the two trigger lists cannot drift. + branches: &scan_branches + - main + - 'v[5-9].*.x' # Matches v5.0.x through v9.9.x + - 'v[1-9][0-9]+.*.x' # Matches v10.0.x and higher (double digits+) + pull_request: + branches: *scan_branches + schedule: + # Monday morning (UTC). The off-the-hour minute avoids GitHub's + # top-of-the-hour scheduling congestion. + - cron: '32 5 * * 1' + workflow_dispatch: + +permissions: + # required for all workflows + security-events: write + # required to fetch internal or private CodeQL packs + packages: read + # only required for workflows in private repositories + actions: read + contents: read + +# Cancel a superseded in-progress run when a newer commit is pushed to +# the same pull request, to avoid stacking up expensive C/C++ builds. +# The group includes the event name so push/schedule runs live in +# separate groups, and cancellation is enabled only for pull_request +# runs -- never the weekly scheduled scan or pushes to a branch. +concurrency: + group: ${{ github.workflow }}-${{ github.event_name }}-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + # ------------------------------------------------------------------- + # Event-driven analysis: scans the ref that triggered the push / PR. + # ------------------------------------------------------------------- + analyze: + if: >- + github.event_name != 'schedule' && + github.event_name != 'workflow_dispatch' && + github.repository == 'open-mpi/ompi' + name: Analyze ${{ matrix.language }} (${{ github.ref_name }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - language: actions + build-mode: none + - language: c-cpp + build-mode: manual + - language: python + build-mode: none + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + submodules: recursive + persist-credentials: false + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + config-file: ./.github/codeql/codeql-config.yml + + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + export AUTOMAKE_JOBS=$(nproc) + ./autogen.pl + ./configure + make -j $(nproc) + + - name: Perform CodeQL Analysis + if: matrix.language != 'c-cpp' + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{ matrix.language }}" + + # CodeQL's paths-ignore setting does not filter C/C++ code that is + # compiled by a manual build. Open MPI's normal build compiles + # bundled third-party projects, which gives CodeQL complete build + # context but can also produce alerts in code we do not maintain. + # For C/C++ only, write SARIF locally, remove alerts whose primary + # locations are under 3rd-party/, and then upload the filtered SARIF. + - name: Perform CodeQL Analysis + if: matrix.language == 'c-cpp' + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{ matrix.language }}" + output: c-cpp-sarif-results + upload: failure-only + + - name: Locate C/C++ SARIF + if: matrix.language == 'c-cpp' + id: cpp_sarif + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + sarif_files=(c-cpp-sarif-results/*.sarif) + if test ${#sarif_files[@]} -ne 1; then + echo "::error::Expected one C/C++ SARIF file, found ${#sarif_files[@]}" + exit 1 + fi + echo "file=${sarif_files[0]}" >> "$GITHUB_OUTPUT" + + - name: Filter third-party C/C++ SARIF results + if: matrix.language == 'c-cpp' + uses: advanced-security/filter-sarif@v1 + with: + patterns: | + -3rd-party/** + -**/3rd-party/** + input: ${{ steps.cpp_sarif.outputs.file }} + output: ${{ steps.cpp_sarif.outputs.file }} + + - name: Upload filtered C/C++ SARIF + if: matrix.language == 'c-cpp' + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: ${{ steps.cpp_sarif.outputs.file }} + category: "/language:${{ matrix.language }}" + + # ------------------------------------------------------------------- + # Scheduled/manual analysis: explicitly scans main and each release + # branch. The schedule only fires from the default branch (main); see + # the header comment. workflow_dispatch intentionally runs this same + # matrix so the scheduled path can be tested without waiting for the + # next cron event. + # ------------------------------------------------------------------- + analyze-scheduled: + if: >- + (github.event_name == 'schedule' || + github.event_name == 'workflow_dispatch') && + github.repository == 'open-mpi/ompi' + name: Analyze ${{ matrix.language }} (${{ matrix.branch }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + # Concrete branch names only: this matrix drives + # 'actions/checkout', so (unlike the on.push.branches globs) + # these cannot be globs or regexes. New release branches must be + # added here, too; the check-scheduled-coverage job enforces it. + branch: + - main + - v5.0.x + - v6.0.x + language: + - actions + - c-cpp + - python + include: + - language: actions + build-mode: none + - language: c-cpp + build-mode: manual + - language: python + build-mode: none + steps: + - name: Checkout ${{ matrix.branch }} + uses: actions/checkout@v6 + with: + ref: ${{ matrix.branch }} + submodules: recursive + persist-credentials: false + + - name: Resolve commit SHA + id: commit + shell: bash + run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT" + + - name: Initialize CodeQL + uses: github/codeql-action/init@v4 + with: + languages: ${{ matrix.language }} + build-mode: ${{ matrix.build-mode }} + config-file: >- + ${{ github.repository }}/.github/codeql/codeql-config.yml@${{ github.event.repository.default_branch }} + + - name: Run manual build steps + if: matrix.build-mode == 'manual' + shell: bash + run: | + export AUTOMAKE_JOBS=$(nproc) + ./autogen.pl + ./configure + make -j $(nproc) + + - name: Perform CodeQL Analysis + if: matrix.language != 'c-cpp' + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{ matrix.language }}" + # Attribute results to the checked-out branch rather than to + # main (which is what a schedule event would otherwise report + # against). The category is keyed on language only -- NOT on + # branch -- so each branch's results update in place and the + # weekly main scan stays consistent with its push/PR scans. + ref: refs/heads/${{ matrix.branch }} + sha: ${{ steps.commit.outputs.sha }} + + # CodeQL's paths-ignore setting does not filter C/C++ code that is + # compiled by a manual build. Open MPI's normal build compiles + # bundled third-party projects, which gives CodeQL complete build + # context but can also produce alerts in code we do not maintain. + # For C/C++ only, write SARIF locally, remove alerts whose primary + # locations are under 3rd-party/, and then upload the filtered SARIF. + - name: Perform CodeQL Analysis + if: matrix.language == 'c-cpp' + uses: github/codeql-action/analyze@v4 + with: + category: "/language:${{ matrix.language }}" + output: c-cpp-sarif-results + upload: failure-only + # Attribute results to the checked-out branch rather than to + # main (which is what a schedule event would otherwise report + # against). The category is keyed on language only -- NOT on + # branch -- so each branch's results update in place and the + # weekly main scan stays consistent with its push/PR scans. + ref: refs/heads/${{ matrix.branch }} + sha: ${{ steps.commit.outputs.sha }} + + - name: Locate C/C++ SARIF + if: matrix.language == 'c-cpp' + id: cpp_sarif + shell: bash + run: | + set -euo pipefail + shopt -s nullglob + sarif_files=(c-cpp-sarif-results/*.sarif) + if test ${#sarif_files[@]} -ne 1; then + echo "::error::Expected one C/C++ SARIF file, found ${#sarif_files[@]}" + exit 1 + fi + echo "file=${sarif_files[0]}" >> "$GITHUB_OUTPUT" + + - name: Filter third-party C/C++ SARIF results + if: matrix.language == 'c-cpp' + uses: advanced-security/filter-sarif@v1 + with: + patterns: | + -3rd-party/** + -**/3rd-party/** + input: ${{ steps.cpp_sarif.outputs.file }} + output: ${{ steps.cpp_sarif.outputs.file }} + + - name: Upload filtered C/C++ SARIF + if: matrix.language == 'c-cpp' + uses: github/codeql-action/upload-sarif@v4 + with: + sarif_file: ${{ steps.cpp_sarif.outputs.file }} + category: "/language:${{ matrix.language }}" + # Match the scheduled analyze attribution above when uploading + # the filtered SARIF. + ref: refs/heads/${{ matrix.branch }} + sha: ${{ steps.commit.outputs.sha }} + + # ------------------------------------------------------------------- + # Guard: fail if a release branch that the push/pull_request globs + # cover is missing from the analyze-scheduled 'branch:' list, so new + # release branches don't silently drop out of the weekly scan. + # + # Runs only in the default-branch (main) context, where the scheduled + # matrix is authoritative. On a release branch the matrix is inert + # (the schedule fires only from the default branch) and its copy can + # lag main's, so running there would block CI for drift it can't fix. + # ------------------------------------------------------------------- + check-scheduled-coverage: + if: >- + github.event_name == 'push' && + github.ref_name == github.event.repository.default_branch && + github.repository == 'open-mpi/ompi' + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + persist-credentials: false + + - name: Verify periodic-scan branch coverage + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + # The push/PR trigger globs in on.push.branches (above) are the + # single definition of which branches get scanned. This guard + # reads them from this file and converts each GitHub filter-glob + # to a regex (. -> \., * -> .*; +, ?, and [..] mean the same in + # both), so it matches exactly what the triggers match -- there + # is no second copy of the pattern to keep in sync. It then + # checks that every such branch is also listed in the + # analyze-scheduled matrix. python3 is always present on the + # runner, so (unlike yq) a runner image change cannot break it. + gh api --paginate "repos/${{ github.repository }}/branches" \ + -q '.[].name' > "$RUNNER_TEMP/branches.txt" + + python3 - .github/workflows/codeql.yml "$RUNNER_TEMP/branches.txt" <<'PY' + import sys, re + + wf = open(sys.argv[1]).read() + branches = [b.strip() for b in open(sys.argv[2]) if b.strip()] + + # on.push.branches globs -> anchored ERE regexes. Capture the + # whole push block (tolerates comments and the &scan_branches + # anchor on the branches: line) and pull out its list items. + push = re.search(r"(?ms)^ push:\n(.*?)^ \S", wf) + raw = re.findall(r"(?m)^ - (\S.*?) *(?:#.*)?$", push.group(1)) if push else [] + globs = [x.strip().strip("'") for x in raw] + triggers = [re.compile("^" + g.replace(".", r"\.").replace("*", ".*") + "$") + for g in globs] + if not triggers: + sys.exit("::error::could not parse on.push.branches; " + "coverage guard cannot run") + + # analyze-scheduled matrix branch list. + sched = re.search(r"(?ms)^ branch:\n(.*?)^ \S", wf) + scheduled = set(re.findall(r"^ - (\S+)", sched.group(1), re.M)) if sched else set() + + missing = [b for b in branches + if any(rx.match(b) for rx in triggers) and b not in scheduled] + + if missing: + print("::error::Branch(es) get push/PR CodeQL scans but are missing " + "from the weekly scan list: " + " ".join(missing)) + print("Add them to jobs.analyze-scheduled.strategy.matrix.branch " + "in .github/workflows/codeql.yml.") + sys.exit(1) + print("OK: every push/PR-scanned branch is covered by the weekly scan.") + PY diff --git a/.github/workflows/compile-cuda.yaml b/.github/workflows/compile-cuda.yaml index 9ba44b4c2b9..44d726cf9cd 100644 --- a/.github/workflows/compile-cuda.yaml +++ b/.github/workflows/compile-cuda.yaml @@ -21,11 +21,12 @@ jobs: sudo dpkg -i cuda-keyring_1.1-1_all.deb sudo apt update sudo apt install -y cuda-toolkit - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: recursive - name: Build Open MPI run: | + export AUTOMAKE_JOBS=$(nproc) ./autogen.pl ./configure --prefix=${PWD}/install --with-cuda=${CUDA_PATH} --with-cuda-libdir=${CUDA_PATH}/lib64/stubs --disable-silent-rules make -j diff --git a/.github/workflows/compile-examples.yaml b/.github/workflows/compile-examples.yaml deleted file mode 100644 index aab1ed6e5ef..00000000000 --- a/.github/workflows/compile-examples.yaml +++ /dev/null @@ -1,22 +0,0 @@ -name: Compile ignored components - -on: [pull_request] - -permissions: - contents: read - -jobs: - compile-ignored: - runs-on: ubuntu-24.04 - steps: - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Remove .opal_ignore files so that we build all examples - run: | - find . -name .opal_ignore -exec rm -f {} \; -print - - name: Build Open MPI - run: | - ./autogen.pl - ./configure --prefix=${PWD}/install --disable-silent-rules - make -j diff --git a/.github/workflows/compile-rocm.yaml b/.github/workflows/compile-rocm.yaml index 804f2e4ce48..fad4956bdad 100644 --- a/.github/workflows/compile-rocm.yaml +++ b/.github/workflows/compile-rocm.yaml @@ -19,11 +19,12 @@ jobs: wget https://repo.radeon.com/amdgpu-install/7.2/ubuntu/jammy/amdgpu-install_7.2.70200-1_all.deb sudo apt install -y ./amdgpu-install_7.2.70200-1_all.deb sudo amdgpu-install --usecase=rocmdev - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: recursive - name: Build Open MPI run: | + export AUTOMAKE_JOBS=$(nproc) ./autogen.pl ./configure --prefix=${PWD}/install --with-rocm=/opt/rocm --disable-mpi-fortran --disable-silent-rules LD_LIBRARY_PATH=/opt/rocm/lib make -j diff --git a/.github/workflows/compile-ze.yaml b/.github/workflows/compile-ze.yaml index b8fb08097e9..bc6760e941e 100644 --- a/.github/workflows/compile-ze.yaml +++ b/.github/workflows/compile-ze.yaml @@ -23,11 +23,12 @@ jobs: cd build cmake ../ -DCMAKE_INSTALL_PREFIX=/opt/ze sudo make -j1 install - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: recursive - name: Build Open MPI (VPATH) run: | + export AUTOMAKE_JOBS=$(nproc) ./autogen.pl mkdir build cd build diff --git a/.github/workflows/hdf5-tests.yaml b/.github/workflows/hdf5-tests.yaml index 5dc7e88cd4c..4ff6c543107 100644 --- a/.github/workflows/hdf5-tests.yaml +++ b/.github/workflows/hdf5-tests.yaml @@ -13,11 +13,12 @@ jobs: run: | sudo apt update sudo apt install -y --no-install-recommends wget - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: submodules: recursive - name: Build Open MPI run: | + export AUTOMAKE_JOBS=$(nproc) ./autogen.pl ./configure --prefix=/opt/openmpi --with-pmix=internal --with-prrte=internal --with-hwloc=internal --with-libevent=internal --disable-mpi-fortran --disable-oshmem make -j 8 && make install diff --git a/.github/workflows/macos-checks.yaml b/.github/workflows/macos-checks.yaml deleted file mode 100644 index 8b18c95e4ca..00000000000 --- a/.github/workflows/macos-checks.yaml +++ /dev/null @@ -1,49 +0,0 @@ -name: macOS - -on: [pull_request] - -permissions: - contents: read - -jobs: - macOS: - runs-on: macos-latest - steps: - - name: Setup macOS - run: | - # Copied from mpi4py/mpi-publish - # create gfortran symlink - cd $(brew --prefix)/bin - gfortran=$(ls gfortran-* | sort | head -n 1) - sudo ln -s $gfortran gfortran - # install autotools - brew install autoconf - brew install automake - brew install libtool - # unlink libevent - brew unlink libevent || true - - uses: actions/checkout@v4 - with: - submodules: recursive - - name: Build Open MPI - run: | - ./autogen.pl - ./configure --prefix=/opt/openmpi --disable-silent-rules - make -j $(sysctl -n hw.logicalcpu) - - name: Run unit tests - run: | - make check - - name: Install Open MPI - run: | - sudo make install - - name: Add Open MPI to PATH - run: echo /opt/openmpi/bin >> $GITHUB_PATH - - name: Build examples - run: | - pushd examples - make - popd - - name: Test ring and sessions sanity check - run: | - mpirun --map-by ppr:1:core examples/ring_c - mpirun --map-by ppr:1:core examples/hello_sessions_c diff --git a/.github/workflows/ompi-pr-builds.yaml b/.github/workflows/ompi-pr-builds.yaml new file mode 100644 index 00000000000..354a4a49c04 --- /dev/null +++ b/.github/workflows/ompi-pr-builds.yaml @@ -0,0 +1,116 @@ +name: Build everything, make check, and run examples (Linux and macOS) + +# Build and test Open MPI on both Linux and macOS: +# +# * Remove all .opal_ignore files so that even the normally-skipped +# components are compiled. +# * Force the Sphinx docs and the Fortran bindings to be built +# (--enable-sphinx / --enable-mpi-fortran), installing the Python +# packages Sphinx needs. +# * Build, then "make check" (intentionally before "make install"), +# then install. +# * Build and run all of the examples in the examples/ directory. + +on: + pull_request: + workflow_dispatch: + +permissions: + contents: read + +jobs: + ompi-pr-builds: + strategy: + # Run both platforms to completion even if one fails. + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v6 + with: + submodules: recursive + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3 + + - name: Install Python packages (Sphinx and friends) + run: python -m pip install --upgrade pip -r docs/requirements.txt + + - name: Set up macOS build dependencies + if: runner.os == 'macOS' + run: | + # Create a plain "gfortran" symlink (Homebrew installs it as + # gfortran-NN). + cd $(brew --prefix)/bin + gfortran=$(ls gfortran-* | sort | head -n 1) + sudo ln -s $gfortran gfortran + # Autotools + brew install autoconf automake libtool + # Keep Homebrew's libevent from being picked up by configure + brew unlink libevent || true + + - name: Set up Linux build dependencies + if: runner.os == 'Linux' + run: | + sudo apt-get update + sudo apt-get install -y --no-install-recommends \ + autoconf automake libtool flex gfortran + + - name: Remove .opal_ignore files so that we build all components + run: | + find . -name .opal_ignore -exec rm -f {} \; -print + + - name: Build Open MPI (forcing Sphinx docs and Fortran bindings) + run: | + NCPU=$(nproc 2>/dev/null || sysctl -n hw.logicalcpu) + # Parallelize automake's Makefile.in generation during autogen.pl. + export AUTOMAKE_JOBS=$NCPU + ./autogen.pl + ./configure --prefix=${PWD}/install \ + --enable-sphinx \ + --enable-mpi-fortran \ + --disable-silent-rules + make -j $NCPU + + - name: Run unit tests + # Deliberately run "make check" BEFORE "make install". + # VERBOSE=1 causes logs to be cat'ed if a test fails. + run: make check VERBOSE=1 + + - name: Install Open MPI + run: make install + + - name: Add Open MPI to PATH + run: echo "${PWD}/install/bin" >> "$GITHUB_PATH" + + - name: Build all examples + run: | + cd examples + make + + - name: Run all built examples + run: | + cd examples + status=0 + # Run every example binary that was actually built. Built + # binaries are regular, executable, and have no filename + # extension (this skips sources, Makefiles, and macOS .dSYM + # bundles). OpenSHMEM examples are launched with oshrun; all + # others with mpirun. + for exe in *; do + [ -f "$exe" ] && [ -x "$exe" ] || continue + case "$exe" in *.*) continue ;; esac + case "$exe" in + *oshmem*|oshmem_*) launcher=oshrun ;; + *) launcher=mpirun ;; + esac + echo "==== Running $exe (via $launcher) ====" + if ! "$launcher" --map-by ppr:1:core "./$exe"; then + echo "==== $exe FAILED" + status=1 + fi + done + exit $status diff --git a/.github/workflows/ompi_mpi4py.yaml b/.github/workflows/ompi_mpi4py.yaml index 29abbcaf70d..c341b9fc00b 100644 --- a/.github/workflows/ompi_mpi4py.yaml +++ b/.github/workflows/ompi_mpi4py.yaml @@ -35,13 +35,13 @@ jobs: if: ${{ runner.os == 'Linux' }} - name: Checkout Open MPI - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: path: mpi-build submodules: recursive - name: Bootstrap Open MPI - run: ./autogen.pl + run: AUTOMAKE_JOBS=$(nproc) ./autogen.pl working-directory: mpi-build # Install into a separate directory (/opt/openmpi) so that we can @@ -105,7 +105,7 @@ jobs: numpy cffi pyyaml - name: Checkout mpi4py - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ inputs.repository || 'mpi4py/mpi4py' }} ref: ${{ inputs.ref }} diff --git a/.github/workflows/ompi_mpi4py_asan.yaml b/.github/workflows/ompi_mpi4py_asan.yaml index 240e3d2f101..ad5b89bf098 100644 --- a/.github/workflows/ompi_mpi4py_asan.yaml +++ b/.github/workflows/ompi_mpi4py_asan.yaml @@ -42,13 +42,13 @@ jobs: if: ${{ runner.os == 'Linux' }} - name: Checkout Open MPI - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: path: mpi-build submodules: recursive - name: Bootstrap Open MPI - run: ./autogen.pl + run: AUTOMAKE_JOBS=$(nproc) ./autogen.pl working-directory: mpi-build # Install into a separate directory (/opt/openmpi) so that we can @@ -106,7 +106,7 @@ jobs: numpy cffi pyyaml - name: Checkout mpi4py - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: ${{ inputs.repository || 'mpi4py/mpi4py' }} ref: ${{ inputs.ref }} diff --git a/.github/workflows/ompi_nvidia.yaml b/.github/workflows/ompi_nvidia.yaml index 8d550fad6cd..b587ea56562 100644 --- a/.github/workflows/ompi_nvidia.yaml +++ b/.github/workflows/ompi_nvidia.yaml @@ -14,11 +14,11 @@ jobs: runs-on: [self-hosted, linux, x64, nvidia] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive - name: Checkout CI scripts - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: repository: Mellanox/jenkins_scripts path: ompi_ci diff --git a/.github/workflows/riscv64-qemu-test.yaml b/.github/workflows/riscv64-qemu-test.yaml index 56a6c741704..a36b4ad08c3 100644 --- a/.github/workflows/riscv64-qemu-test.yaml +++ b/.github/workflows/riscv64-qemu-test.yaml @@ -20,12 +20,13 @@ jobs: sed -i "s|libdir='/mnt/riscv/riscv64-unknown-linux-gnu/lib'|libdir='/opt/riscv/riscv64-unknown-linux-gnu/lib'|g" /opt/riscv/riscv64-unknown-linux-gnu/lib/libatomic.la - name: Checkout Open MPI - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: recursive - name: Bootstrap Open MPI run: | + export AUTOMAKE_JOBS=$(nproc) ./autogen.pl sed -i '/^func_exec_program_core ()/,/^}/ s/\/exec qemu-riscv64 -cpu rv64,v=true,vext_spec=v1.0,vlen=128 -L \/opt\/riscv\/sysroot/g' config/ltmain.sh @@ -65,6 +66,6 @@ jobs: export QEMU_LD_PREFIX=/opt/riscv/sysroot export PATH="/opt/riscv/bin:$PATH" export LD_LIBRARY_PATH="/opt/riscv/lib:$LD_LIBRARY_PATH" - cd test/datatype + cd ompi/test/datatype qemu-riscv64 -cpu rv64,v=true,vext_spec=v1.0,vlen=128 /opt/riscv/bin/ompi_info --param all all --level 9 | grep "MCA op " qemu-riscv64 -cpu rv64,v=true,vext_spec=v1.0,vlen=128 /opt/riscv/bin/mpirun -n 1 ./reduce_local -l 1024 -u 1024 diff --git a/.gitignore b/.gitignore index b30321da7ca..86ab1392aec 100644 --- a/.gitignore +++ b/.gitignore @@ -228,13 +228,65 @@ ompi/mpi/fortran/use-mpi-tkr/mpi_kinds.ompi_module ompi/mpi/fortran/use-mpi-tkr/mpi-tkr-sizeof.f90 ompi/mpi/fortran/use-mpi-tkr/mpi-tkr-sizeof.h -ompi/mpi/java/java/mpi -ompi/mpi/java/java/*.jar -ompi/mpi/java/java/*.h -ompi/mpi/java/java/doc - ompi/mpi/tool/profile/*.c +ompi/test/t/mpi_t_category_get_events + +ompi/test/file/file_info_defaults +ompi/test/file/file_info_hints +ompi/test/file/file_info_memkind +ompi/test/file/file_info_set_view + +ompi/test/datatype/checksum +ompi/test/datatype/ddt_pack +ompi/test/datatype/ddt_raw +ompi/test/datatype/ddt_raw2 +ompi/test/datatype/ddt_test +ompi/test/datatype/external32 +ompi/test/datatype/large_data +ompi/test/datatype/mpi_datatype_bigcount +ompi/test/datatype/ompi_datatype_bigcount +ompi/test/datatype/partial +ompi/test/datatype/position +ompi/test/datatype/position_noncontig +ompi/test/datatype/reduce_local +ompi/test/datatype/to_self +ompi/test/datatype/unpack_ooo + +ompi/test/general/attr_interlang_mpifh +ompi/test/general/attr_interlang_mpifh.f90 +ompi/test/general/attr_interlang_usempi +ompi/test/general/attr_interlang_usempi.f90 +ompi/test/general/attr_interlang_usempif08 +ompi/test/general/attr_interlang_usempif08.f90 +ompi/test/general/attribute +ompi/test/general/communicator +ompi/test/general/datatype +ompi/test/general/errhandler +ompi/test/general/file +ompi/test/general/group +ompi/test/general/info_mpi +ompi/test/general/info_t +ompi/test/general/instance +ompi/test/general/message +ompi/test/general/mpi_init +ompi/test/general/op +ompi/test/general/ompi_file_test.dat +ompi/test/general/proc +ompi/test/general/request +ompi/test/general/seq_tracker +ompi/test/general/win +ompi/test/general/win_notify +ompi/test/general/win_notify_unsupported + +ompi/test/monitoring/monitoring_test +ompi/test/monitoring/check_monitoring +ompi/test/monitoring/example_reduce_count +ompi/test/monitoring/test_overhead +ompi/test/monitoring/test_pvar_access + +ompi/test/spc/spc_test + ompi/mpiext/affinity/c/example ompi/mpiext/ftmpi/c/profile/pcomm_agree.c @@ -277,7 +329,6 @@ ompi/tools/wrappers/ompi.pc ompi/tools/wrappers/ompi-c.pc ompi/tools/wrappers/ompi-cxx.pc ompi/tools/wrappers/ompi-fort.pc -ompi/tools/wrappers/mpijavac.pl ompi/tools/wrappers/mpicxx-wrapper-data.txt ompi/tools/wrappers/mpif77-wrapper-data.txt ompi/tools/wrappers/mpif90-wrapper-data.txt @@ -317,15 +368,13 @@ opal/tools/wrappers/opal.pc opal/util/show_help_content.c opal/util/keyval/keyval_lex.c -test/monitoring/aggregate_profile.pl -test/monitoring/profile2mat.pl - test/simple/abort test/simple/accept test/simple/attach test/simple/bad_exit test/simple/bcast_loop test/simple/binding +test/simple/comm_abort test/simple/concurrent_spawn test/simple/connect test/simple/crisscross @@ -336,6 +385,7 @@ test/simple/hello_output test/simple/hello_show_help test/simple/hello test/simple/hello++ +test/simple/initial_errh test/simple/intercomm1 test/simple/interlib test/simple/loop_child @@ -344,6 +394,7 @@ test/simple/mpi_barrier test/simple/mpi_no_op test/simple/mpi_spin test/simple/multi_abort +test/simple/ompio_file_info test/simple/parallel_r8 test/simple/parallel_r64 test/simple/parallel_w8 @@ -436,7 +487,7 @@ test/class/*.txt test/class/ompi_bitmap_test_out.txt test/class/ompi_circular_buffer_fifo test/class/ompi_fifo -test/class/ompi_rb_tree +test/class/opal_rb_tree test/class/ompi_bitmap test/class/opal_bitmap test/class/opal_fifo @@ -448,30 +499,21 @@ test/class/opal_pointer_array test/class/opal_proc_table test/class/opal_tree test/class/opal_value_array +test/class/opal_graph +test/class/opal_hotel +test/class/opal_interval_tree +test/class/opal_ring_buffer -test/datatype/ddt_test -test/datatype/ddt_pack -test/datatype/external32 -test/datatype/to_self -test/datatype/checksum -test/datatype/position -test/datatype/ddt_raw test/datatype/opal_datatype_test -test/datatype/position_noncontig -test/datatype/unpack_ooo +test/datatype/opal_datatype_bigcount +test/datatype/opal_ddt_api test/datatype/unpack_hetero test/event/signal-test test/event/event-test test/event/time-test -test/monitoring/monitoring_test -test/monitoring/check_monitoring -test/monitoring/example_reduce_count -test/monitoring/test_overhead -test/monitoring/test_pvar_access - -test/mpi/environment/chello +test/mpool/mpool_memkind test/runtime/parse_context test/runtime/sigchld @@ -479,10 +521,9 @@ test/runtime/start_shut test/runtime/opal_init_finalize test/runtime/orte_init_finalize -test/spc/spc_test - test/threads/opal_condition test/threads/opal_thread +test/threads/opal_atomic_thread_bench test/util/aaa test/util/test_session_dir_out @@ -497,7 +538,7 @@ test/util/orte_session_dir test/util/orte_sys_info test/util/orte_universe_setup_file_io test/util/opal_basename -test/util/ompi_numtostr +test/util/opal_numtostr test/util/ompi_pack test/util/test-file test/util/opal_sos @@ -506,6 +547,29 @@ test/util/opal_path_nfs.out test/util/opal_bit_ops test/util/bipartite_graph test/util/opal_sha256 +test/util/opal_json +test/util/opal_alfg +test/util/opal_arch +test/util/opal_cmd_line +test/util/opal_crc +test/util/opal_environ +test/util/opal_fd +test/util/opal_few +test/util/opal_getcwd +test/util/opal_info +test/util/opal_info_subscriber +test/util/opal_keyval_parse +test/util/opal_malloc +test/util/opal_net +test/util/opal_os_dirpath +test/util/opal_output +test/util/opal_path +test/util/opal_printf +test/util/opal_proc +test/util/opal_qsort +test/util/opal_string_copy +test/util/opal_sys_limits +test/util/opal_uri opal/test/reachable/reachable_netlink opal/test/reachable/reachable_weighted @@ -517,6 +581,10 @@ docs/_static docs/_static/css/custom.css docs/_templates docs/man-openmpi/man3/bindings +docs/llms-build +docs/llms-build.stamp +docs/llms-det-a +docs/llms-det-b docs/*.inv # Common Python virtual environment and cache directory names @@ -533,6 +601,9 @@ docs/schizo-ompi-rst-content docs/html docs/man +# other doc RST files to ignore as they are generated +docs/code-of-conduct.rst + # Generated C Bindings ompi/mpi/c/*_generated*.c diff --git a/.mailmap b/.mailmap index 0b2e1684f6d..86031b5c04d 100644 --- a/.mailmap +++ b/.mailmap @@ -139,3 +139,6 @@ Brian Barrett Andrii Bilokur B-a-S Kento Hasegawa hasegawa.kento + +Brelle Emmanuel Brelle Emmanuel +Brelle Emmanuel Brelle Emmanuel diff --git a/.readthedocs-pre-create-environment.sh b/.readthedocs-pre-create-environment.sh index cc461ac2b13..7b141f36df3 100755 --- a/.readthedocs-pre-create-environment.sh +++ b/.readthedocs-pre-create-environment.sh @@ -29,3 +29,13 @@ cp -rp $PRRTE_RST_SRC_DIR $PRRTE_RST_TARGET_DIR cd docs python3 ./generate-mpi-man3-bindings.py --srcdir . --builddir . +python3 ./generate-code-of-conduct-rst.py --input ../.github/CODE_OF_CONDUCT.md --output code-of-conduct.rst + +# Generate the LLM-friendly artifacts into the build-tree staging directory +# (must run after the man3 bindings, whose generated RST it include-expands). +# A "build-finished" hook in conf.py copies the staging tree into the HTML +# output during the sphinx-build that Read the Docs runs. The URL base for +# the generated links is taken from READTHEDOCS_CANONICAL_URL, so they use the +# correct version slug (e.g. .../en/main/). +python3 ./generate-llm-docs.py --srcdir . --builddir . + diff --git a/3rd-party/openpmix b/3rd-party/openpmix index 53fce423d5d..cb89026c747 160000 --- a/3rd-party/openpmix +++ b/3rd-party/openpmix @@ -1 +1 @@ -Subproject commit 53fce423d5d6b25798ed1f32837671dc55d0230d +Subproject commit cb89026c747e8540692eed29b3880e67b7ba9aa2 diff --git a/3rd-party/prrte b/3rd-party/prrte index 2d9b0aaaeea..a2d2b039a4f 160000 --- a/3rd-party/prrte +++ b/3rd-party/prrte @@ -1 +1 @@ -Subproject commit 2d9b0aaaeea49a0e7850aed95e5ace9340c7d847 +Subproject commit a2d2b039a4f0de9af2b36be90283591c82cdc794 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..7a99923298a --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,369 @@ + + +# AGENTS.md + +Guidance for AI coding agents (and the humans driving them) working in +the Open MPI source tree. This file is an *orientation map*, not the +full rulebook: the authoritative, human-maintained documentation lives +under [`docs/developers/`](docs/developers/) and +[`docs/contributing.rst`](docs/contributing.rst) (rendered at +). When this file and those docs disagree, +**the docs win** — and please fix this file. + +AI-assisted contributions are welcome. But Open MPI runs on the largest +supercomputers in the world and across a huge range of operating +systems and hardware. We want careful, portable, performant code — not +plausible-looking code that solves one problem in one environment at the +expense of others. Hold yourself to the same bar as a thoughtful human +contributor. + +## What Open MPI is + +Open MPI is an open source implementation of the [Message Passing +Interface (MPI) specification](https://www.mpi-forum.org/docs/) — a +high-level library for sending discrete, typed messages between +processes, independent of the underlying network or OS. It also +includes a run-time system that launches and manages the lifecycle of +many processes across many hosts as a single MPI "job". + +## The mental model: three projects + +The code base is divided into three *projects*, which are strict +abstraction barriers — each compiles to its own library with a one-way +dependency order: + +``` +OSHMEM (liboshmem) OpenSHMEM API layer + │ depends on +OMPI (libmpi) MPI API layer + language bindings + │ depends on +OPAL (libopen-pal) portability layer (OS/arch abstractions) +``` + +- **OPAL** — portability primitives. Symbols prefixed `opal_` / `OPAL_`. + This is where most OS/arch `#if` blocks belong. +- **OMPI** — everything the MPI standard mandates: the language bindings + (C and several Fortran flavors) on top, MCA frameworks + underneath. Symbols prefixed `ompi_` / `OMPI_`; only *official* MPI + symbols get `MPI_` / `mpi_`. +- **OSHMEM** — the OpenSHMEM API layer; sibling to OMPI, changes slowly. + Symbols prefixed `oshmem_` / `OSHMEM_`. + +**Linker boundary (a real, hard error if you violate it):** code in a +lower layer *cannot* directly call functions in a higher layer. OPAL +cannot call OMPI or OSHMEM; OMPI cannot call OSHMEM. The legal way for a +lower layer to reach upward is a **callback function pointer** handed +down from the higher layer. Direct upward calls fail to resolve at link +time. + +## MCA: the Modular Component Architecture + +OPAL, OMPI, and OSHMEM are built almost entirely out of MCA plugins. +Read [`docs/developers/terminology.rst`](docs/developers/terminology.rst) +and the MCA section it points to before doing real work. The hierarchy: + +- **Project** → **Framework** → **Component** → **Module** (runtime + instance, like a C++ object). Each level is isolated from its + siblings; a framework exposes a top-level header for its public API. +- Example: `opal/mca/btl/` is the BTL framework in OPAL; + `opal/mca/btl/tcp/` is one component within it. +- **MCA parameters** let users change behavior at run time (env var, + file, CLI). **Prefer adding an MCA parameter over hard-coding a + constant** — this is idiomatic and expected here. + +## Golden rules (the things agents most often get wrong) + +From [`docs/developers/source-code.rst`](docs/developers/source-code.rst) +and [`docs/contributing.rst`](docs/contributing.rst): + +- **Prefix rule.** Filenames are prefixed `_`. + Public symbols in a component are prefixed + `__` (`` ∈ `mca`, `opal`, + `ompi`, `oshmem`). Non-public symbols must be `static` or otherwise + kept out of global scope. When in doubt, add the prefix. +- **Include `_config.h` first** — `opal_config.h`, + `ompi_config.h`, or `oshmem_config.h` for the layer you're in — as the + very first `#include`, before any system header. +- **Use the `__opal_attribute_*__` macros for compiler attributes.** + [`opal/include/opal_config_bottom.h`](opal/include/opal_config_bottom.h), + pulled in transitively by `opal_config.h`, defines portable wrappers — + `__opal_attribute_unused__`, `__opal_attribute_noreturn__`, + `__opal_attribute_format__`, `__opal_attribute_deprecated__`, and many + more — that expand to the appropriate `__attribute__((...))` on + compilers that support it and to nothing elsewhere. Reach for these + (for example, to mark an unused function parameter) rather than writing + a bare `__attribute__` or leaving a warning unaddressed. +- **MPI back-end code must never call public `MPI_*()` APIs.** The + bindings are thin wrappers; call the internal `ompi_*` routines, not + the user-facing entry points. +- **New files need the standard copyright/license header.** Copy the + multi-institution BSD header block — including the `$COPYRIGHT$` and + `$HEADER$` tokens — from a neighboring file. If you substantially + change an existing file, add your copyright line to its block. +- **`#define` logical macros to `0` or `1`; never `#undef` them.** Test + with `#if FOO`, not `#ifdef FOO`, so a misspelling is a compiler + error, not a silent false. +- **Put constants on the left** of equality tests: `if (NULL == ptr)`. +- **Always brace blocks**, even one-liners. **4-space indents, never a + literal tab character**, in any language. +- C11 is required (Open MPI â‰Ĩ 6.0): C++-style `//` comments and C99 + mixed declarations are allowed and preferred. Fortran has no formal + style — match the surrounding code. +- **Stay compiler-warning-free.** Open MPI strives to build with zero + compiler warnings. Do not introduce code that adds new warnings. +- **Ignore new build products; never commit them.** If your change makes + the build emit something new — a test binary, a generated source or + header, a wrapper data file — add it to the top-level + [`.gitignore`](.gitignore), in the block for its directory, matching + the surrounding path style (repo-root-relative, no leading slash). + Open MPI keeps essentially all ignores in that one file; per-directory + `.gitignore` files are a rare exception, so don't create one. After a + clean build, `git status` must be clean — an untracked generated file + means you missed an entry, and committing the artifact itself is never + the fix. + +## Generated code: edit the source, not the output + +The MPI C/Fortran bindings are **generated at build time** by the Python +generator under [`ompi/mpi/bindings/`](ompi/mpi/bindings/) +(`bindings.py` + `ompi_bindings/`), driven in part by official MPI +symbol/signature data pulled from the MPI Forum's `pympistandard`. If +you need to change a binding's behavior, change the **generator, +templates, or the back-end implementation** — never the emitted `.c`/`.h` +files. + +## Do NOT hand-edit + +- **`3rd-party/` and the git submodules** (embedded OpenPMIx, and the + Open MPI *fork* of PRRTE). Fixes belong upstream, not patched in here. +- **Autotools-generated output** — `configure`, `Makefile.in`, + `config.status`, anything produced by `./autogen.pl`. Edit + `configure.ac`, `Makefile.am`, or the m4 in [`config/`](config/) + instead. +- **Generated MPI bindings** — see the section above. +- **Pre-rendered docs** — shipped HTML and generated man pages. Edit the + RST sources under [`docs/`](docs/). + +## Build and test + +Open MPI uses the GNU Autotools (Autoconf / Automake / Libtool). From a +Git clone: + +```sh +./autogen.pl # regenerate the build system (one-time / after build-system changes) +./configure --prefix=/path/to/install +make -j # full builds are SLOW +make install +``` + +Out-of-tree (VPATH) builds are not required, but they are often helpful +for sanity checks because they avoid perturbing the source tree: + +```sh +mkdir build && cd build +../configure --prefix=/path/to/install +make -j +``` + +See [`docs/developers/building-open-mpi.rst`](docs/developers/building-open-mpi.rst) +and the [install docs](docs/installing-open-mpi/) for options. + +## Modifying the configure / build system + +Editing the build system means regenerating it — `make` alone can't, +and trying will wedge the tree. If you change `configure.ac` or any +`config/*.m4` file (including the embedded oac/Autotools macros), the +change does not take effect until the build system is regenerated. Do +not rely on a plain `make`: Open MPI builds in maintainer mode, so +`make` auto-triggers a partial in-tree Autotools regeneration that +frequently fails (e.g., unexpanded `OAC_*` macros, `config.status` +errors) and can leave the tree half-regenerated and +unbuildable. Instead, regenerate and reconfigure explicitly: + +```sh +./autogen.pl +./configure +make -j +``` + +Recover the original configure invocation options from the existing +tree with `./config.status --config` (or read the header of +`config.log`). This process is slow but mandatory after any +build-system source change — there is no safe shortcut. + +Note that editing `Makefile.am` files do *not* require the full +`autogen.pl` + `./configure` process. A simple `make` will regenerate +the relevant `Makefile[.in]` files and then complete the build +successfully. + +**"Did I break it?" — layered:** + +1. **Build cleanly.** A clean `make` after your change is the baseline. + Open MPI is highly configurable at build time: many components, + source files, directories, and generated artifacts are selected or + omitted by `configure` and Automake based on the local environment. + For any change to code or documentation that might be conditionally + built, verify that your configured build is actually compiling or + generating the thing you changed; do not assume this can be checked + only by looking for `#if` blocks. +2. **Documentation-only changes can be narrower.** If the change is + wholly under [`docs/`](docs/), it is often enough to configure with + Sphinx support and run `make` in the `docs/` build directory instead + of doing a full build, install, smoke test, or `make check`. Make + sure Sphinx was really enabled by `configure`; one practical check is + that `SPHINX_BUILD` in `config.status` names a valid executable. +3. **Quick smoke test.** After `make install`, put your `--prefix`'s + `bin/` on your `PATH`, then build and run an example on the local + host: + + ```sh + cd examples && make # compiles against the installed mpicc/mpifort wrappers + mpirun --np 2 ./hello_c # smallest launch + MPI_Init/Finalize sanity check + mpirun --np 2 ./ring_c # adds real point-to-point messaging + ``` +4. **Deeper validation** when your environment supports it: `make check` + and the programs under [`test/`](test/). Be aware that the full suite + and realistic MPI jobs frequently need a proper launcher and/or + multiple hosts/specialized hardware — **do not assume you can run all + of it locally, and don't report untested code as verified.** + +**Test across environments when you can.** Portability across a wide +variety of environments is a core Open MPI goal. The primary development +environments are common Linux distributions and macOS, but the code is +expected to run far more widely. When container-based tooling is +available, it can be a practical way to reproduce, diagnose, and test +user-space behavior specific to an environment you aren't running +natively — for example, using Docker on macOS to exercise Linux +user-space code paths. (Containers don't replace real +network/hardware/launcher testing, but they're useful for OS and +user-space differences.) + +**Add tests for new code.** Whenever practical, add unit tests under +[`test/`](test/) that are wired into `make check` (and therefore run in +CI). Prefer a `make check`-able test over a manual one-off so the +coverage sticks and regressions are caught automatically. + +**Never bend a test to accommodate a bug.** Do not weaken, skip, or +rewrite an existing test — and do not craft a new one — merely to make +buggy behavior pass. Tests encode intended behavior: when one fails, the +default assumption is that the code is wrong, not the test. If you find a +genuine bug in the code base, identify it, report it, and where +appropriate fix it — don't paper over it in the test suite. + +## Performance discipline + +Performance is paramount: short-message **latency** and large-message +**bandwidth** are headline metrics, along with the ability to offload +work to networking/GPU hardware so the CPU can make progress +elsewhere. Microseconds — sometimes nanoseconds — matter, and much of +the hot path uses OS-bypass techniques talking directly to network, +CUDA, and ROCm hardware. + +Concrete rules for hot paths: + +- **Don't add allocations, locks, or branches to the critical + send/receive path** without a clear, measured justification. +- **Guard debug output and expensive assertions behind + `OPAL_ENABLE_DEBUG`** so release builds pay nothing for them. +- **Prefer an MCA parameter** to a hard-coded constant when a value + might need tuning per environment. +- Keep environment-specific optimization where it belongs — generally in + OPAL or in the hardware-specific component — not smeared across + portable MPI logic. + +## Working in a shared repository + +Don't assume you're the only agent (or person) using this clone. In +particular, if you're working in a **git worktree**, other worktrees may +be active against the same underlying repository at the same time. Avoid +repo-wide git commands that reach outside your own working area and can +disrupt others — for example, `git worktree prune`, or `git stash` +(which writes to the repository-wide stash ref shared by all worktrees). +Keep your git operations scoped to your own branch and worktree. + +As a narrow exception, creating a **new branch** when you need to park +work in progress (for example, instead of `git stash`) is fine. Just be +careful not to collide with branches that other agents or people may be +using in the same clone — pick a clearly-scoped, unlikely-to-clash name. + +## Contributing + +Authoritative process: +[`docs/contributing.rst`](docs/contributing.rst). Highlights agents must +honor: + +- **Sign off every commit.** Each commit needs a `Signed-off-by:` line + per the Contributor's Declaration — use `git commit -s`. Commits + without it are not accepted. This applies to AI-assisted work too: the + human submitter certifies the contribution. +- **Commit messages:** a short first line saying *what* changed, then a + body explaining *why*. Open MPI does **not** use Conventional Commits + (`feat:`/`fix:` prefixes) — write prose. Don't add AI tooling + attribution. Wrap commit-message lines at around 75 characters. +- **Keep incidental fixes as their own commits.** Small "drive-by" bug + fixes you notice while working on something else are welcome, but it is + usually best to land them as standalone commits, separate from your + main change, so each can be evaluated and reviewed on its own. One + logical change per commit keeps history reviewable and easy to bisect. +- **Branch flow:** land on `main` first via a GitHub pull request, then + cherry-pick to the relevant release branch(es) `vMAJOR.MINOR.x` with a + `(cherry picked from commit ...)` line at the end of the commit + message; use `git cherry-pick -x` to add it. Open MPI always lands + commits on `main` and release branches through pull requests; never + push directly to those branches. Never commit features directly to a + release branch. See + [`docs/developers/git-github.rst`](docs/developers/git-github.rst). +- **Update the docs and the changelog** when user-visible behavior + changes: RST under [`docs/`](docs/), and a release-notes entry under + [`docs/release-notes/changelog/`](docs/release-notes/changelog/) + (`vMAJOR.MINOR.x.rst`). +- **In user-facing docs, the package's name is "Open MPI."** When + referring to this software package as a whole in user-facing + documentation, always write the formal name **Open MPI** — never the + abbreviation "OMPI". ("OMPI" is correct only as the internal name of + the middle project layer (OPAL → OMPI → OSHMEM) and its `ompi_` / + `OMPI_` symbol prefix — never as a public-facing name for the + package.) +- **Keep the LLM-friendly docs in sync.** The machine-readable MPI API + artifacts (catalog, corpora, per-symbol pages, manifest) regenerate + automatically from the man-page RST and binding metadata, so they + cannot drift. The *curated* sources under + [`docs/llms-src/`](docs/llms-src/) (the interface guide, examples, and + the `ompi_info` runtime-introspection guide) can: when a PR changes + public MPI documentation, update the affected curated files when + relevant. If you change the curated examples or the + JSON Schemas, regenerate + `specs/llms-friendly-docs/sample-records.jsonl` so `make check` + (which validates the artifacts) still passes. See + [`docs/developers/llm-friendly-docs.rst`](docs/developers/llm-friendly-docs.rst). + +## Repository map + +| Path | What's there | +|------|--------------| +| `opal/` | OPAL portability layer (`opal/mca/` = its frameworks) | +| `ompi/` | OMPI / MPI layer; `ompi/mpi/` = language bindings, `ompi/mca/` = frameworks | +| `oshmem/` | OpenSHMEM layer | +| `3rd-party/` | embedded upstreams + submodules (OpenPMIx, PRRTE fork) — don't hand-edit | +| `config/` | m4 macros for Autoconf / Automake / Libtool | +| `docs/` | all RST documentation (Sphinx); `docs/developers/` is the dev guide | +| `examples/` | small MPI example programs (good smoke tests) | +| `test/` | unit / functional tests | +| `contrib/` | unsupported contributed scripts and tools | + +## When in doubt + +- Match the surrounding code's style and conventions — this is an old, + multi-author code base with established patterns. +- Read the relevant [`docs/developers/`](docs/developers/) page before + inventing a new pattern. +- Ask on the developer mailing list / a GitHub issue for anything large + before writing it; see [`docs/contributing.rst`](docs/contributing.rst). diff --git a/CLAUDE.md b/CLAUDE.md new file mode 120000 index 00000000000..47dc3e3d863 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1 @@ +AGENTS.md \ No newline at end of file diff --git a/Doxyfile b/Doxyfile deleted file mode 100644 index e8c88d33574..00000000000 --- a/Doxyfile +++ /dev/null @@ -1,1099 +0,0 @@ -# Doxyfile 1.3.4 -# -# Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana -# University Research and Technology -# Corporation. All rights reserved. -# Copyright (c) 2004-2005 The University of Tennessee and The University -# of Tennessee Research Foundation. All rights -# reserved. -# Copyright (c) 2004-2005 High Performance Computing Center Stuttgart, -# University of Stuttgart. All rights reserved. -# Copyright (c) 2004-2005 The Regents of the University of California. -# All rights reserved. -# $COPYRIGHT$ -# -# Additional copyrights may follow -# -# $HEADER$ -# - -# This file describes the settings to be used by the documentation system -# doxygen (www.doxygen.org) for a project -# -# All text after a hash (#) is considered a comment and will be ignored -# The format is: -# TAG = value [value, ...] -# For lists items can also be appended using: -# TAG += value [value, ...] -# Values that contain spaces should be placed between quotes (" ") - -#--------------------------------------------------------------------------- -# Project related configuration options -#--------------------------------------------------------------------------- - -# The PROJECT_NAME tag is a single word (or a sequence of words surrounded -# by quotes) that should identify the project. - -PROJECT_NAME = Open MPI - -# The PROJECT_NUMBER tag can be used to enter a project or revision number. -# This could be handy for archiving the generated documentation or -# if some version control system is used. - -PROJECT_NUMBER = 0.1.1 - -# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) -# base path where the generated documentation will be put. -# If a relative path is entered, it will be relative to the location -# where doxygen was started. If left blank the current directory will be used. - -OUTPUT_DIRECTORY = doxygen - -# The OUTPUT_LANGUAGE tag is used to specify the language in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all constant output in the proper language. -# The default language is English, other supported languages are: -# Brazilian, Catalan, Chinese, Chinese-Traditional, Croatian, Czech, Danish, Dutch, -# Finnish, French, German, Greek, Hungarian, Italian, Japanese, Japanese-en -# (Japanese with English messages), Korean, Norwegian, Polish, Portuguese, -# Romanian, Russian, Serbian, Slovak, Slovene, Spanish, Swedish, and Ukrainian. - -OUTPUT_LANGUAGE = English - -# This tag can be used to specify the encoding used in the generated output. -# The encoding is not always determined by the language that is chosen, -# but also whether or not the output is meant for Windows or non-Windows users. -# In case there is a difference, setting the USE_WINDOWS_ENCODING tag to YES -# forces the Windows encoding (this is the default for the Windows binary), -# whereas setting the tag to NO uses a Unix-style encoding (the default for -# all platforms other than Windows). - -USE_WINDOWS_ENCODING = NO - -# If the BRIEF_MEMBER_DESC tag is set to YES (the default) Doxygen will -# include brief member descriptions after the members that are listed in -# the file and class documentation (similar to JavaDoc). -# Set to NO to disable this. - -BRIEF_MEMBER_DESC = YES - -# If the REPEAT_BRIEF tag is set to YES (the default) Doxygen will -# prepend the brief description of a member or function before the -# detailed description. Note: if both HIDE_UNDOC_MEMBERS and -# BRIEF_MEMBER_DESC are set to NO, the brief descriptions will be -# completely suppressed. - -REPEAT_BRIEF = YES - -# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then -# Doxygen will generate a detailed section even if there is only a brief -# description. - -ALWAYS_DETAILED_SEC = NO - -# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show -# all inherited members of a class in the documentation of that class -# as if those members were ordinary class members. Constructors, -# destructors and assignment operators of the base classes will not be -# shown. - -INLINE_INHERITED_MEMB = NO - -# If the FULL_PATH_NAMES tag is set to YES then Doxygen will prepend the full -# path before files name in the file list and in the header files. If set -# to NO the shortest path that makes the file name unique will be used. - -FULL_PATH_NAMES = YES - -# If the FULL_PATH_NAMES tag is set to YES then the STRIP_FROM_PATH tag -# can be used to strip a user-defined part of the path. Stripping is -# only done if one of the specified strings matches the left-hand part of -# the path. It is allowed to use relative paths in the argument list. - -STRIP_FROM_PATH = - -# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter -# (but less readable) file names. This can be useful is your file systems -# doesn't support long names like on DOS, Mac, or CD-ROM. - -SHORT_NAMES = NO - -# If the JAVADOC_AUTOBRIEF tag is set to YES then Doxygen -# will interpret the first line (until the first dot) of a JavaDoc-style -# comment as the brief description. If set to NO, the JavaDoc -# comments will behave just like the Qt-style comments (thus requiring an -# explict @brief command for a brief description. - -JAVADOC_AUTOBRIEF = YES - -# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make Doxygen -# treat a multi-line C++ special comment block (i.e. a block of //! or /// -# comments) as a brief description. This used to be the default behaviour. -# The new default is to treat a multi-line C++ comment block as a detailed -# description. Set this tag to YES if you prefer the old behaviour instead. - -MULTILINE_CPP_IS_BRIEF = NO - -# If the DETAILS_AT_TOP tag is set to YES then Doxygen -# will output the detailed description near the top, like JavaDoc. -# If set to NO, the detailed description appears after the member -# documentation. - -DETAILS_AT_TOP = NO - -# If the INHERIT_DOCS tag is set to YES (the default) then an undocumented -# member inherits the documentation from any documented member that it -# reimplements. - -INHERIT_DOCS = YES - -# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC -# tag is set to YES, then doxygen will reuse the documentation of the first -# member in the group (if any) for the other members of the group. By default -# all members of a group must be documented explicitly. - -DISTRIBUTE_GROUP_DOC = NO - -# The TAB_SIZE tag can be used to set the number of spaces in a tab. -# Doxygen uses this value to replace tabs by spaces in code fragments. - -TAB_SIZE = 8 - -# This tag can be used to specify a number of aliases that acts -# as commands in the documentation. An alias has the form "name=value". -# For example adding "sideeffect=\par Side Effects:\n" will allow you to -# put the command \sideeffect (or @sideeffect) in the documentation, which -# will result in a user-defined paragraph with heading "Side Effects:". -# You can put \n's in the value part of an alias to insert newlines. - -ALIASES = - -# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of -# C sources only. Doxygen will then generate output that is more -# tailored for C. For instance, some of the names that are used will -# be different. The list of all members will be omitted, etc. - -OPTIMIZE_OUTPUT_FOR_C = YES - -# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of -# Java sources only. Doxygen will then generate output that is more -# tailored for Java. For instance, namespaces will be presented as -# packages, qualified scopes will look different, etc. - -OPTIMIZE_OUTPUT_JAVA = NO - -# Set the SUBGROUPING tag to YES (the default) to allow class member groups of -# the same type (for instance a group of public functions) to be put as a -# subgroup of that type (e.g. under the Public Functions section). Set it to -# NO to prevent subgrouping. Alternatively, this can be done per class using -# the \nosubgrouping command. - -SUBGROUPING = YES - -#--------------------------------------------------------------------------- -# Build related configuration options -#--------------------------------------------------------------------------- - -# If the EXTRACT_ALL tag is set to YES doxygen will assume all entities in -# documentation are documented, even if no documentation was available. -# Private class members and static file members will be hidden unless -# the EXTRACT_PRIVATE and EXTRACT_STATIC tags are set to YES - -EXTRACT_ALL = NO - -# If the EXTRACT_PRIVATE tag is set to YES all private members of a -# class will be included in the documentation. - -EXTRACT_PRIVATE = NO - -# If the EXTRACT_STATIC tag is set to YES all static members of a file -# will be included in the documentation. - -EXTRACT_STATIC = YES - -# If the EXTRACT_LOCAL_CLASSES tag is set to YES classes (and structs) -# defined locally in source files will be included in the documentation. -# If set to NO only classes defined in header files are included. - -EXTRACT_LOCAL_CLASSES = YES - -# If the HIDE_UNDOC_MEMBERS tag is set to YES, Doxygen will hide all -# undocumented members of documented classes, files or namespaces. -# If set to NO (the default) these members will be included in the -# various overviews, but no documentation section is generated. -# This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_MEMBERS = NO - -# If the HIDE_UNDOC_CLASSES tag is set to YES, Doxygen will hide all -# undocumented classes that are normally visible in the class hierarchy. -# If set to NO (the default) these classes will be included in the various -# overviews. This option has no effect if EXTRACT_ALL is enabled. - -HIDE_UNDOC_CLASSES = NO - -# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, Doxygen will hide all -# friend (class|struct|union) declarations. -# If set to NO (the default) these declarations will be included in the -# documentation. - -HIDE_FRIEND_COMPOUNDS = NO - -# If the HIDE_IN_BODY_DOCS tag is set to YES, Doxygen will hide any -# documentation blocks found inside the body of a function. -# If set to NO (the default) these blocks will be appended to the -# function's detailed documentation block. - -HIDE_IN_BODY_DOCS = NO - -# The INTERNAL_DOCS tag determines if documentation -# that is typed after a \internal command is included. If the tag is set -# to NO (the default) then the documentation will be excluded. -# Set it to YES to include the internal documentation. - -INTERNAL_DOCS = NO - -# If the CASE_SENSE_NAMES tag is set to NO then Doxygen will only generate -# file names in lower-case letters. If set to YES upper-case letters are also -# allowed. This is useful if you have classes or files whose names only differ -# in case and if your file system supports case sensitive file names. Windows -# users are advised to set this option to NO. - -CASE_SENSE_NAMES = YES - -# If the HIDE_SCOPE_NAMES tag is set to NO (the default) then Doxygen -# will show members with their full class and namespace scopes in the -# documentation. If set to YES the scope will be hidden. - -HIDE_SCOPE_NAMES = NO - -# If the SHOW_INCLUDE_FILES tag is set to YES (the default) then Doxygen -# will put a list of the files that are included by a file in the documentation -# of that file. - -SHOW_INCLUDE_FILES = YES - -# If the INLINE_INFO tag is set to YES (the default) then a tag [inline] -# is inserted in the documentation for inline members. - -INLINE_INFO = YES - -# If the SORT_MEMBER_DOCS tag is set to YES (the default) then doxygen -# will sort the (detailed) documentation of file and class members -# alphabetically by member name. If set to NO the members will appear in -# declaration order. - -SORT_MEMBER_DOCS = YES - -# The GENERATE_TODOLIST tag can be used to enable (YES) or -# disable (NO) the todo list. This list is created by putting \todo -# commands in the documentation. - -GENERATE_TODOLIST = YES - -# The GENERATE_TESTLIST tag can be used to enable (YES) or -# disable (NO) the test list. This list is created by putting \test -# commands in the documentation. - -GENERATE_TESTLIST = YES - -# The GENERATE_BUGLIST tag can be used to enable (YES) or -# disable (NO) the bug list. This list is created by putting \bug -# commands in the documentation. - -GENERATE_BUGLIST = YES - -# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or -# disable (NO) the deprecated list. This list is created by putting -# \deprecated commands in the documentation. - -GENERATE_DEPRECATEDLIST= YES - -# The ENABLED_SECTIONS tag can be used to enable conditional -# documentation sections, marked by \if sectionname ... \endif. - -ENABLED_SECTIONS = - -# The MAX_INITIALIZER_LINES tag determines the maximum number of lines -# the initial value of a variable or define consists of for it to appear in -# the documentation. If the initializer consists of more lines than specified -# here it will be hidden. Use a value of 0 to hide initializers completely. -# The appearance of the initializer of individual variables and defines in the -# documentation can be controlled using \showinitializer or \hideinitializer -# command in the documentation regardless of this setting. - -MAX_INITIALIZER_LINES = 30 - -# Set the SHOW_USED_FILES tag to NO to disable the list of files generated -# at the bottom of the documentation of classes and structs. If set to YES the -# list will mention the files that were used to generate the documentation. - -SHOW_USED_FILES = YES - -#--------------------------------------------------------------------------- -# configuration options related to warning and progress messages -#--------------------------------------------------------------------------- - -# The QUIET tag can be used to turn on/off the messages that are generated -# by doxygen. Possible values are YES and NO. If left blank NO is used. - -QUIET = NO - -# The WARNINGS tag can be used to turn on/off the warning messages that are -# generated by doxygen. Possible values are YES and NO. If left blank -# NO is used. - -WARNINGS = YES - -# If WARN_IF_UNDOCUMENTED is set to YES, then doxygen will generate warnings -# for undocumented members. If EXTRACT_ALL is set to YES then this flag will -# automatically be disabled. - -WARN_IF_UNDOCUMENTED = YES - -# If WARN_IF_DOC_ERROR is set to YES, doxygen will generate warnings for -# potential errors in the documentation, such as not documenting some -# parameters in a documented function, or documenting parameters that -# don't exist or using markup commands wrongly. - -WARN_IF_DOC_ERROR = YES - -# The WARN_FORMAT tag determines the format of the warning messages that -# doxygen can produce. The string should contain the $file, $line, and $text -# tags, which will be replaced by the file and line number from which the -# warning originated and the warning text. - -WARN_FORMAT = "$file:$line: $text" - -# The WARN_LOGFILE tag can be used to specify a file to which warning -# and error messages should be written. If left blank the output is written -# to stderr. - -WARN_LOGFILE = - -#--------------------------------------------------------------------------- -# configuration options related to the input files -#--------------------------------------------------------------------------- - -# The INPUT tag can be used to specify the files and/or directories that contain -# documented source files. You may enter file names like "myfile.cpp" or -# directories like "/usr/src/myproject". Separate the files or directories -# with spaces. - -INPUT = opal orte ompi - -# If the value of the INPUT tag contains directories, you can use the -# FILE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank the following patterns are tested: -# *.c *.cc *.cxx *.cpp *.c++ *.java *.ii *.ixx *.ipp *.i++ *.inl *.h *.hh *.hxx *.hpp -# *.h++ *.idl *.odl *.cs *.php *.php3 *.inc - -FILE_PATTERNS = *.c *.h *.cc *.dox - -# The RECURSIVE tag can be used to turn specify whether or not subdirectories -# should be searched for input files as well. Possible values are YES and NO. -# If left blank NO is used. - -RECURSIVE = YES - -# The EXCLUDE tag can be used to specify files and/or directories that should -# excluded from the INPUT source files. This way you can easily exclude a -# subdirectory from a directory tree whose root is specified with the INPUT tag. - -EXCLUDE = opal/event - -# The EXCLUDE_SYMLINKS tag can be used select whether or not files or -# directories that are symbolic links (a Unix filesystem feature) are -# excluded from the input. - -EXCLUDE_SYMLINKS = YES - -# If the value of the INPUT tag contains directories, you can use the -# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude -# certain files from those directories. - -EXCLUDE_PATTERNS = static-modules.h *config*.h ompi_stdint.h ltdl.h - -# The EXAMPLE_PATH tag can be used to specify one or more files or -# directories that contain example code fragments that are included (see -# the \include command). - -EXAMPLE_PATH = - -# If the value of the EXAMPLE_PATH tag contains directories, you can use the -# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp -# and *.h) to filter out the source-files in the directories. If left -# blank all files are included. - -EXAMPLE_PATTERNS = - -# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be -# searched for input files to be used with the \include or \dontinclude -# commands irrespective of the value of the RECURSIVE tag. -# Possible values are YES and NO. If left blank NO is used. - -EXAMPLE_RECURSIVE = NO - -# The IMAGE_PATH tag can be used to specify one or more files or -# directories that contain image that are included in the documentation (see -# the \image command). - -IMAGE_PATH = - -# The INPUT_FILTER tag can be used to specify a program that doxygen should -# invoke to filter for each input file. Doxygen will invoke the filter program -# by executing (via popen()) the command , where -# is the value of the INPUT_FILTER tag, and is the name of an -# input file. Doxygen will then use the output that the filter program writes -# to standard output. - -INPUT_FILTER = - -# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using -# INPUT_FILTER) will be used to filter the input files when producing source -# files to browse (i.e. when SOURCE_BROWSER is set to YES). - -FILTER_SOURCE_FILES = NO - -#--------------------------------------------------------------------------- -# configuration options related to source browsing -#--------------------------------------------------------------------------- - -# If the SOURCE_BROWSER tag is set to YES then a list of source files will -# be generated. Documented entities will be cross-referenced with these sources. - -SOURCE_BROWSER = NO - -# Setting the INLINE_SOURCES tag to YES will include the body -# of functions and classes directly in the documentation. - -INLINE_SOURCES = NO - -# Setting the STRIP_CODE_COMMENTS tag to YES (the default) will instruct -# doxygen to hide any special comment blocks from generated source code -# fragments. Normal C and C++ comments will always remain visible. - -STRIP_CODE_COMMENTS = NO - -# If the REFERENCED_BY_RELATION tag is set to YES (the default) -# then for each documented function all documented -# functions referencing it will be listed. - -REFERENCED_BY_RELATION = YES - -# If the REFERENCES_RELATION tag is set to YES (the default) -# then for each documented function all documented entities -# called/used by that function will be listed. - -REFERENCES_RELATION = YES - -# If the VERBATIM_HEADERS tag is set to YES (the default) then Doxygen -# will generate a verbatim copy of the header file for each class for -# which an include is specified. Set to NO to disable this. - -VERBATIM_HEADERS = YES - -#--------------------------------------------------------------------------- -# configuration options related to the alphabetical class index -#--------------------------------------------------------------------------- - -# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index -# of all compounds will be generated. Enable this if the project -# contains a lot of classes, structs, unions or interfaces. - -ALPHABETICAL_INDEX = NO - -# If the alphabetical index is enabled (see ALPHABETICAL_INDEX) then -# the COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns -# in which this list will be split (can be a number in the range [1..20]) - -COLS_IN_ALPHA_INDEX = 5 - -# In case all classes in a project start with a common prefix, all -# classes will be put under the same header in the alphabetical index. -# The IGNORE_PREFIX tag can be used to specify one or more prefixes that -# should be ignored while generating the index headers. - -IGNORE_PREFIX = - -#--------------------------------------------------------------------------- -# configuration options related to the HTML output -#--------------------------------------------------------------------------- - -# If the GENERATE_HTML tag is set to YES (the default) Doxygen will -# generate HTML output. - -GENERATE_HTML = YES - -# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `html' will be used as the default path. - -HTML_OUTPUT = html - -# The HTML_FILE_EXTENSION tag can be used to specify the file extension for -# each generated HTML page (for example: .htm,.php,.asp). If it is left blank -# doxygen will generate files with .html extension. - -HTML_FILE_EXTENSION = .html - -# The HTML_HEADER tag can be used to specify a personal HTML header for -# each generated HTML page. If it is left blank doxygen will generate a -# standard header. - -HTML_HEADER = - -# The HTML_FOOTER tag can be used to specify a personal HTML footer for -# each generated HTML page. If it is left blank doxygen will generate a -# standard footer. - -HTML_FOOTER = - -# The HTML_STYLESHEET tag can be used to specify a user-defined cascading -# style sheet that is used by each HTML page. It can be used to -# fine-tune the look of the HTML output. If the tag is left blank doxygen -# will generate a default style sheet - -HTML_STYLESHEET = - -# If the HTML_ALIGN_MEMBERS tag is set to YES, the members of classes, -# files or namespaces will be aligned in HTML using tables. If set to -# NO a bullet list will be used. - -HTML_ALIGN_MEMBERS = YES - -# If the GENERATE_HTMLHELP tag is set to YES, additional index files -# will be generated that can be used as input for tools like the -# Microsoft HTML help workshop to generate a compressed HTML help file (.chm) -# of the generated HTML documentation. - -GENERATE_HTMLHELP = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the CHM_FILE tag can -# be used to specify the file name of the resulting .chm file. You -# can add a path in front of the file if the result should not be -# written to the html output dir. - -CHM_FILE = - -# If the GENERATE_HTMLHELP tag is set to YES, the HHC_LOCATION tag can -# be used to specify the location (absolute path including file name) of -# the HTML help compiler (hhc.exe). If non-empty doxygen will try to run -# the HTML help compiler on the generated index.hhp. - -HHC_LOCATION = - -# If the GENERATE_HTMLHELP tag is set to YES, the GENERATE_CHI flag -# controls if a separate .chi index file is generated (YES) or that -# it should be included in the master .chm file (NO). - -GENERATE_CHI = NO - -# If the GENERATE_HTMLHELP tag is set to YES, the BINARY_TOC flag -# controls whether a binary table of contents is generated (YES) or a -# normal table of contents (NO) in the .chm file. - -BINARY_TOC = NO - -# The TOC_EXPAND flag can be set to YES to add extra items for group members -# to the contents of the HTML help documentation and to the tree view. - -TOC_EXPAND = NO - -# The DISABLE_INDEX tag can be used to turn on/off the condensed index at -# top of each HTML page. The value NO (the default) enables the index and -# the value YES disables it. - -DISABLE_INDEX = NO - -# This tag can be used to set the number of enum values (range [1..20]) -# that doxygen will group on one line in the generated HTML documentation. - -ENUM_VALUES_PER_LINE = 4 - -# If the GENERATE_TREEVIEW tag is set to YES, a side panel will be -# generated containing a tree-like index structure (just like the one that -# is generated for HTML Help). For this to work a browser that supports -# JavaScript, DHTML, CSS and frames is required (for instance Mozilla 1.0+, -# Netscape 6.0+, Internet explorer 5.0+, or Konqueror). Windows users are -# probably better off using the HTML help feature. - -GENERATE_TREEVIEW = YES - -# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be -# used to set the initial width (in pixels) of the frame in which the tree -# is shown. - -TREEVIEW_WIDTH = 250 - -#--------------------------------------------------------------------------- -# configuration options related to the LaTeX output -#--------------------------------------------------------------------------- - -# If the GENERATE_LATEX tag is set to YES (the default) Doxygen will -# generate Latex output. - -GENERATE_LATEX = YES - -# The LATEX_OUTPUT tag is used to specify where the LaTeX docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `latex' will be used as the default path. - -LATEX_OUTPUT = latex - -# The LATEX_CMD_NAME tag can be used to specify the LaTeX command name to be -# invoked. If left blank `latex' will be used as the default command name. - -LATEX_CMD_NAME = latex - -# The MAKEINDEX_CMD_NAME tag can be used to specify the command name to -# generate index for LaTeX. If left blank `makeindex' will be used as the -# default command name. - -MAKEINDEX_CMD_NAME = makeindex - -# If the COMPACT_LATEX tag is set to YES Doxygen generates more compact -# LaTeX documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_LATEX = NO - -# The PAPER_TYPE tag can be used to set the paper type that is used -# by the printer. Possible values are: a4, a4wide, letter, legal and -# executive. If left blank a4wide will be used. - -#PAPER_TYPE = a4wide -PAPER_TYPE = letter - -# The EXTRA_PACKAGES tag can be to specify one or more names of LaTeX -# packages that should be included in the LaTeX output. - -EXTRA_PACKAGES = - -# The LATEX_HEADER tag can be used to specify a personal LaTeX header for -# the generated latex document. The header should contain everything until -# the first chapter. If it is left blank doxygen will generate a -# standard header. Notice: only use this tag if you know what you are doing! - -LATEX_HEADER = - -# If the PDF_HYPERLINKS tag is set to YES, the LaTeX that is generated -# is prepared for conversion to pdf (using ps2pdf). The pdf file will -# contain links (just like the HTML output) instead of page references -# This makes the output suitable for online browsing using a pdf viewer. - -PDF_HYPERLINKS = YES - -# If the USE_PDFLATEX tag is set to YES, pdflatex will be used instead of -# plain latex in the generated Makefile. Set this option to YES to get a -# higher quality PDF documentation. - -USE_PDFLATEX = YES - -# If the LATEX_BATCHMODE tag is set to YES, doxygen will add the \\batchmode. -# command to the generated LaTeX files. This will instruct LaTeX to keep -# running if errors occur, instead of asking the user for help. -# This option is also used when generating formulas in HTML. - -LATEX_BATCHMODE = NO - -# If LATEX_HIDE_INDICES is set to YES then doxygen will not -# include the index chapters (such as File Index, Compound Index, etc.) -# in the output. - -LATEX_HIDE_INDICES = NO - -#--------------------------------------------------------------------------- -# configuration options related to the RTF output -#--------------------------------------------------------------------------- - -# If the GENERATE_RTF tag is set to YES Doxygen will generate RTF output -# The RTF output is optimised for Word 97 and may not look very pretty with -# other RTF readers or editors. - -GENERATE_RTF = NO - -# The RTF_OUTPUT tag is used to specify where the RTF docs will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `rtf' will be used as the default path. - -RTF_OUTPUT = rtf - -# If the COMPACT_RTF tag is set to YES Doxygen generates more compact -# RTF documents. This may be useful for small projects and may help to -# save some trees in general. - -COMPACT_RTF = NO - -# If the RTF_HYPERLINKS tag is set to YES, the RTF that is generated -# will contain hyperlink fields. The RTF file will -# contain links (just like the HTML output) instead of page references. -# This makes the output suitable for online browsing using WORD or other -# programs which support those fields. -# Note: wordpad (write) and others do not support links. - -RTF_HYPERLINKS = NO - -# Load stylesheet definitions from file. Syntax is similar to doxygen's -# config file, i.e. a series of assigments. You only have to provide -# replacements, missing definitions are set to their default value. - -RTF_STYLESHEET_FILE = - -# Set optional variables used in the generation of an rtf document. -# Syntax is similar to doxygen's config file. - -RTF_EXTENSIONS_FILE = - -#--------------------------------------------------------------------------- -# configuration options related to the man page output -#--------------------------------------------------------------------------- - -# If the GENERATE_MAN tag is set to YES (the default) Doxygen will -# generate man pages - -GENERATE_MAN = YES - -# The MAN_OUTPUT tag is used to specify where the man pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `man' will be used as the default path. - -MAN_OUTPUT = man - -# The MAN_EXTENSION tag determines the extension that is added to -# the generated man pages (default is the subroutine's section .3) - -MAN_EXTENSION = .3 - -# If the MAN_LINKS tag is set to YES and Doxygen generates man output, -# then it will generate one additional man file for each entity -# documented in the real man page(s). These additional files -# only source the real man page, but without them the man command -# would be unable to find the correct page. The default is NO. - -MAN_LINKS = NO - -#--------------------------------------------------------------------------- -# configuration options related to the XML output -#--------------------------------------------------------------------------- - -# If the GENERATE_XML tag is set to YES Doxygen will -# generate an XML file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. - -GENERATE_XML = NO - -# The XML_OUTPUT tag is used to specify where the XML pages will be put. -# If a relative path is entered the value of OUTPUT_DIRECTORY will be -# put in front of it. If left blank `xml' will be used as the default path. - -XML_OUTPUT = xml - -# The XML_SCHEMA tag can be used to specify an XML schema, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_SCHEMA = - -# The XML_DTD tag can be used to specify an XML DTD, -# which can be used by a validating XML parser to check the -# syntax of the XML files. - -XML_DTD = - -#--------------------------------------------------------------------------- -# configuration options for the AutoGen Definitions output -#--------------------------------------------------------------------------- - -# If the GENERATE_AUTOGEN_DEF tag is set to YES Doxygen will -# generate an AutoGen Definitions (see autogen.sf.net) file -# that captures the structure of the code including all -# documentation. Note that this feature is still experimental -# and incomplete at the moment. - -GENERATE_AUTOGEN_DEF = NO - -#--------------------------------------------------------------------------- -# configuration options related to the Perl module output -#--------------------------------------------------------------------------- - -# If the GENERATE_PERLMOD tag is set to YES Doxygen will -# generate a Perl module file that captures the structure of -# the code including all documentation. Note that this -# feature is still experimental and incomplete at the -# moment. - -GENERATE_PERLMOD = NO - -# If the PERLMOD_LATEX tag is set to YES Doxygen will generate -# the necessary Makefile rules, Perl scripts and LaTeX code to be able -# to generate PDF and DVI output from the Perl module output. - -PERLMOD_LATEX = NO - -# If the PERLMOD_PRETTY tag is set to YES the Perl module output will be -# nicely formatted so it can be parsed by a human reader. This is useful -# if you want to understand what is going on. On the other hand, if this -# tag is set to NO the size of the Perl module output will be much smaller -# and Perl will parse it just the same. - -PERLMOD_PRETTY = YES - -# The names of the make variables in the generated doxyrules.make file -# are prefixed with the string contained in PERLMOD_MAKEVAR_PREFIX. -# This is useful so different doxyrules.make files included by the same -# Makefile don't overwrite each other's variables. - -PERLMOD_MAKEVAR_PREFIX = - -#--------------------------------------------------------------------------- -# Configuration options related to the preprocessor -#--------------------------------------------------------------------------- - -# If the ENABLE_PREPROCESSING tag is set to YES (the default) Doxygen will -# evaluate all C-preprocessor directives found in the sources and include -# files. - -ENABLE_PREPROCESSING = YES - -# If the MACRO_EXPANSION tag is set to YES Doxygen will expand all macro -# names in the source code. If set to NO (the default) only conditional -# compilation will be performed. Macro expansion can be done in a controlled -# way by setting EXPAND_ONLY_PREDEF to YES. - -MACRO_EXPANSION = NO - -# If the EXPAND_ONLY_PREDEF and MACRO_EXPANSION tags are both set to YES -# then the macro expansion is limited to the macros specified with the -# PREDEFINED and EXPAND_AS_PREDEFINED tags. - -EXPAND_ONLY_PREDEF = NO - -# If the SEARCH_INCLUDES tag is set to YES (the default) the includes files -# in the INCLUDE_PATH (see below) will be search if a #include is found. - -SEARCH_INCLUDES = YES - -# The INCLUDE_PATH tag can be used to specify one or more directories that -# contain include files that are not input files but should be processed by -# the preprocessor. - -INCLUDE_PATH = - -# You can use the INCLUDE_FILE_PATTERNS tag to specify one or more wildcard -# patterns (like *.h and *.hpp) to filter out the header-files in the -# directories. If left blank, the patterns specified with FILE_PATTERNS will -# be used. - -INCLUDE_FILE_PATTERNS = - -# The PREDEFINED tag can be used to specify one or more macro names that -# are defined before the preprocessor is started (similar to the -D option of -# gcc). The argument of the tag is a list of macros of the form: name -# or name=definition (no spaces). If the definition and the = are -# omitted =1 is assumed. - -PREDEFINED = DOXYGEN - -# If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then -# this tag can be used to specify a list of macro names that should be expanded. -# The macro definition that is found in the sources will be used. -# Use the PREDEFINED tag if you want to use a different macro definition. - -EXPAND_AS_DEFINED = - -# If the SKIP_FUNCTION_MACROS tag is set to YES (the default) then -# doxygen's preprocessor will remove all function-like macros that are alone -# on a line, have an all uppercase name, and do not end with a semicolon. Such -# function macros are typically used for boiler-plate code, and will confuse the -# parser if not removed. - -SKIP_FUNCTION_MACROS = YES - -#--------------------------------------------------------------------------- -# Configuration::addtions related to external references -#--------------------------------------------------------------------------- - -# The TAGFILES option can be used to specify one or more tagfiles. -# Optionally an initial location of the external documentation -# can be added for each tagfile. The format of a tag file without -# this location is as follows: -# TAGFILES = file1 file2 ... -# Adding location for the tag files is done as follows: -# TAGFILES = file1=loc1 "file2 = loc2" ... -# where "loc1" and "loc2" can be relative or absolute paths or -# URLs. If a location is present for each tag, the installdox tool -# does not have to be run to correct the links. -# Note that each tag file must have a unique name -# (where the name does NOT include the path) -# If a tag file is not located in the directory in which doxygen -# is run, you must also specify the path to the tagfile here. - -TAGFILES = - -# When a file name is specified after GENERATE_TAGFILE, doxygen will create -# a tag file that is based on the input files it reads. - -GENERATE_TAGFILE = doxygen/tagfile - -# If the ALLEXTERNALS tag is set to YES all external classes will be listed -# in the class index. If set to NO only the inherited external classes -# will be listed. - -ALLEXTERNALS = NO - -# If the EXTERNAL_GROUPS tag is set to YES all external groups will be listed -# in the modules index. If set to NO, only the current project's groups will -# be listed. - -EXTERNAL_GROUPS = YES - -# The PERL_PATH should be the absolute path and name of the perl script -# interpreter (i.e. the result of `which perl'). - -PERL_PATH = /usr/bin/perl - -#--------------------------------------------------------------------------- -# Configuration options related to the dot tool -#--------------------------------------------------------------------------- - -# If the CLASS_DIAGRAMS tag is set to YES (the default) Doxygen will -# generate a inheritance diagram (in HTML, RTF and LaTeX) for classes with base or -# super classes. Setting the tag to NO turns the diagrams off. Note that this -# option is superceded by the HAVE_DOT option below. This is only a fallback. It is -# recommended to install and use dot, since it yields more powerful graphs. - -CLASS_DIAGRAMS = YES - -# If set to YES, the inheritance and collaboration graphs will hide -# inheritance and usage relations if the target is undocumented -# or is not a class. - -HIDE_UNDOC_RELATIONS = YES - -# If you set the HAVE_DOT tag to YES then doxygen will assume the dot tool is -# available from the path. This tool is part of Graphviz, a graph visualization -# toolkit from AT&T and Lucent Bell Labs. The other options in this section -# have no effect if this option is set to NO (the default) - -HAVE_DOT = NO - -# If the CLASS_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect inheritance relations. Setting this tag to YES will force the -# the CLASS_DIAGRAMS tag to NO. - -CLASS_GRAPH = YES - -# If the COLLABORATION_GRAPH and HAVE_DOT tags are set to YES then doxygen -# will generate a graph for each documented class showing the direct and -# indirect implementation dependencies (inheritance, containment, and -# class references variables) of the class with other documented classes. - -COLLABORATION_GRAPH = YES - -# If the UML_LOOK tag is set to YES doxygen will generate inheritance and -# collaboration diagrams in a style similiar to the OMG's Unified Modeling -# Language. - -UML_LOOK = NO - -# If set to YES, the inheritance and collaboration graphs will show the -# relations between templates and their instances. - -TEMPLATE_RELATIONS = NO - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDE_GRAPH, and HAVE_DOT -# tags are set to YES then doxygen will generate a graph for each documented -# file showing the direct and indirect include dependencies of the file with -# other documented files. - -INCLUDE_GRAPH = YES - -# If the ENABLE_PREPROCESSING, SEARCH_INCLUDES, INCLUDED_BY_GRAPH, and -# HAVE_DOT tags are set to YES then doxygen will generate a graph for each -# documented header file showing the documented files that directly or -# indirectly include this file. - -INCLUDED_BY_GRAPH = YES - -# If the CALL_GRAPH and HAVE_DOT tags are set to YES then doxygen will -# generate a call dependency graph for every global function or class method. -# Note that enabling this option will significantly increase the time of a run. -# So in most cases it will be better to enable call graphs for selected -# functions only using the \callgraph command. - -CALL_GRAPH = NO - -# If the GRAPHICAL_HIERARCHY and HAVE_DOT tags are set to YES then doxygen -# will graphical hierarchy of all classes instead of a textual one. - -GRAPHICAL_HIERARCHY = YES - -# The DOT_IMAGE_FORMAT tag can be used to set the image format of the images -# generated by dot. Possible values are png, jpg, or gif -# If left blank png will be used. - -DOT_IMAGE_FORMAT = png - -# The tag DOT_PATH can be used to specify the path where the dot tool can be -# found. If left blank, it is assumed the dot tool can be found on the path. - -DOT_PATH = - -# The DOTFILE_DIRS tag can be used to specify one or more directories that -# contain dot files that are included in the documentation (see the -# \dotfile command). - -DOTFILE_DIRS = - -# The MAX_DOT_GRAPH_WIDTH tag can be used to set the maximum allowed width -# (in pixels) of the graphs generated by dot. If a graph becomes larger than -# this value, doxygen will try to truncate the graph, so that it fits within -# the specified constraint. Beware that most browsers cannot cope with very -# large images. - -MAX_DOT_GRAPH_WIDTH = 1024 - -# The MAX_DOT_GRAPH_HEIGHT tag can be used to set the maximum allows height -# (in pixels) of the graphs generated by dot. If a graph becomes larger than -# this value, doxygen will try to truncate the graph, so that it fits within -# the specified constraint. Beware that most browsers cannot cope with very -# large images. - -MAX_DOT_GRAPH_HEIGHT = 1024 - -# The MAX_DOT_GRAPH_DEPTH tag can be used to set the maximum depth of the -# graphs generated by dot. A depth value of 3 means that only nodes reachable -# from the root by following a path via at most 3 edges will be shown. Nodes that -# lay further from the root node will be omitted. Note that setting this option to -# 1 or 2 may greatly reduce the computation time needed for large code bases. Also -# note that a graph may be further truncated if the graph's image dimensions are -# not sufficient to fit the graph (see MAX_DOT_GRAPH_WIDTH and MAX_DOT_GRAPH_HEIGHT). -# If 0 is used for the depth value (the default), the graph is not depth-constrained. - -MAX_DOT_GRAPH_DEPTH = 0 - -# If the GENERATE_LEGEND tag is set to YES (the default) Doxygen will -# generate a legend page explaining the meaning of the various boxes and -# arrows in the dot generated graphs. - -GENERATE_LEGEND = YES - -# If the DOT_CLEANUP tag is set to YES (the default) Doxygen will -# remove the intermediate dot files that are used to generate -# the various graphs. - -DOT_CLEANUP = YES - -#--------------------------------------------------------------------------- -# Configuration::addtions related to the search engine -#--------------------------------------------------------------------------- - -# The SEARCHENGINE tag specifies whether or not a search engine should be -# used. If set to NO the values of all tags below this one will be ignored. - -SEARCHENGINE = NO diff --git a/Makefile.am b/Makefile.am index bff3c79c64f..49c6090a485 100644 --- a/Makefile.am +++ b/Makefile.am @@ -15,6 +15,7 @@ # Copyright (c) 2017-2022 Amazon.com, Inc. or its affiliates. All Rights reserved. # All Rights reserved. # Copyright (c) 2020 IBM Corporation. All rights reserved. +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -27,7 +28,7 @@ # be required. SUBDIRS = config contrib 3rd-party $(MCA_PROJECT_SUBDIRS) test docs DIST_SUBDIRS = config contrib 3rd-party $(MCA_PROJECT_DIST_SUBDIRS) test docs -EXTRA_DIST = README.md VERSION Doxyfile LICENSE autogen.pl AUTHORS +EXTRA_DIST = README.md VERSION LICENSE autogen.pl AUTHORS include examples/Makefile.include diff --git a/Makefile.mca-dso-check b/Makefile.mca-dso-check new file mode 100644 index 00000000000..0a40afe2252 --- /dev/null +++ b/Makefile.mca-dso-check @@ -0,0 +1,93 @@ +# -*- makefile -*- +# +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# +# Shared Automake fragment for "make check" unit tests that initialize +# OPAL (via opal_init() / opal_init_util()) or MPI (via MPI_Init(), +# which additionally pulls in OMPI-level frameworks such as pml). +# +# When Open MPI is configured with --enable-mca-dso, MCA components are +# built as standalone DSOs that are dlopen'ed at run time. In an +# installed tree they live in $libdir/openmpi; but "make check" runs +# before "make install", so at test time they are scattered through the +# build tree under libtool ".libs" directories. Two things are needed +# to make such tests work in an uninstalled --enable-mca-dso tree: +# +# 1. Point the Open MPI and PMIx MCA component search paths at the +# build-tree ".libs" directories that hold the freshly-built +# component DSOs, so opal_init() / MPI_Init() can find them. A +# single Open MPI MCA search path (opal_mca_base_component_path) +# serves all of the Open MPI layers, so it lists the ".libs" dirs +# under opal/mca, ompi/mca, and oshmem/mca -- not just opal/mca. The +# corresponding PMIx parameter uses PMIx's own "PMIX_MCA_" prefix and +# is named mca_base_component_path (no "opal_"). These are computed +# at test-run time (shell backquotes), not at Makefile-parse time, +# because the DSOs do not exist until the "all" prerequisite of "make +# check" has completed. +# +# The PMIx search path is only set when Open MPI was built against its +# internal (bundled) PMIx (OPAL_USING_INTERNAL_PMIX); with an external +# PMIx there are no PMIx component DSOs to find under 3rd-party, and a +# stale internal-PMIx build left over in the tree must not be pointed +# at by a run that actually links the external PMIx library. +# +# 2. Make each component DSO's own load-time dependencies resolvable. +# Some components link against an "mca_common" shared library shared +# by a family of sibling components (e.g. io/ompio, fcoll/*, fbtl/*, +# and sharedfp/* all depend on libmca_common_ompio). Those common +# libraries live under /mca/common//.libs, which is +# NOT on the runtime library search path baked into the libtool test +# wrapper scripts: the wrapper only lists /.libs (home of +# libopen-pal / libmpi / liboshmem). On macOS this cannot be worked +# around via DYLD_LIBRARY_PATH in AM_TESTS_ENVIRONMENT, because +# System Integrity Protection strips DYLD_* when the /bin/sh wrapper +# is exec'ed. So, before the tests run, symlink each project's +# mca_common shared libraries into that project's own .libs +# directory, which the wrapper already searches. +# +# Both mechanisms are harmless for a static (non-DSO) build: the symlink +# step finds nothing to link, and although the component-search-path +# variables may still be exported (libtool creates ".libs" dirs for +# static convenience libraries too), they point at directories with no +# loadable components, so nothing is dlopen'ed. They are likewise +# harmless for tests that never open a framework. The symlinks live in +# build-tree ".libs" directories and are removed automatically by +# libtool's "clean-libtool" rule (rm -rf .libs). +# +# Note: the env var prefix really is "OMPI_MCA_" even for OPAL-level +# parameters (OPAL_MCA_PREFIX == "OMPI_MCA_"); the full parameter name +# is opal_mca_base_component_path. +# +# NOTE: this fragment defines an "all-local" rule. Automake forbids two +# definitions of the same *-local target in one Makefile.am, so a +# Makefile.am that includes this fragment must NOT define its own +# "all-local"; instead, add prerequisites to the mca-common-dso-symlinks +# target below. + +AM_TESTS_ENVIRONMENT = \ + ompi_mca_path="`find $(abs_top_builddir)/opal/mca $(abs_top_builddir)/ompi/mca $(abs_top_builddir)/oshmem/mca -type d -name .libs 2>/dev/null | sort | tr '\n' ':'`"; \ + if test -n "$$ompi_mca_path"; then OMPI_MCA_opal_mca_base_component_path="$$ompi_mca_path"; export OMPI_MCA_opal_mca_base_component_path; fi; \ + if test "@OPAL_USING_INTERNAL_PMIX@" = "1"; then \ + pmix_mca_path="`find $(abs_top_builddir)/3rd-party/openpmix/src/mca -type d -name .libs 2>/dev/null | sort | tr '\n' ':'`"; \ + if test -n "$$pmix_mca_path"; then PMIX_MCA_mca_base_component_path="$$pmix_mca_path"; export PMIX_MCA_mca_base_component_path; fi; \ + fi; + +# See item 2 above. Symlink each project's mca_common DSOs into that +# project's own .libs directory (which the libtool wrappers already put +# on the runtime library search path) so dlopen'ed components can +# resolve their load-time dependencies. Created "if missing" so +# concurrent invocations (parallel "make check") never leave a symlink +# transiently absent. +all-local: mca-common-dso-symlinks +.PHONY: mca-common-dso-symlinks +mca-common-dso-symlinks: + $(AM_V_at)for proj in opal ompi oshmem; do \ + libs="$(abs_top_builddir)/$$proj/.libs"; test -d "$$libs" || continue; \ + find "$(abs_top_builddir)/$$proj/mca/common" \( -name 'lib*mca_common_*.dylib' -o -name 'lib*mca_common_*.so' -o -name 'lib*mca_common_*.so.*' \) 2>/dev/null | \ + while read lib; do base=`basename "$$lib"`; test -e "$$libs/$$base" || ln -s "$$lib" "$$libs/$$base" 2>/dev/null || true; done; \ + done diff --git a/VERSION b/VERSION index 44eb7329d5d..c82de41b70f 100644 --- a/VERSION +++ b/VERSION @@ -20,7 +20,7 @@ minor=1 release=0 # MPI Standard Compliance Level -mpi_standard_version=3 +mpi_standard_version=4 mpi_standard_subversion=1 # OMPI required dependency versions. @@ -101,7 +101,6 @@ libmpi_usempi_tkr_so_version=0:0:0 libmpi_usempi_ignore_tkr_so_version=0:0:0 libmpi_usempif08_so_version=0:0:0 libopen_pal_so_version=0:0:0 -libmpi_java_so_version=0:0:0 liboshmem_so_version=0:0:0 libompitrace_so_version=0:0:0 diff --git a/config/Makefile.am b/config/Makefile.am index 4af3a33f87c..d4ba7b16c33 100644 --- a/config/Makefile.am +++ b/config/Makefile.am @@ -41,7 +41,6 @@ EXTRA_DIST = \ opal_get_version.m4sh \ ltmain_nag_pthread.diff \ ltmain_pgi_tp.diff \ - opal_mca_priority_sort.pl \ find_common_syms \ getdate.sh \ from-savannah/upstream-config.guess \ diff --git a/config/c_weak_aliases.m4 b/config/c_weak_aliases.m4 new file mode 100644 index 00000000000..2a51c11b8a1 --- /dev/null +++ b/config/c_weak_aliases.m4 @@ -0,0 +1,135 @@ +dnl -*- shell-script -*- +dnl +dnl Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana +dnl University Research and Technology +dnl Corporation. All rights reserved. +dnl Copyright (c) 2004-2005 The University of Tennessee and The University +dnl of Tennessee Research Foundation. All rights +dnl reserved. +dnl Copyright (c) 2004-2005 High Performance Computing Center Stuttgart, +dnl University of Stuttgart. All rights reserved. +dnl Copyright (c) 2004-2005 The Regents of the University of California. +dnl All rights reserved. +dnl Copyright (c) 2014 Research Organization for Information Science +dnl and Technology (RIST). All rights reserved. +dnl Copyright (c) 2014 Cisco Systems, Inc. All rights reserved. +dnl Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. +dnl $COPYRIGHT$ +dnl +dnl Additional copyrights may follow +dnl +dnl $HEADER$ +dnl + +# _OPAL_C_WEAK_ALIASES(action_if_found, [action_if_not_found]) +# ------------------------------------------------------------ +AC_DEFUN([_OPAL_C_WEAK_ALIASES],[ + # need two files because icc will incorrectly not create the + # symbols if they are not used in the object file in which they + # are defined. Blah! + # To get to compile with icc, have them in a separate header. + cat > conftest_weak.h < conftest_weak.c < conftest.c < conftest_weak.h < conftest_weak.c < conftest.c < conftest_weak.h < conftest_weak.c < conftest.c < conftest_weak.h < conftest_weak.c < conftest.c < + # INTEGER4) and then run AS_TR_SH to map any remaining non-symbol + # characters to '_'. AS_TR_SH is locale-safe; an earlier version + # used m4_bpatsubst with a regex character class, which mis-handled + # locales whose collation defines digraphs (e.g. cs_CZ.UTF-8 treats + # 'ch' as one element and would mangle "char" -> "_ar"). # If we got a pretty name, use that as the basis. If not, use the # first part of the provided fortran type (e.g., @@ -174,38 +177,38 @@ AC_DEFUN([OMPI_FORTRAN_CHECK], [ # the result of the BLUm4E in a shell variable and use that in # AC_DEFINE_UNQUOTED), autoheader won't put them in the # AC_CONFIG_HEADER (or AM_CONFIG_HEADER, in our case). - AC_DEFINE_UNQUOTED([OMPI_HAVE_FORTRAN_]m4_translit(m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_]), [a-z], [A-Z]), + AC_DEFINE_UNQUOTED([OMPI_HAVE_FORTRAN_]m4_translit(AS_TR_SH(m4_bpatsubst([$1], [*], [])), [a-z], [A-Z]), [$ofc_have_type], [Whether we have Fortran $1 or not]) - AC_DEFINE_UNQUOTED([OMPI_SIZEOF_FORTRAN_]m4_translit(m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_]), [a-z], [A-Z]), + AC_DEFINE_UNQUOTED([OMPI_SIZEOF_FORTRAN_]m4_translit(AS_TR_SH(m4_bpatsubst([$1], [*], [])), [a-z], [A-Z]), [$ofc_type_size], [Size of Fortran $1]) - AC_DEFINE_UNQUOTED([OMPI_ALIGNMENT_FORTRAN_]m4_translit(m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_]), [a-z], [A-Z]), + AC_DEFINE_UNQUOTED([OMPI_ALIGNMENT_FORTRAN_]m4_translit(AS_TR_SH(m4_bpatsubst([$1], [*], [])), [a-z], [A-Z]), [$ofc_type_alignment], [Alignment of Fortran $1]) - AC_DEFINE_UNQUOTED([OMPI_KIND_FORTRAN_]m4_translit(m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_]), [a-z], [A-Z]), + AC_DEFINE_UNQUOTED([OMPI_KIND_FORTRAN_]m4_translit(AS_TR_SH(m4_bpatsubst([$1], [*], [])), [a-z], [A-Z]), [$ofc_type_kind], [Fortran KIND number for $1]) if test "$3" != "" && test "$ofc_define_type" = "yes"; then - AC_DEFINE_UNQUOTED([ompi_fortran_]m4_translit(m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_]), [A-Z], [a-z])[_t], + AC_DEFINE_UNQUOTED([ompi_fortran_]m4_translit(AS_TR_SH(m4_bpatsubst([$1], [*], [])), [A-Z], [a-z])[_t], [$ofc_c_type], [C type corresponding to Fortran $1]) fi # Save some in shell variables for later use (e.g., need # OMPI_SIZEOF_FORTRAN_INTEGER in OMPI_FORTRAN_GET_HANDLE_MAX) - [OMPI_FORTRAN_]m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_])[_C_TYPE=$ofc_c_type] - [OMPI_KIND_FORTRAN_]m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_])[=$ofc_type_kind] - [OMPI_HAVE_FORTRAN_]m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_])[=$ofc_have_type] - [OMPI_SIZEOF_FORTRAN_]m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_])[=$ofc_type_size] - [OMPI_ALIGNMENT_FORTRAN_]m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_])[=$ofc_type_alignment] + [OMPI_FORTRAN_]AS_TR_SH(m4_bpatsubst([$1], [*], []))[_C_TYPE=$ofc_c_type] + [OMPI_KIND_FORTRAN_]AS_TR_SH(m4_bpatsubst([$1], [*], []))[=$ofc_type_kind] + [OMPI_HAVE_FORTRAN_]AS_TR_SH(m4_bpatsubst([$1], [*], []))[=$ofc_have_type] + [OMPI_SIZEOF_FORTRAN_]AS_TR_SH(m4_bpatsubst([$1], [*], []))[=$ofc_type_size] + [OMPI_ALIGNMENT_FORTRAN_]AS_TR_SH(m4_bpatsubst([$1], [*], []))[=$ofc_type_alignment] # Wow, this is sick. But it works! :-) - AC_SUBST([OMPI_HAVE_FORTRAN_]m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_])) - AC_SUBST([OMPI_KIND_FORTRAN_]m4_translit(m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_]), [a-z], [A-Z])) - AC_SUBST([OMPI_SIZEOF_FORTRAN_]m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_])) - AC_SUBST([OMPI_SIZEOF_FORTRAN_]m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_])) - AC_SUBST([OMPI_ALIGNMENT_FORTRAN_]m4_bpatsubst(m4_bpatsubst([$1], [*], []), [[^a-zA-Z0-9_]], [_])) + AC_SUBST([OMPI_HAVE_FORTRAN_]AS_TR_SH(m4_bpatsubst([$1], [*], []))) + AC_SUBST([OMPI_KIND_FORTRAN_]m4_translit(AS_TR_SH(m4_bpatsubst([$1], [*], [])), [a-z], [A-Z])) + AC_SUBST([OMPI_SIZEOF_FORTRAN_]AS_TR_SH(m4_bpatsubst([$1], [*], []))) + AC_SUBST([OMPI_SIZEOF_FORTRAN_]AS_TR_SH(m4_bpatsubst([$1], [*], []))) + AC_SUBST([OMPI_ALIGNMENT_FORTRAN_]AS_TR_SH(m4_bpatsubst([$1], [*], []))) # Clean up OPAL_VAR_SCOPE_POP diff --git a/config/ompi_setup_java.m4 b/config/ompi_setup_java.m4 deleted file mode 100644 index 596df8be9b7..00000000000 --- a/config/ompi_setup_java.m4 +++ /dev/null @@ -1,234 +0,0 @@ -dnl -*- shell-script -*- -dnl -dnl Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana -dnl University Research and Technology -dnl Corporation. All rights reserved. -dnl Copyright (c) 2004-2006 The University of Tennessee and The University -dnl of Tennessee Research Foundation. All rights -dnl reserved. -dnl Copyright (c) 2004-2008 High Performance Computing Center Stuttgart, -dnl University of Stuttgart. All rights reserved. -dnl Copyright (c) 2004-2006 The Regents of the University of California. -dnl All rights reserved. -dnl Copyright (c) 2006-2012 Los Alamos National Security, LLC. All rights -dnl reserved. -dnl Copyright (c) 2007-2012 Oracle and/or its affiliates. All rights reserved. -dnl Copyright (c) 2008-2018 Cisco Systems, Inc. All rights reserved -dnl Copyright (c) 2013 Intel, Inc. All rights reserved. -dnl Copyright (c) 2015-2018 Research Organization for Information Science -dnl and Technology (RIST). All rights reserved. -dnl Copyright (c) 2017 FUJITSU LIMITED. All rights reserved. -dnl Copyright (c) 2025 Nanook Consulting All rights reserved. -dnl $COPYRIGHT$ -dnl -dnl Additional copyrights may follow -dnl -dnl $HEADER$ -dnl - -dnl _OMPI_SETUP_JAVA() -dnl ---------------- -dnl Invoked by OMPI_SETUP_JAVA only if --enable-mpi-java was specified. -AC_DEFUN([_OMPI_SETUP_JAVA],[ - OPAL_VAR_SCOPE_PUSH([ompi_java_bad ompi_java_found ompi_java_dir ompi_java_jnih ompi_java_PATH_save ompi_java_CPPFLAGS_save]) - - # Check for bozo case: ensure a directory was specified - AS_IF([test "$with_jdk_dir" = "yes" || test "$with_jdk_dir" = "no"], - [AC_MSG_WARN([Must specify a directory name for --with-jdk-dir]) - AC_MSG_ERROR([Cannot continue])]) - AS_IF([test "$with_jdk_bindir" = "yes" || test "$with_jdk_bindir" = "no"], - [AC_MSG_WARN([Must specify a directory name for --with-jdk-bindir]) - AC_MSG_ERROR([Cannot continue])]) - AS_IF([test "$with_jdk_headers" = "yes" || test "$with_jdk_headers" = "no"], - [AC_MSG_WARN([Must specify a directory name for --with-jdk-headers]) - AC_MSG_ERROR([Cannot continue])]) - - # Check for bozo case: either specify --with-jdk-dir or - # (--with-jdk-bindir, --with-jdk-headers) -- not both. - ompi_java_bad=0 - AS_IF([test -n "$with_jdk_dir" && \ - (test -n "$with_jdk_bindir" || test -n "$with_jdk_headers")], - [ompi_java_bad=1]) - AS_IF([(test -z "$with_jdk_bindir" && test -n "$with_jdk_headers") || \ - (test -n "$with_jdk_bindir" && test -z "$with_jdk_headers")], - [ompi_java_bad=1]) - AS_IF([test $ompi_java_bad -eq 1], - [AC_MSG_WARN([Either specify --with-jdk-dir or both of (--with-jdk_bindir, --with-jdk-headers) -- not both.]) - AC_MSG_ERROR([Cannot continue])]) - - AS_IF([test -n "$with_jdk_dir"], - [with_jdk_bindir=$with_jdk_dir/bin - with_jdk_headers=$with_jdk_dir/include]) - - ################################################################## - # with_jdk_dir can now be ignored; with_jdk_bindir and - # with_jdk_headers will be either empty or have valid values. - ################################################################## - - # Some java installations are in obscure places. So let's - # hard-code a few of the common ones so that users don't have to - # specify --with-java-=LONG_ANNOYING_DIRECTORY. - AS_IF([test -z "$with_jdk_bindir"], - [ # OS X/macOS - ompi_java_found=0 - # The following logic was deliberately decided upon in - # https://github.com/open-mpi/ompi/pull/5015 specifically - # to prevent this script and the rest of Open MPI's build - # system from getting confused by the somewhat unorthodox - # Java toolchain layout present on OS X/macOS systems, - # described in depth by - # https://github.com/open-mpi/ompi/pull/5015#issuecomment-379324639, - # and mishandling OS X/macOS Java toolchain path detection - # as a result. - AS_IF([test -x /usr/libexec/java_home], - [ompi_java_dir=`/usr/libexec/java_home`], - [ompi_java_dir=/System/Library/Frameworks/JavaVM.framework/Versions/Current]) - AC_MSG_CHECKING([for Java in OS X/macOS locations]) - AS_IF([test -d "$ompi_java_dir"], - [AC_MSG_RESULT([found ($ompi_java_dir)]) - ompi_java_found=1 - if test -d "$ompi_java_dir/Headers" && test -d "$ompi_java_dir/Commands"; then - with_jdk_headers=$ompi_java_dir/Headers - with_jdk_bindir=$ompi_java_dir/Commands - elif test -d "$ompi_java_dir/include" && test -d "$ompi_java_dir/bin"; then - with_jdk_headers=$ompi_java_dir/include - with_jdk_bindir=$ompi_java_dir/bin - else - AC_MSG_WARN([No recognized OS X/macOS JDK directory structure found under $ompi_java_dir]) - ompi_java_found=0 - fi], - [AC_MSG_RESULT([not found])]) - - if test "$ompi_java_found" = "0"; then - # Various Linux - if test -z "$JAVA_HOME"; then - ompi_java_dir='/usr/lib/jvm/java-*-openjdk*/include/' - else - ompi_java_dir=$JAVA_HOME/include - fi - ompi_java_jnih=`ls $ompi_java_dir/jni.h 2>/dev/null | head -n 1` - AC_MSG_CHECKING([for Java in Linux locations]) - AS_IF([test -r "$ompi_java_jnih"], - [with_jdk_headers=`dirname $ompi_java_jnih` - OPAL_WHICH([javac], [with_jdk_bindir]) - AS_IF([test -n "$with_jdk_bindir"], - [AC_MSG_RESULT([found ($with_jdk_headers)]) - ompi_java_found=1 - with_jdk_bindir=`dirname $with_jdk_bindir`], - [with_jdk_headers=])], - [ompi_java_dir='/usr/lib/jvm/default-java/include/' - ompi_java_jnih=`ls $ompi_java_dir/jni.h 2>/dev/null | head -n 1` - AS_IF([test -r "$ompi_java_jnih"], - [with_jdk_headers=`dirname $ompi_java_jnih` - OPAL_WHICH([javac], [with_jdk_bindir]) - AS_IF([test -n "$with_jdk_bindir"], - [AC_MSG_RESULT([found ($with_jdk_headers)]) - ompi_java_found=1 - with_jdk_bindir=`dirname $with_jdk_bindir`], - [with_jdk_headers=])], - [AC_MSG_RESULT([not found])])]) - fi - - ], - [ompi_java_found=1]) - - if test "$ompi_java_found" = "1"; then - OPAL_CHECK_WITHDIR([jdk-bindir], [$with_jdk_bindir], [javac]) - OPAL_CHECK_WITHDIR([jdk-headers], [$with_jdk_headers], [jni.h]) - - # Look for various Java-related programs - ompi_java_happy=no - ompi_java_PATH_save=$PATH - AS_IF([test -n "$with_jdk_bindir" && test "$with_jdk_bindir" != "yes" && test "$with_jdk_bindir" != "no"], - [PATH="$with_jdk_bindir:$PATH"]) - AC_PATH_PROG(JAVAC, javac) - AC_PATH_PROG(JAR, jar) - AC_PATH_PROG(JAVADOC, javadoc) - AC_PATH_PROG(JAVAH, javah) - PATH=$ompi_java_PATH_save - - # Check to see if we have all 3 programs. - AS_IF([test -z "$JAVAC" || test -z "$JAR" || test -z "$JAVADOC"], - [ompi_java_happy=no], - [ompi_java_happy=yes]) - - # Look for jni.h - AS_IF([test "$ompi_java_happy" = "yes"], - [ompi_java_CPPFLAGS_save=$CPPFLAGS - # silence a stupid Mac warning - CPPFLAGS="$CPPFLAGS -DTARGET_RT_MAC_CFM=0" - AC_MSG_CHECKING([javac -h]) - cat > Conftest.java << EOF -public final class Conftest { - public native void conftest(); -} -EOF - AS_IF([$JAVAC -d . -h . Conftest.java > /dev/null 2>&1], - [AC_MSG_RESULT([yes])], - [AC_MSG_RESULT([no]) - AS_IF([test -n "$JAVAH"], - [ompi_javah_happy=yes], - [ompi_java_happy=no])]) - rm -f Conftest.java Conftest.class Conftest.h - - AS_IF([test -n "$with_jdk_headers" && test "$with_jdk_headers" != "yes" && test "$with_jdk_headers" != "no"], - [OMPI_JDK_CPPFLAGS="-I$with_jdk_headers" - # Some flavors of JDK also require -I/linux. - # See if that's there, and if so, add a -I for that, - # too. Ugh. - AS_IF([test -d "$with_jdk_headers/linux"], - [OMPI_JDK_CPPFLAGS="$OMPI_JDK_CPPFLAGS -I$with_jdk_headers/linux"]) - # Darwin JDK also require -I/darwin. - # See if that's there, and if so, add a -I for that, - # too. Ugh. - AS_IF([test -d "$with_jdk_headers/darwin"], - [OMPI_JDK_CPPFLAGS="$OMPI_JDK_CPPFLAGS -I$with_jdk_headers/darwin"]) - - CPPFLAGS="$CPPFLAGS $OMPI_JDK_CPPFLAGS"]) - AC_CHECK_HEADER([jni.h], [], - [ompi_java_happy=no]) - CPPFLAGS=$ompi_java_CPPFLAGS_save - ]) - else - ompi_java_happy=no - fi - AC_SUBST(OMPI_JDK_CPPFLAGS) - - # Are we happy? - AC_MSG_CHECKING([if Java support available]) - AS_IF([test "$ompi_java_happy" = "yes"], - [AC_MSG_RESULT([yes])], - [AC_MSG_RESULT([no]) - AC_MSG_WARN([Java support requested but not found.]) - AC_MSG_ERROR([Cannot continue])]) - - OPAL_VAR_SCOPE_POP -]) - -dnl OMPI_SETUP_JAVA() -dnl ---------------- -dnl Do everything required to setup the Java compiler. -AC_DEFUN([OMPI_SETUP_JAVA],[ - OPAL_VAR_SCOPE_PUSH([ompi_java_happy ompi_javah_happy]) - - ompi_java_happy=no - ompi_javah_happy=no - - AC_ARG_WITH([jdk-dir], - [AS_HELP_STRING([--with-jdk-dir(=DIR)], - [Location of the JDK header directory. If you use this option, do not specify --with-jdk-bindir or --with-jdk-headers.])]) - AC_ARG_WITH([jdk-bindir], - [AS_HELP_STRING([--with-jdk-bindir(=DIR)], - [Location of the JDK bin directory. If you use this option, you must also use --with-jdk-headers (and you must NOT use --with-jdk-dir)])]) - AC_ARG_WITH([jdk-headers], - [AS_HELP_STRING([--with-jdk-headers(=DIR)], - [Location of the JDK header directory. If you use this option, you must also use --with-jdk-bindir (and you must NOT use --with-jdk-dir)])]) - - # Only setup the compiler if we were requested to - AS_IF([test "$1" = "yes"], - [_OMPI_SETUP_JAVA]) - - AM_CONDITIONAL(OMPI_HAVE_JAVAH_SUPPORT, test "$ompi_javah_happy" = "yes") - - OPAL_VAR_SCOPE_POP -]) diff --git a/config/ompi_setup_mpi_fortran.m4 b/config/ompi_setup_mpi_fortran.m4 index c396a2efab6..bdb92ec3124 100644 --- a/config/ompi_setup_mpi_fortran.m4 +++ b/config/ompi_setup_mpi_fortran.m4 @@ -255,6 +255,19 @@ AC_DEFUN([OMPI_SETUP_MPI_FORTRAN],[ # MPI_Status struct contains 4 C ints and a size_t. OMPI_FORTRAN_STATUS_SIZE=0 + # The C-to-Fortran MPI_Status conversion (and other code that passes + # MPI values through Fortran default INTEGERs) copies C ints into + # INTEGER slots one-for-one. If the Fortran default INTEGER is + # narrower than a C int, the public MPI_Status fields (and other + # values) would be silently truncated. Refuse to build in that case + # rather than produce a broken Fortran MPI. Skip the check when we + # are not building any Fortran bindings (e.g., --disable-mpi-fortran, + # or no usable Fortran compiler), in which case + # OMPI_SIZEOF_FORTRAN_INTEGER is 0. + AS_IF([test "$OMPI_SIZEOF_FORTRAN_INTEGER" -gt 0 && \ + test "$OMPI_SIZEOF_FORTRAN_INTEGER" -lt "$ac_cv_sizeof_int"], + [AC_MSG_ERROR([the Fortran default INTEGER ($OMPI_SIZEOF_FORTRAN_INTEGER bytes) is smaller than a C int ($ac_cv_sizeof_int bytes). Open MPI requires the Fortran default INTEGER to be at least as large as a C int; otherwise values such as the public MPI_Status fields would be truncated when converted between C and Fortran. Reconfigure with a larger Fortran default INTEGER, or build with --disable-mpi-fortran.])]) + # Calculate how many C int's can fit in sizeof(MPI_Status). Yes, # I do mean C ints -- not Fortran INTEGERS. The reason is because # an mpif.h MPI_Status is an array of INTEGERS. But these @@ -745,7 +758,7 @@ end type test_mpi_handle], # disabled # 1. MPI_ bindings are needed if: # - Profiling is not required - # - Profiling is required but weak symbols are not supported + # - Profiling is required but weak aliases are not supported # 2. PMPI_ bindings are needed if profiling is required. # # Hence we define 2 conditionals which tell us whether each of @@ -825,13 +838,13 @@ end type test_mpi_handle], AC_SUBST(OMPI_F08_SUFFIX) AC_SUBST(OMPI_F_SUFFIX) - # This is used to generate weak symbols (or not) in + # This is used to generate weak aliases (or not) in # ompi/mpi/fortran/mpif-h/_f.c, and # ompi/mpi/fortran/configure-fortran-output.h. AC_SUBST(OMPI_FORTRAN_NEED_WRAPPER_ROUTINES) AC_DEFINE_UNQUOTED(OMPI_FORTRAN_NEED_WRAPPER_ROUTINES, [$OMPI_FORTRAN_NEED_WRAPPER_ROUTINES], - [Whether the mpi_f08 implementation is using wrapper routines ("bad" Fortran compiler) or weak symbols ("good" Fortran compiler) for the F08 interface definition implementations]) + [Whether the mpi_f08 implementation is using wrapper routines ("bad" Fortran compiler) or weak aliases ("good" Fortran compiler) for the F08 interface definition implementations]) AC_DEFINE_UNQUOTED(OMPI_FORTRAN_F08_HANDLE_SIZE, $OMPI_FORTRAN_F08_HANDLE_SIZE, diff --git a/config/ompi_setup_mpi_java.m4 b/config/ompi_setup_mpi_java.m4 deleted file mode 100644 index 3f8f76c580f..00000000000 --- a/config/ompi_setup_mpi_java.m4 +++ /dev/null @@ -1,85 +0,0 @@ -dnl -*- shell-script -*- -dnl -dnl Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana -dnl University Research and Technology -dnl Corporation. All rights reserved. -dnl Copyright (c) 2004-2006 The University of Tennessee and The University -dnl of Tennessee Research Foundation. All rights -dnl reserved. -dnl Copyright (c) 2004-2008 High Performance Computing Center Stuttgart, -dnl University of Stuttgart. All rights reserved. -dnl Copyright (c) 2004-2006 The Regents of the University of California. -dnl All rights reserved. -dnl Copyright (c) 2006-2012 Los Alamos National Security, LLC. All rights -dnl reserved. -dnl Copyright (c) 2007-2012 Oracle and/or its affiliates. All rights reserved. -dnl Copyright (c) 2008-2018 Cisco Systems, Inc. All rights reserved -dnl Copyright (c) 2015 Research Organization for Information Science -dnl and Technology (RIST). All rights reserved. -dnl $COPYRIGHT$ -dnl -dnl Additional copyrights may follow -dnl -dnl $HEADER$ -dnl - -dnl OMPI_SETUP_JAVA_BINDINGS() -dnl ---------------- -dnl Do everything required to setup the Java MPI bindings. -AC_DEFUN([OMPI_SETUP_JAVA_BINDINGS],[ - opal_show_subtitle "Java MPI bindings" - - AC_ARG_ENABLE([mpi-java], - [AS_HELP_STRING([--enable-mpi-java], - [enable Java MPI bindings (default: disabled)])]) - - # Find the Java compiler and whatnot. - # It knows to do very little if $enable_mpi_java!="yes". - OMPI_SETUP_JAVA([$enable_mpi_java]) - - # Only build the Java bindings if requested - AC_MSG_CHECKING([if want Java bindings]) - if test "$enable_mpi_java" = "yes"; then - AC_MSG_RESULT([yes]) - WANT_MPI_JAVA_BINDINGS=1 - AC_MSG_CHECKING([if shared libraries are enabled]) - AS_IF([test "$enable_shared" != "yes"], - [AC_MSG_RESULT([no]) - AC_MSG_WARN([Java bindings cannot be built without shared libraries]) - AC_MSG_WARN([Please reconfigure with --enable-shared]) - AC_MSG_ERROR([Cannot continue])], - [AC_MSG_RESULT([yes])]) - - # Mac Java requires this file (i.e., some other Java-related - # header file needs this file, so we need to check for - # it/include it in our sources when compiling on Mac). - AC_CHECK_HEADERS([TargetConditionals.h]) - - # dladdr and Dl_info are required to build the full path to - # libmpi on OS X 10.11 (a.k.a. El Capitan) - AC_CHECK_TYPES([Dl_info], [], [], [[#include ]]) - else - AC_MSG_RESULT([no]) - WANT_MPI_JAVA_BINDINGS=0 - fi - AC_DEFINE_UNQUOTED([OMPI_WANT_JAVA_BINDINGS], [$WANT_MPI_JAVA_BINDINGS], - [do we want java mpi bindings]) - AM_CONDITIONAL(OMPI_WANT_JAVA_BINDINGS, test "$WANT_MPI_JAVA_BINDINGS" = "1") - - # Are we happy? - AS_IF([test $WANT_MPI_JAVA_BINDINGS -eq 1], - [AC_MSG_WARN([******************************************************]) - AC_MSG_WARN([*** Java MPI bindings are provided on a provisional]) - AC_MSG_WARN([*** basis. They are NOT part of the current or]) - AC_MSG_WARN([*** proposed MPI standard. Continued inclusion of]) - AC_MSG_WARN([*** the Java MPI bindings in Open MPI is contingent]) - AC_MSG_WARN([*** upon user interest and developer support.]) - AC_MSG_WARN([******************************************************]) - ]) - - AC_CONFIG_FILES([ - ompi/mpi/java/Makefile - ompi/mpi/java/java/Makefile - ompi/mpi/java/c/Makefile - ]) -]) diff --git a/config/ompi_setup_mpi_profiling.m4 b/config/ompi_setup_mpi_profiling.m4 index 2fc2ba1f0f7..64158e2e453 100644 --- a/config/ompi_setup_mpi_profiling.m4 +++ b/config/ompi_setup_mpi_profiling.m4 @@ -33,7 +33,7 @@ AC_DEFUN([OMPI_SETUP_MPI_PROFILING],[ # both) these layers are built. # 1. MPI_* bindings are needed if: # - Profiling is not required - # - Profiling is required but weak symbols are not + # - Profiling is required but weak aliases are not # supported # 2. PMPI_* bindings are needed if profiling is required. Hence we # define 2 conditionals which tell us whether each of these layers @@ -45,6 +45,6 @@ AC_DEFUN([OMPI_SETUP_MPI_PROFILING],[ AM_CONDITIONAL(COMPILE_PROFILING_SEPARATELY, test "$OMPI_PROFILING_COMPILE_SEPARATELY" = 1) - AC_DEFINE_UNQUOTED(OPAL_HAVE_WEAK_SYMBOLS, $OPAL_C_HAVE_WEAK_SYMBOLS, - [Whether we have weak symbols or not]) + AC_DEFINE_UNQUOTED(OPAL_HAVE_WEAK_ALIASES, $OPAL_C_HAVE_WEAK_ALIASES, + [Whether we have weak aliases or not]) ]) diff --git a/config/opal_check_knem.m4 b/config/opal_check_knem.m4 index 65ac21b028e..e0168b33a0d 100644 --- a/config/opal_check_knem.m4 +++ b/config/opal_check_knem.m4 @@ -36,7 +36,7 @@ AC_DEFUN([OPAL_CHECK_KNEM],[ [opal_check_knem_happy="yes"]) AS_IF([test "${opal_check_knem_happy}" = "yes"], - [AS_IF([test -a "${with_knem}" != "yes"], + [AS_IF([test -n "${with_knem}" -a "${with_knem}" != "yes"], [$1_CPPFLAGS="-I${with_knem}/include" CPPFLAGS="$CPPFLAGS ${$1_CPPFLAGS}"]) AC_CHECK_HEADER([knem_io.h], [opal_check_knem_happy="yes"], [opal_check_knem_happy="no"])]) diff --git a/config/opal_configure_options.m4 b/config/opal_configure_options.m4 index 7f5822782fe..a9bbca06135 100644 --- a/config/opal_configure_options.m4 +++ b/config/opal_configure_options.m4 @@ -291,19 +291,29 @@ AC_DEFINE_UNQUOTED([OPAL_ENABLE_PTY_SUPPORT], [$OPAL_ENABLE_PTY_SUPPORT], # -# Do we want to disable weak symbols for some reason? +# Do we want to disable weak aliases for some reason? # -AC_MSG_CHECKING([if want weak symbol support]) +AC_ARG_ENABLE([weak-aliases], + [AS_HELP_STRING([--enable-weak-aliases], + [use weak symbol aliases, if available (default: enabled)])]) +# --enable-weak-symbols is the historical name for this option; keep it +# as a deprecated synonym for --enable-weak-aliases. AC_ARG_ENABLE([weak-symbols], [AS_HELP_STRING([--enable-weak-symbols], - [use weak symbols, if available (default: enabled)])]) -if test "$enable_weak_symbols" != "no"; then + [Deprecated synonym for --enable-weak-aliases])]) +AS_IF([test -n "$enable_weak_symbols"], + [AC_MSG_WARN([--enable-weak-symbols is deprecated; please use --enable-weak-aliases instead]) + AS_IF([test -z "$enable_weak_aliases"], + [enable_weak_aliases=$enable_weak_symbols])]) + +AC_MSG_CHECKING([if want weak alias support]) +if test "$enable_weak_aliases" != "no"; then AC_MSG_RESULT([yes]) - WANT_WEAK_SYMBOLS=1 + WANT_WEAK_ALIASES=1 else AC_MSG_RESULT([no]) - WANT_WEAK_SYMBOLS=0 + WANT_WEAK_ALIASES=0 fi diff --git a/config/opal_find_type.m4 b/config/opal_find_type.m4 index bc98dd681c5..0fb12be0aea 100644 --- a/config/opal_find_type.m4 +++ b/config/opal_find_type.m4 @@ -34,7 +34,11 @@ AC_DEFUN([OPAL_FIND_TYPE],[ AS_IF([test "$oft_target_size" != ""], [m4_foreach(oft_type, [$2], [if test -z "$oft_real_type"; then - if test "[$ac_cv_sizeof_]m4_bpatsubst(oft_type, [[^a-zA-Z0-9_]], [_])" = "$oft_target_size" ; then + dnl AS_TR_SH expands literals via m4_translit, which is + dnl locale-independent; m4_bpatsubst regex character + dnl classes are not (e.g. cs_CZ.UTF-8 collates "ch" as + dnl one element, mangling "char" -> "_ar"). + if test "[$ac_cv_sizeof_]AS_TR_SH(oft_type)" = "$oft_target_size" ; then oft_real_type="oft_type" fi fi diff --git a/config/opal_mca.m4 b/config/opal_mca.m4 index bb51d3bc5f1..38f985bf571 100644 --- a/config/opal_mca.m4 +++ b/config/opal_mca.m4 @@ -338,15 +338,62 @@ AC_DEFUN([MCA_CONFIGURE_PROJECT],[ AC_SUBST(MCA_$1_FRAMEWORK_CORE_LIBS) ]) +# _MCA_TRACK_PRIORITY_RANGE(priority) +# ----------------------------------- +# Fold the given priority into the running +# mca_component_max_priority / mca_component_min_priority values. Both +# must be defined (to the empty string, if this is the first priority +# seen) before invoking this macro. +m4_define([_MCA_TRACK_PRIORITY_RANGE], + [m4_ifval(mca_component_max_priority, + [m4_if(m4_eval([$1] > mca_component_max_priority), [1], + [m4_define([mca_component_max_priority], [$1])])dnl + m4_if(m4_eval([$1] < mca_component_min_priority), [1], + [m4_define([mca_component_min_priority], [$1])])], + [m4_define([mca_component_max_priority], [$1])dnl + m4_define([mca_component_min_priority], [$1])])]) + +# _MCA_EMIT_COMPONENT_IF_PRIORITY(project_name, framework_name, +# component_name, priority) +# ------------------------------------------------------------- +# Emit component_name (preceded by a separator, if it is not the first +# component emitted) if and only if its priority is the given priority. +# mca_component_separator must be defined to the empty string before +# the first invocation of this macro. +m4_define([_MCA_EMIT_COMPONENT_IF_PRIORITY], + [m4_if(m4_eval(OPAL_EVAL_ARG([MCA_]$1[_]$2[_]$3[_PRIORITY]) == [$4]), [1], + [mca_component_separator[]$3[]m4_define([mca_component_separator], [, ])])]) + # MCA_ORDER_COMPONENT_LIST(project_name, framework_name) +# ------------------------------------------------------ +# Define component_list to be the framework's m4-configure component +# list, ordered from highest to lowest +# MCA____PRIORITY. Every component in +# the list must have a priority. AC_DEFUN([MCA_ORDER_COMPONENT_LIST], [ m4_foreach(mca_component, [mca_$1_$2_m4_config_component_list], [m4_ifval(mca_component, [m4_ifdef([MCA_]$1[_]$2[_]mca_component[_PRIORITY], [], [m4_fatal([MCA_$1_$2_]mca_component[_PRIORITY not found, but required.])])])]) +dnl Find the highest and lowest priorities in the framework. + m4_define([mca_component_max_priority], [])dnl + m4_define([mca_component_min_priority], [])dnl + m4_foreach([mca_component], [mca_$1_$2_m4_config_component_list], + [m4_ifval(mca_component, + [_MCA_TRACK_PRIORITY_RANGE(OPAL_EVAL_ARG([MCA_]$1[_]$2[_]mca_component[_PRIORITY]))])])dnl +dnl Walk the priorities from highest to lowest, emitting the components +dnl that have each priority. Walking the priorities (vs. sorting the +dnl components) means that components of equal priority are emitted in +dnl the same relative order in which they appear in the original list. + m4_define([mca_component_separator], [])dnl m4_define([component_list], - [esyscmd([config/opal_mca_priority_sort.pl] m4_foreach([mca_component], [mca_$1_$2_m4_config_component_list], - [m4_ifval(mca_component, [mca_component ]OPAL_EVAL_ARG([MCA_]$1[_]$2[_]mca_component[_PRIORITY ]))]))]) + m4_dquote(m4_ifval(mca_component_max_priority, + [m4_for([mca_component_priority], + mca_component_max_priority, mca_component_min_priority, -1, + [m4_foreach([mca_component], [mca_$1_$2_m4_config_component_list], + [m4_ifval(mca_component, + [_MCA_EMIT_COMPONENT_IF_PRIORITY($1, $2, mca_component, + mca_component_priority)])])])]))) ]) AC_DEFUN([MCA_CHECK_IGNORED_PRIORITY], [ diff --git a/config/opal_mca_priority_sort.pl b/config/opal_mca_priority_sort.pl deleted file mode 100755 index 4026e990edc..00000000000 --- a/config/opal_mca_priority_sort.pl +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/env perl -# -# Copyright (c) 2010 Sandia National Laboratories. All rights reserved. -# -# $COPYRIGHT$ -# -# Additional copyrights may follow -# -# $HEADER$ -# - -my $components; -my @result; - -while (@ARGV) { - my $component; - $component->{"name"} = shift(@ARGV); - $component->{"value"} = shift(@ARGV); - push(@{$components}, $component); -} - -foreach my $component (sort { $b->{value} <=> $a->{value} } @{$components}) { - push(@result, $component->{name}); -} -sub commify_series { - (@_ == 0) ? '' : - (@_ == 1) ? $_[0] : - join(", ", @_[0 .. ($#_-1)], "$_[-1]"); -} - -print commify_series(@result); diff --git a/config/opal_summary.m4 b/config/opal_summary.m4 index a922a836a17..67e6e959d5c 100644 --- a/config/opal_summary.m4 +++ b/config/opal_summary.m4 @@ -7,6 +7,7 @@ dnl Copyright (c) 2016 Research Organization for Information Science dnl and Technology (RIST). All rights reserved. dnl Copyright (c) 2022 Amazon.com, Inc. or its affiliates. All Rights reserved. dnl Copyright (c) 2022 IBM Corporation. All rights reserved. +dnl Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. dnl $COPYRIGHT$ dnl dnl Additional copyrights may follow @@ -57,12 +58,6 @@ EOF else echo "Build MPI Fortran bindings: no" >&AS_MESSAGE_FD fi - - if test $WANT_MPI_JAVA_BINDINGS -eq 1 ; then - echo "Build MPI Java bindings (experimental): yes" >&AS_MESSAGE_FD - else - echo "Build MPI Java bindings (experimental): no" >&AS_MESSAGE_FD - fi fi if test "$project_oshmem_amc" = "true" ; then diff --git a/config/oshmem_configure_options.m4 b/config/oshmem_configure_options.m4 index 0e4b0ea55bc..a6347843c56 100644 --- a/config/oshmem_configure_options.m4 +++ b/config/oshmem_configure_options.m4 @@ -117,7 +117,7 @@ AS_IF([test "$enable_oshmem" = "no" && \ AC_MSG_ERROR([Cannot continue])]) # Cannot check if we can enable profiling because it is not yet -# known whether the compiler supports weak symbols. +# known whether the compiler supports weak aliases. # diff --git a/config/oshmem_setup_profiling.m4 b/config/oshmem_setup_profiling.m4 index c31c3731075..6f76dba8179 100644 --- a/config/oshmem_setup_profiling.m4 +++ b/config/oshmem_setup_profiling.m4 @@ -16,20 +16,20 @@ AC_DEFUN([OSHMEM_SETUP_PROFILING],[ AC_MSG_CHECKING([if pshmem will be enabled]) AS_IF([test "$enable_oshmem" != "no" && \ test "$enable_oshmem_profile" != "no"], - [AS_IF([test "$OPAL_C_HAVE_WEAK_SYMBOLS" -eq 1 && \ - test "$OPAL_C_HAVE_MACRO_WEAK_SYMBOLS" -eq 1], + [AS_IF([test "$OPAL_C_HAVE_WEAK_ALIASES" -eq 1 && \ + test "$OPAL_C_HAVE_MACRO_WEAK_ALIASES" -eq 1], [ # We want OSHMEM and we have all the required weak # symbol support oshmem_profiling_support=1 - AC_MSG_RESULT([yes (weak symbols supported)])], + AC_MSG_RESULT([yes (weak aliases supported)])], [ # We want OSHMEM, but we do NOT have all the - # required weak symbol support + # required weak alias support oshmem_profiling_support=0 - AC_MSG_RESULT([no (weak symbols not supported)]) - AS_IF([test "$OPAL_C_HAVE_WEAK_SYMBOLS" -eq 0], - [AC_MSG_WARN([Weak symbols not supported by compiler])]) - AS_IF([test "$OPAL_C_HAVE_MACRO_WEAK_SYMBOLS" -eq 0], - [AC_MSG_WARN([Macro weak symbols not supported by compiler])]) + AC_MSG_RESULT([no (weak aliases not supported)]) + AS_IF([test "$OPAL_C_HAVE_WEAK_ALIASES" -eq 0], + [AC_MSG_WARN([Weak aliases not supported by compiler])]) + AS_IF([test "$OPAL_C_HAVE_MACRO_WEAK_ALIASES" -eq 0], + [AC_MSG_WARN([Macro weak aliases not supported by compiler])]) AC_MSG_WARN([OpenSHMEM profiling is disabled.]) AS_IF([test "$enable_oshmem_profile" = "yes" && \ diff --git a/configure.ac b/configure.ac index d4276b23284..6520e80afee 100644 --- a/configure.ac +++ b/configure.ac @@ -28,7 +28,7 @@ # Copyright (c) 2018 FUJITSU LIMITED. All rights reserved. # Copyright (c) 2019 Triad National Security, LLC. All rights # reserved. -# Copyright (c) 2023 Jeffrey M. Squyres. All rights reserved. +# Copyright (c) 2023-2026 Jeffrey M. Squyres. All rights reserved. # Copyright (c) 2025 Nanook Consulting All rights reserved. # $COPYRIGHT$ # @@ -151,7 +151,6 @@ m4_ifdef([project_ompi], AC_SUBST(libmpi_usempi_tkr_so_version) AC_SUBST(libmpi_usempi_ignore_tkr_so_version) AC_SUBST(libmpi_usempif08_so_version) - AC_SUBST(libmpi_java_so_version) AC_SUBST(libompitrace_so_version)]) m4_ifdef([project_oshmem], [AC_SUBST(liboshmem_so_version)]) @@ -555,45 +554,23 @@ OPAL_C_GET_ALIGNMENT(size_t, OPAL_ALIGNMENT_SIZE_T) OPAL_CHECK_ALT_SHORT_FLOAT -# Check system alignment requirements -if test "$opal_want_heterogeneous" = 1; then - ompi_cv_c_word_size_align=yes -else - AC_CACHE_CHECK([if word-sized integers must be word-size aligned], - [ompi_cv_c_word_size_align], - [AC_LANG_PUSH(C) - AC_RUN_IFELSE([AC_LANG_PROGRAM([dnl -#include ], [[ long data[2] = {0, 0}; - long *lp; - int *ip; - ip = (int*) data; - ip++; - lp = (long*) ip; - return lp[0]; ]])], - [ompi_cv_c_word_size_align=no], - [ompi_cv_c_word_size_align=yes], - [ompi_cv_c_word_size_align=yes])]) -fi -AS_IF([test $ompi_cv_c_word_size_align = yes], [results=1], [results=0]) -AC_DEFINE_UNQUOTED([OPAL_ALIGN_WORD_SIZE_INTEGERS], [$results], - [set to 1 if word-size integers must be aligned to word-size padding to prevent bus errors]) - # # Check for other compiler characteristics # +OPAL_C_WEAK_ALIASES +OPAL_C_MACRO_WEAK_ALIASES OPAL_C_WEAK_SYMBOLS -OPAL_C_MACRO_WEAK_SYMBOLS # If we want the profiling layer: -# - If the C compiler has weak symbols, use those. +# - If the C compiler has weak aliases, use those. # - If not, then set to compile the code again with #define's in a # separate directory. -if test "$WANT_WEAK_SYMBOLS" = "0"; then - OPAL_C_HAVE_WEAK_SYMBOLS=0 +if test "$WANT_WEAK_ALIASES" = "0"; then + OPAL_C_HAVE_WEAK_ALIASES=0 fi -if test "$OPAL_C_HAVE_WEAK_SYMBOLS" = "1"; then +if test "$OPAL_C_HAVE_WEAK_ALIASES" = "1"; then OMPI_PROFILING_COMPILE_SEPARATELY=0 else OMPI_PROFILING_COMPILE_SEPARATELY=1 @@ -641,13 +618,6 @@ AS_IF([test "$opal_cv_compiler_FAMILYNAME" = "GNU" && \ AC_MSG_ERROR([Cannot continue]) ]) -################################## -# Java MPI Binding request -################################## -# Only needed for OMPI -m4_ifdef([project_ompi], [OMPI_SETUP_JAVA_BINDINGS]) - - ################################## # MPI / OpenSHMEM API profiling layer ################################## @@ -1552,8 +1522,6 @@ AC_CONFIG_FILES([ test/util/Makefile ]) -m4_ifdef([project_ompi], [AC_CONFIG_FILES([test/monitoring/Makefile test/spc/Makefile])]) - AC_CONFIG_FILES([contrib/dist/mofed/debian/rules], [chmod +x contrib/dist/mofed/debian/rules]) AC_CONFIG_FILES([contrib/dist/mofed/compile_debian_mlnx_example], diff --git a/contrib/annual-maintenance/convert-to-git.txt b/contrib/annual-maintenance/convert-to-git.txt deleted file mode 100644 index fde32ad9205..00000000000 --- a/contrib/annual-maintenance/convert-to-git.txt +++ /dev/null @@ -1,60 +0,0 @@ -From Dave: - -For fun, here's the Git version of the "AUTHORS with commits in the past year" part of your script: - -----8<---- -savbu-usnic-a ~/g/ompi-svn-mirror git:main ❎❎❎ git log --all --since='1 year ago' --pretty=tformat:'%ae' | sort | uniq -c - 39 adrian@open-mpi-git-mirror.example.com - 3 alex@open-mpi-git-mirror.example.com - 5 alinas@open-mpi-git-mirror.example.com - 9 amikheev@open-mpi-git-mirror.example.com - 90 bosilca@open-mpi-git-mirror.example.com - 22 brbarret@open-mpi-git-mirror.example.com - 5 devendar@open-mpi-git-mirror.example.com - 60 dgoodell@open-mpi-git-mirror.example.com - 8 edgar@open-mpi-git-mirror.example.com - 48 ggouaillardet@open-mpi-git-mirror.example.com - 4 hadi@open-mpi-git-mirror.example.com - 295 hjelmn@open-mpi-git-mirror.example.com - 4 hpcchris@open-mpi-git-mirror.example.com - 2 hppritcha@open-mpi-git-mirror.example.com - 28 jladd@open-mpi-git-mirror.example.com - 2 jroman@open-mpi-git-mirror.example.com - 697 jsquyres@open-mpi-git-mirror.example.com - 11 jurenz@open-mpi-git-mirror.example.com - 3 manjugv@open-mpi-git-mirror.example.com - 209 miked@open-mpi-git-mirror.example.com - 24 mpiteam@open-mpi-git-mirror.example.com - 4 naughtont@open-mpi-git-mirror.example.com - 50 osvegis@open-mpi-git-mirror.example.com - 2 pasha@open-mpi-git-mirror.example.com - 5 regrant@open-mpi-git-mirror.example.com - 6 rfaucett@open-mpi-git-mirror.example.com - 1516 rhc@open-mpi-git-mirror.example.com - 79 rolfv@open-mpi-git-mirror.example.com - 2 swise@open-mpi-git-mirror.example.com - 7 vasily@open-mpi-git-mirror.example.com - 1 vvenkatesan@open-mpi-git-mirror.example.com - 10 yosefe@open-mpi-git-mirror.example.com -----8<---- - -And the "NO commits": -----8<---- -savbu-usnic-a ~/g/ompi-svn-mirror git:main ❯❯❯ git log --all --since='1 year ago' --pretty=tformat:'%ae' | sort | uniq > /tmp/active ; git log --pretty=tformat:'%ae' --all | sort | uniq > /tmp/all ; diff -u -/tmp/all /tmp/active | grep '^-[^-]' --abbyz@open-mpi-git-mirror.example.com --adi@open-mpi-git-mirror.example.com --adkulkar@open-mpi-git-mirror.example.com --afriedle@open-mpi-git-mirror.example.com --alekseys@open-mpi-git-mirror.example.com --alexma@open-mpi-git-mirror.example.com --angskun@open-mpi-git-mirror.example.com --Anya@open-mpi-git-mirror.example.com --apant@open-mpi-git-mirror.example.com --bbenton@open-mpi-git-mirror.example.com --bouteill@open-mpi-git-mirror.example.com --casswell@open-mpi-git-mirror.example.com --coti@open-mpi-git-mirror.example.com -[...] -----8<---- - diff --git a/contrib/annual-maintenance/find-committers-from-last-year.pl b/contrib/annual-maintenance/find-committers-from-last-year.pl deleted file mode 100755 index 2b9110fb2df..00000000000 --- a/contrib/annual-maintenance/find-committers-from-last-year.pl +++ /dev/null @@ -1,114 +0,0 @@ -#!/usr/bin/env perl - -use strict; - -use Data::Dumper; - -########################################################################## - -# Read in the AUTHORS file - -die "Run this script at the top of an OMPI SVN tree" - if (! -r "AUTHORS"); - -print "Reading AUTHORS file...\n"; -open(AUTHORS, "AUTHORS") || die "Can't open AUTHORS file"; -my $in = 0; -my $authors; -my $count = 0; -while () { - chomp; - - if ($in) { - if (length($_) == 0) { - $in = 0; - next; - } - - # There's probably a good regexp that will sort this better, - # but I'm a little too lazy at the moment. :-( So just use - # fixed widths for the name and affiliation fields, and then - # strip off trailing whitespace. - m/(\S+)\s+/; - my $username = $1; - my $name = substr($_, 14, 27); - my $affiliation = substr($_, 42); - $name =~ s/(\s+)$//; - $affiliation =~ s/(\s+)$//; - - $authors->{$username} = { - username => $username, - name => $name, - affiliation => $affiliation, - active => 0, - }; - ++$count; - } - - else { - $in = 1 - if (/^------.+ .+ .+-----$/); - } -} -close(AUTHORS); -print "Read $count authors from AUTHORS file\n"; - -########################################################################## - -# Read committers from SVN log over the past year - -my $trunk = "https://svn.open-mpi.org/svn/ompi/trunk"; - -my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = - localtime(time); - -$year += 1900; -my $today = sprintf("%04d-%02d-%02d", $year, $mon+1, $mday); -my $year_ago = sprintf("%04d-%02d-%02d", $year-1, $mon+1, $mday); - -$count = 0; -my $committers; -print "Reading SVN log to find committers over past year...\n"; -open(SVN, "svn log -v -r '{$today}:{$year_ago}' $trunk|") || - die "Can't open svn log"; -while () { - if (m/^(r\d+) \| (\S+) \| (\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)/) { - print "Indexed $1 ($3)\n"; - - if (!exists($committers->{$2})) { - $committers->{$2} = 1; - ++$count; - } - - if (!exists($authors->{$2})) { - print "WARNING: committer $2 is not in AUTHORS!\n"; - } else { - $authors->{$2}->{active} = 1; - } - } -} -close(SVN); -print "Found $count SVN committers over the last year\n"; - -########################################################################## - -# Print list of committers over the past year - -print " -AUTHORS with commits in the past year: ---------------------------------------\n"; -foreach my $username (sort(keys(%{$authors}))) { - if ($authors->{$username}->{active}) { - print "$username -> $authors->{$username}->{name}, $authors->{$username}->{affiliation}\n"; - } -} - -print " -AUTHORS with NO commits in the past year: ------------------------------------------\n"; -foreach my $username (sort(keys(%{$authors}))) { - if (!$authors->{$username}->{active}) { - print "$username -> $authors->{$username}->{name}, $authors->{$username}->{affiliation}\n"; - } -} - diff --git a/contrib/build-mca-comps-outside-of-tree/btl_tcp2_endpoint.c b/contrib/build-mca-comps-outside-of-tree/btl_tcp2_endpoint.c index 385f645d062..339db08da89 100644 --- a/contrib/build-mca-comps-outside-of-tree/btl_tcp2_endpoint.c +++ b/contrib/build-mca-comps-outside-of-tree/btl_tcp2_endpoint.c @@ -122,7 +122,7 @@ static void mca_btl_tcp2_endpoint_send_handler(int sd, short flags, void* user); void mca_btl_tcp_endpoint_dump(mca_btl_base_endpoint_t* btl_endpoint, const char* msg) { - char src[64], dst[64], *status; + char src[INET_ADDRSTRLEN], dst[INET_ADDRSTRLEN], *status; int sndbuf, rcvbuf, nodelay, flags = -1; #if OPAL_ENABLE_IPV6 struct sockaddr_storage inaddr; @@ -144,7 +144,7 @@ void mca_btl_tcp_endpoint_dump(mca_btl_base_endpoint_t* btl_endpoint, const char } } #else - sprintf(src, "%s", inet_ntoa(inaddr.sin_addr)); + inet_ntop(AF_INET, &inaddr.sin_addr, src, sizeof(src)); #endif getpeername(btl_endpoint->endpoint_sd, (struct sockaddr*)&inaddr, &addrlen); #if OPAL_ENABLE_IPV6 @@ -156,7 +156,7 @@ void mca_btl_tcp_endpoint_dump(mca_btl_base_endpoint_t* btl_endpoint, const char } } #else - sprintf(dst, "%s", inet_ntoa(inaddr.sin_addr)); + inet_ntop(AF_INET, &inaddr.sin_addr, dst, sizeof(dst)); #endif if((flags = fcntl(btl_endpoint->endpoint_sd, F_GETFL, 0)) < 0) { diff --git a/contrib/openmpi-valgrind.supp b/contrib/openmpi-valgrind.supp index 0a3ba945658..ca7826a3bf2 100644 --- a/contrib/openmpi-valgrind.supp +++ b/contrib/openmpi-valgrind.supp @@ -67,22 +67,6 @@ # ############################################################### -# inet_ntoa on linux mallocs a static buffer. We can't free -# it, so we have to live with it -{ - linux_inet_ntoa - Memcheck:Leak - fun:malloc - fun:inet_ntoa -} -{ - linux_inet_ntoa_thread - Memcheck:Leak - fun:calloc - fun:pthread_setspecific - fun:inet_ntoa -} - ############################################################### # diff --git a/contrib/platform/hadoop/cisco b/contrib/platform/hadoop/cisco index ac506c30365..25a5e4f64e9 100644 --- a/contrib/platform/hadoop/cisco +++ b/contrib/platform/hadoop/cisco @@ -14,7 +14,6 @@ enable_mpi_fortran=no enable_mpi_cxx=no enable_mpi_cxx_seek=no enable_cxx_exceptions=no -enable_mpi_java=yes enable_per_user_config_files=no enable_script_wrapper_compilers=no enable_orterun_prefix_by_default=yes @@ -27,5 +26,3 @@ with_portals=no with_valgrind=no with_slurm=/opt/slurm/2.1.0 with_openib=no -with_jdk_bindir=/usr/lib/jvm/java-1.6.0/bin -with_jdk_headers=/usr/lib/jvm/java-1.6.0/include diff --git a/contrib/platform/hadoop/linux b/contrib/platform/hadoop/linux index 4cd76ed4db0..fd5be6316ec 100644 --- a/contrib/platform/hadoop/linux +++ b/contrib/platform/hadoop/linux @@ -15,7 +15,6 @@ enable_mpi_fortran=no enable_mpi_cxx=no enable_mpi_cxx_seek=no enable_cxx_exceptions=no -enable_mpi_java=yes enable_io_romio=no enable_mca_no_build=memchecker with_memory_manager=no diff --git a/contrib/platform/hadoop/mac b/contrib/platform/hadoop/mac index 844e1d4ec6c..31238c40483 100644 --- a/contrib/platform/hadoop/mac +++ b/contrib/platform/hadoop/mac @@ -13,12 +13,9 @@ enable_ipv6=no enable_mpi_fortran=no enable_mpi_cxx=no enable_mpi_cxx_seek=no -enable_mpi_java=yes enable_memchecker=no enable_mca_no_build=memchecker with_memory_manager=no with_devel_headers=yes with_xgrid=no with_slurm=no -with_jdk_bindir=/usr/bin -with_jdk_headers=/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers diff --git a/contrib/platform/intel/bend/ext b/contrib/platform/intel/bend/ext index 127e61f9e80..46467051dc6 100644 --- a/contrib/platform/intel/bend/ext +++ b/contrib/platform/intel/bend/ext @@ -15,7 +15,6 @@ enable_mpi_fortran=yes enable_mpi_cxx=no enable_mpi_cxx_seek=no enable_cxx_exceptions=no -enable_mpi_java=no enable_io_romio=no enable_contrib_no_build=libnbc with_memory_manager=no diff --git a/contrib/platform/intel/bend/gadget b/contrib/platform/intel/bend/gadget index 152f17f8191..87dc62ff358 100644 --- a/contrib/platform/intel/bend/gadget +++ b/contrib/platform/intel/bend/gadget @@ -16,7 +16,6 @@ enable_mpi_cxx=no enable_mpi_cxx_seek=no enable_cxx_exceptions=no enable_oshmem=no -enable_mpi_java=no enable_io_romio=no enable_builtin_atomics=no enable_contrib_no_build=libnbc diff --git a/contrib/platform/intel/bend/gadget-optimized b/contrib/platform/intel/bend/gadget-optimized index 99f459f31eb..365623c9f68 100644 --- a/contrib/platform/intel/bend/gadget-optimized +++ b/contrib/platform/intel/bend/gadget-optimized @@ -16,7 +16,6 @@ enable_mpi_cxx=no enable_mpi_cxx_seek=no enable_cxx_exceptions=no enable_oshmem=no -enable_mpi_java=no enable_io_romio=no enable_contrib_no_build=libnbc with_memory_manager=no diff --git a/contrib/platform/intel/bend/linux-optimized b/contrib/platform/intel/bend/linux-optimized index fa9350f4703..efffa66c26b 100644 --- a/contrib/platform/intel/bend/linux-optimized +++ b/contrib/platform/intel/bend/linux-optimized @@ -15,7 +15,6 @@ enable_mpi_fortran=no enable_mpi_cxx=no enable_mpi_cxx_seek=no enable_cxx_exceptions=no -enable_mpi_java=yes enable_io_romio=no enable_mca_no_build=memchecker enable_contrib_no_build=libnbc diff --git a/contrib/platform/intel/bend/mac-optimized b/contrib/platform/intel/bend/mac-optimized index d5e239ad73a..c1747db2cdf 100644 --- a/contrib/platform/intel/bend/mac-optimized +++ b/contrib/platform/intel/bend/mac-optimized @@ -14,7 +14,6 @@ enable_ipv6=no enable_mpi_fortran=no enable_mpi_cxx=no enable_mpi_cxx_seek=no -enable_mpi_java=yes enable_memchecker=no enable_mca_no_build=memchecker enable_contrib_no_build=libnbc @@ -22,6 +21,4 @@ with_memory_manager=no with_devel_headers=yes with_xgrid=no with_slurm=no -with_jdk_bindir=/usr/bin -with_jdk_headers=/System/Library/Frameworks/JavaVM.framework/Versions/Current/Headers with_mpi_param_check=no diff --git a/contrib/platform/intel/bend/ubuntu b/contrib/platform/intel/bend/ubuntu index 98df8b25777..49d3da0d813 100644 --- a/contrib/platform/intel/bend/ubuntu +++ b/contrib/platform/intel/bend/ubuntu @@ -15,7 +15,6 @@ enable_mpi_fortran=no enable_mpi_cxx=no enable_mpi_cxx_seek=no enable_cxx_exceptions=no -enable_mpi_java=no enable_io_romio=no enable_contrib_no_build=libnbc enable_install_libpmix=yes diff --git a/contrib/platform/lanl/macosx-dynamic-java b/contrib/platform/lanl/macosx-dynamic-java deleted file mode 100644 index 2cb0976cde5..00000000000 --- a/contrib/platform/lanl/macosx-dynamic-java +++ /dev/null @@ -1,21 +0,0 @@ -with_memory_manager=no -enable_mem_debug=yes -enable_mem_profile=no -enable_debug_symbols=yes -enable_binaries=yes -with_devel_headers=yes -enable_heterogeneous=no -enable_picky=yes -enable_debug=yes -enable_shared=yes -enable_static=no -enable_contrib_no_build=libnbc -with_xgrid=no -enable_io_romio=no -enable_ipv6=no -enable_mpi_fortran=no -enable_mpi_cxx=no -enable_mpi_cxx_seek=no -enable_mpi_java=yes -enable_memchecker=no -enable_mca_no_build=pml-cm,filem,pml-v diff --git a/contrib/platform/lanl/macosx-dynamic-java.conf b/contrib/platform/lanl/macosx-dynamic-java.conf deleted file mode 100644 index c888e678047..00000000000 --- a/contrib/platform/lanl/macosx-dynamic-java.conf +++ /dev/null @@ -1,60 +0,0 @@ -# -# Copyright (c) 2004-2005 The Trustees of Indiana University and Indiana -# University Research and Technology -# Corporation. All rights reserved. -# Copyright (c) 2004-2005 The University of Tennessee and The University -# of Tennessee Research Foundation. All rights -# reserved. -# Copyright (c) 2004-2005 High Performance Computing Center Stuttgart, -# University of Stuttgart. All rights reserved. -# Copyright (c) 2004-2005 The Regents of the University of California. -# All rights reserved. -# Copyright (c) 2006 Cisco Systems, Inc. All rights reserved. -# $COPYRIGHT$ -# -# Additional copyrights may follow -# -# $HEADER$ -# - -# This is the default system-wide MCA parameters defaults file. -# Specifically, the MCA parameter "mca_param_files" defaults to a -# value of -# "$HOME/.openmpi/mca-params.conf:$sysconf/openmpi-mca-params.conf" -# (this file is the latter of the two). So if the default value of -# mca_param_files is not changed, this file is used to set system-wide -# MCA parameters. This file can therefore be used to set system-wide -# default MCA parameters for all users. Of course, users can override -# these values if they want, but this file is an excellent location -# for setting system-specific MCA parameters for those users who don't -# know / care enough to investigate the proper values for them. - -# Note that this file is only applicable where it is visible (in a -# filesystem sense). Specifically, MPI processes each read this file -# during their startup to determine what default values for MCA -# parameters should be used. mpirun does not bundle up the values in -# this file from the node where it was run and send them to all nodes; -# the default value decisions are effectively distributed. Hence, -# these values are only applicable on nodes that "see" this file. If -# $sysconf is a directory on a local disk, it is likely that changes -# to this file will need to be propagated to other nodes. If $sysconf -# is a directory that is shared via a networked filesystem, changes to -# this file will be visible to all nodes that share this $sysconf. - -# The format is straightforward: one per line, mca_param_name = -# rvalue. Quoting is ignored (so if you use quotes or escape -# characters, they'll be included as part of the value). For example: - -# Disable run-time MPI parameter checking -# mpi_param_check = 0 - -# Note that the value "~/" will be expanded to the current user's home -# directory. For example: - -# Change component loading path -# component_path = /usr/local/lib/openmpi:~/my_openmpi_components - -# See "ompi_info --param all all" for a full listing of Open MPI MCA -# parameters available and their default values. -# - diff --git a/contrib/platform/mellanox/optimized b/contrib/platform/mellanox/optimized index f75b8e09c0f..fa476c4bd86 100644 --- a/contrib/platform/mellanox/optimized +++ b/contrib/platform/mellanox/optimized @@ -1,6 +1,10 @@ enable_mca_no_build=coll-ml,btl-uct enable_debug_symbols=yes -enable_orterun_prefix_by_default=yes +# orterun/mpirun-prefix-by-default is deprecated and intentionally not replaced +# by enable_prte_prefix_by_default here: platform variables are sourced into the +# top-level configure and do not reach the PRRTE sub-configure, while leaving +# the variable unset makes ompi_setup_prrte.m4 pass +# --enable-prte-prefix-by-default to PRRTE on its own. with_devel_headers=yes enable_oshmem=yes enable_oshmem_fortran=yes diff --git a/contrib/update-my-copyright.pl b/contrib/update-my-copyright.pl index 3ac826bfa32..1b3dd7a011f 100755 --- a/contrib/update-my-copyright.pl +++ b/contrib/update-my-copyright.pl @@ -269,6 +269,7 @@ sub quiet_print { # Returns a list of file names (relative to pwd) which git considers # to be modified. sub find_modified_files { + my %seen; my @files = (); # Number of path entries to remove from ${top}-relative paths. @@ -322,10 +323,73 @@ sub find_modified_files { my $relname = $fullname; $relname =~ s!^([^/]*/){$n_strip}!!g; - push @files, $relname - if (-f $relname); + if (-f $relname && !$seen{$relname}++) { + push @files, $relname; + } } } + # Also include files changed in commits on this branch that have not + # yet been pushed / are not in the base branch. This covers the common + # case of running the script after committing. + # + # Strategy: find a base ref whose merge-base with HEAD is not HEAD + # itself (i.e. there are actual commits on this branch). Try, in order: + # 1. The upstream tracking branch — but only if it is not the current + # branch pushed to a remote (which would give merge-base == HEAD). + # 2. origin/HEAD (the remote's default branch). + # 3. Well-known names: origin/main, origin/master, main, master. + my $head_sha = `git rev-parse HEAD 2>/dev/null`; + chomp($head_sha); + my $current_branch = `git rev-parse --abbrev-ref HEAD 2>/dev/null`; + chomp($current_branch); + + my $base_ref = ""; + my @candidates; + + # Upstream tracking branch (skip if it tracks the same branch on the remote) + my $upstream = `git rev-parse --abbrev-ref \@{upstream} 2>/dev/null`; + chomp($upstream); + if ($upstream) { + # e.g. "origin/bigcount-datatypes" tracks the same branch — skip it + my $upstream_branch = $upstream; + $upstream_branch =~ s!^[^/]+/!!; # strip "origin/" prefix + push @candidates, $upstream unless ($upstream_branch eq $current_branch); + } + + # Remote default branch and common well-known names (avoid origin/HEAD — + # it can be a stale symref pointing to the wrong branch) + push @candidates, "origin/main", "origin/master", "main", "master"; + + for my $candidate (@candidates) { + my $sha = `git rev-parse --verify $candidate 2>/dev/null`; + chomp($sha); + next unless $sha; + my $mb = `git merge-base HEAD $candidate 2>/dev/null`; + chomp($mb); + # Only useful if the merge-base is not HEAD itself + next unless ($mb && $mb ne $head_sha); + $base_ref = $candidate; + last; + } + + if ($base_ref) { + my $merge_base = `git merge-base HEAD $base_ref 2>/dev/null`; + chomp($merge_base); + quiet_print "==> Using base ref '$base_ref' (merge-base: $merge_base)\n"; + my $diff_cmd = "git diff --name-only --diff-filter=ACMR $merge_base HEAD -- ."; + quiet_print "==> Running: \"$diff_cmd\"\n"; + my @diff_files = split /\n/, `$diff_cmd`; + for my $fullname (@diff_files) { + my $relname = $fullname; + $relname =~ s!^([^/]*/){$n_strip}!!g; + if (-f $relname && !$seen{$relname}++) { + push @files, $relname; + } + } + } else { + quiet_print "==> WARNING: Could not determine base branch for branch diff\n"; + } + return @files; } diff --git a/docs/Makefile.am b/docs/Makefile.am index a6edc6ae045..0afec54eed0 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -1,6 +1,6 @@ # # Copyright (c) 2022 Cisco Systems, Inc. All rights reserved. -# Copyright (c) 2023-2025 Jeffrey M. Squyres. All rights reserved. +# Copyright (c) 2023-2026 Jeffrey M. Squyres. All rights reserved. # Copyright (c) 2025 Triad National Security, LLC. All rights reserved. # # $COPYRIGHT$ @@ -36,6 +36,8 @@ SPHINX_OPTS ?= -W --keep-going -j auto # However, it is necessary to list $(srcdir) when using wildcards. TEXT_SOURCE_FILES = \ $(srcdir)/license/*.txt +MARKDOWN_SOURCE_FILES = \ + $(top_srcdir)/.github/CODE_OF_CONDUCT.md IMAGE_SOURCE_FILES = \ $(srcdir)/openmpi_logo.png \ $(srcdir)/installing-open-mpi/required-support-libraries-dependency-graph.png \ @@ -62,12 +64,22 @@ RST_SOURCE_FILES = \ EXTRA_DIST = \ requirements.txt \ no-prrte-content.rst.txt \ + generate-code-of-conduct-rst.py \ generate-mpi-man3-bindings.py \ + generate-llm-docs.py \ + validate-llm-docs.py \ + ompi_docs_common.py \ mpi-standard-apis.json \ + llms-src/openmpi-mpi-api.schema.json \ + llms-src/openmpi-docs-manifest.schema.json \ + llms-src/openmpi-mpi-interface-guide.md \ + llms-src/openmpi-mpi-examples.md \ + llms-src/openmpi-runtime-introspection.md \ html \ man \ $(SPHINX_CONFIG) \ $(TEXT_SOURCE_FILES) \ + $(MARKDOWN_SOURCE_FILES) \ $(IMAGE_SOURCE_FILES) \ $(RST_SOURCE_FILES) @@ -190,6 +202,7 @@ OMPI_MAN3 = \ MPI_Errors.3 \ MPI_Exscan.3 \ MPI_Exscan_init.3 \ + MPI_F_sync_reg.3 \ MPI_Fetch_and_op.3 \ MPI_File_c2f.3 \ MPI_File_call_errhandler.3 \ @@ -455,10 +468,12 @@ OMPI_MAN3 = \ MPI_T_category_get_categories.3 \ MPI_T_category_get_cvars.3 \ MPI_T_category_get_events.3 \ + MPI_T_category_get_index.3 \ MPI_T_category_get_info.3 \ MPI_T_category_get_num.3 \ MPI_T_category_get_num_events.3 \ MPI_T_category_get_pvars.3 \ + MPI_T_cvar_get_index.3 \ MPI_T_cvar_get_info.3 \ MPI_T_cvar_get_num.3 \ MPI_T_cvar_handle_alloc.3 \ @@ -490,6 +505,7 @@ OMPI_MAN3 = \ MPI_T_finalize.3 \ MPI_T_init_thread.3 \ MPI_Topo_test.3 \ + MPI_T_pvar_get_index.3 \ MPI_T_pvar_get_info.3 \ MPI_T_pvar_get_num.3 \ MPI_T_pvar_handle_alloc.3 \ @@ -868,10 +884,22 @@ OMPI_MAN3_RST = $(OMPI_MAN3:%.3=man-openmpi/man3/%.3.rst) OMPI_MAN3_BUILT = $(OMPI_MAN3:%.3=$(MAN_OUTDIR)/%.3) OMPI_MAN3_INSTALL_FROM = $(OMPI_MAN3:%.3=$(MAN_INSTALL_FROM)/%.3) +# Generate this file from CODE_OF_CONDUCT.md as part of the Sphinx +# docs build. +CODE_OF_CONDUCT_RST = $(builddir)/code-of-conduct.rst + # Use this one file as a sentinel for building all the Open MPI man # page API bindings files SENTINEL_OMPI_MAN3_BINDING = $(builddir)/man-openmpi/man3/bindings/mpi_init.rst +# Stamp file marking a completed LLM-friendly documentation generator run. +# A stamp (touched unconditionally after the generator) is used rather than a +# generated artifact: the generator only rewrites files whose content changed, +# so an artifact's mtime would not advance when only *other* artifacts changed, +# and the HTML copy step below could be skipped. The stamp lives outside the +# llms-build/ staging tree so the generator's stale-file pruning leaves it. +SENTINEL_OMPI_LLM = $(builddir)/llms-build.stamp + OMPI_MAN7_RST = $(OMPI_MAN7:%.7=man-openmpi/man7/%.7.rst) OMPI_MAN7_BUILT = $(OMPI_MAN7:%.7=$(MAN_OUTDIR)/%.7) OMPI_MAN7_INSTALL_FROM = $(OMPI_MAN7:%.7=$(MAN_INSTALL_FROM)/%.7) @@ -890,8 +918,8 @@ OSHMEM_MAN3_INSTALL_FROM = $(OSHMEM_MAN3:%.3=$(MAN_INSTALL_FROM)/%.3) # Sphinx-generated man pages) -- they exist directly in Git. We # *always* want these files to be in EXTRA_DIST (so that they're in # distribution tarballs). We'll decide later whether we install each -# of these (e.g., if configure disabled the Java bindings, we won't -# install mpijavac.1). +# of these (e.g., if configure did not find a C++ compiler, we won't +# install mpic++.1). OMPI_MAN1_C_REDIRECTS = man-openmpi/man1/mpicc.1 OMPI_MAN1_CXX_REDIRECTS = \ man-openmpi/man1/mpicxx.1 \ @@ -900,7 +928,6 @@ OMPI_MAN1_FORTRAN_REDIRECTS = \ man-openmpi/man1/mpifort.1 \ man-openmpi/man1/mpif77.1 \ man-openmpi/man1/mpif90.1 -OMPI_MAN1_JAVA_REDIRECTS = man-openmpi/man1/mpijavac.1 OSHMEM_MAN1_C_REDIRECTS = \ man-openshmem/man1/oshcc.1 \ @@ -918,7 +945,6 @@ EXTRA_DIST += \ $(OMPI_MAN1_C_REDIRECTS) \ $(OMPI_MAN1_CXX_REDIRECTS) \ $(OMPI_MAN1_FORTRAN_REDIRECTS) \ - $(OMPI_MAN1_JAVA_REDIRECTS) \ $(OSHMEM_MAN1_C_REDIRECTS) \ $(OSHMEM_MAN1_CXX_REDIRECTS) \ $(OSHMEM_MAN1_FORTRAN_REDIRECTS) @@ -950,7 +976,7 @@ man: $(ALL_MAN_BUILT) # Remove the copies of the built HTML and man pages to get back to a # clean git clone. maintainer-clean-local: - rm -rf html man man-openmpi/man3/bindings + rm -rf html man man-openmpi/man3/bindings llms-build llms-build.stamp $(CODE_OF_CONDUCT_RST) # If we're doing a VPATH build, we may have "html" and "man" # directories in the build tree (e.g., if we did "make dist"). Remove @@ -1032,15 +1058,41 @@ $(builddir)/man-openmpi/man3/bindings: $(SENTINEL_OMPI_MAN3_BINDING): $(builddir)/man-openmpi/man3/bindings $(SENTINEL_OMPI_MAN3_BINDING): generate-mpi-man3-bindings.py +$(SENTINEL_OMPI_MAN3_BINDING): ompi_docs_common.py $(SENTINEL_OMPI_MAN3_BINDING): mpi-standard-apis.json $(OMPI_V_GEN) $(PYTHON3) $(srcdir)/generate-mpi-man3-bindings.py \ --srcdir $(srcdir) --builddir $(builddir) +# Generate the LLM-friendly artifacts into the build-tree staging +# directory. This must run after the man3 bindings (whose generated RST +# the LLM generator include-expands) and before Sphinx (which copies the +# staging tree into the HTML output, below). +$(SENTINEL_OMPI_LLM): generate-llm-docs.py +$(SENTINEL_OMPI_LLM): ompi_docs_common.py +$(SENTINEL_OMPI_LLM): mpi-standard-apis.json +$(SENTINEL_OMPI_LLM): llms-src/openmpi-mpi-interface-guide.md +$(SENTINEL_OMPI_LLM): llms-src/openmpi-mpi-examples.md +$(SENTINEL_OMPI_LLM): llms-src/openmpi-runtime-introspection.md +$(SENTINEL_OMPI_LLM): llms-src/openmpi-mpi-api.schema.json +$(SENTINEL_OMPI_LLM): llms-src/openmpi-docs-manifest.schema.json +$(SENTINEL_OMPI_LLM): $(RST_SOURCE_FILES) +$(SENTINEL_OMPI_LLM): $(SENTINEL_OMPI_MAN3_BINDING) + $(OMPI_V_GEN) $(PYTHON3) $(srcdir)/generate-llm-docs.py \ + --srcdir $(srcdir) --builddir $(builddir) + @touch $@ + +$(CODE_OF_CONDUCT_RST): $(top_srcdir)/.github/CODE_OF_CONDUCT.md +$(CODE_OF_CONDUCT_RST): generate-code-of-conduct-rst.py + $(OMPI_V_GEN) $(PYTHON3) $(srcdir)/generate-code-of-conduct-rst.py \ + --input $(top_srcdir)/.github/CODE_OF_CONDUCT.md --output $@ + $(ALL_MAN_BUILT): $(builddir)/prrte-rst-content $(ALL_MAN_BUILT): $(builddir)/schizo-ompi-rst-content/schizo-ompi-cli.rstxt $(ALL_MAN_BUILT): $(RST_SOURCE_FILES) $(IMAGE_SOURCE_FILES) $(ALL_MAN_BUILT): $(TEXT_SOURCE_FILES) $(SPHINX_CONFIG) +$(ALL_MAN_BUILT): $(CODE_OF_CONDUCT_RST) $(ALL_MAN_BUILT): $(SENTINEL_OMPI_MAN3_BINDING) +$(ALL_MAN_BUILT): $(SENTINEL_OMPI_LLM) # Render the RST source into both 1) full HTML docs and 2) nroff man # pages. @@ -1103,6 +1155,7 @@ $(ALL_MAN_BUILT): OPAL_USING_INTERNAL_PMIX="$(OPAL_USING_INTERNAL_PMIX)" \ OMPI_PRRTE_DOCS_URL_BASE="$(OMPI_PRRTE_DOCS_URL_BASE)" \ OMPI_USING_INTERNAL_PRRTE="$(OMPI_USING_INTERNAL_PRRTE)" \ + OMPI_LLM_BUILDDIR="$(builddir)" \ $(SPHINX_BUILD) -M html "$(builddir)" "$(OUTDIR)" $(SPHINX_OPTS) $(OMPI_V_SPHINX_HTML) \ OMPI_TOP_SRCDIR="$(top_srcdir)" \ @@ -1129,8 +1182,35 @@ linkcheck: # # Also, if this is a VPATH build, then we made a copy of a bunch of # RST source files to the build tree. So delete all of those, too. +# Validate the generated LLM-friendly artifacts (JSON Schema, cross-field +# invariants, no unresolved RST, manifest integrity, sample sync) and confirm +# the generator is deterministic at a fixed SOURCE_DATE_EPOCH. Runs as part of +# "make check"; depends on the LLM-docs generation sentinel so the artifacts are +# always present (a missing/failed generation now fails the check loudly rather +# than being silently skipped). +check-local: $(SENTINEL_OMPI_LLM) + @$(PYTHON3) $(srcdir)/validate-llm-docs.py \ + --srcdir $(srcdir) --llms-dir $(builddir)/llms-build/llms \ + --samples $(srcdir)/../specs/llms-friendly-docs/sample-records.jsonl && \ + rm -rf $(builddir)/llms-det-a $(builddir)/llms-det-b && \ + SOURCE_DATE_EPOCH=1700000000 $(PYTHON3) $(srcdir)/generate-llm-docs.py \ + --srcdir $(srcdir) --builddir $(builddir) \ + --outdir $(builddir)/llms-det-a > /dev/null && \ + SOURCE_DATE_EPOCH=1700000000 $(PYTHON3) $(srcdir)/generate-llm-docs.py \ + --srcdir $(srcdir) --builddir $(builddir) \ + --outdir $(builddir)/llms-det-b > /dev/null && \ + if diff -r $(builddir)/llms-det-a $(builddir)/llms-det-b > /dev/null; then \ + echo " CHECK LLM docs: deterministic at fixed SOURCE_DATE_EPOCH"; \ + rm -rf $(builddir)/llms-det-a $(builddir)/llms-det-b; \ + else \ + echo "ERROR: LLM docs are not deterministic" >&2; \ + rm -rf $(builddir)/llms-det-a $(builddir)/llms-det-b; exit 1; \ + fi + clean-local: rm -rf $(OUTDIR) + rm -rf llms-build llms-build.stamp llms-det-a llms-det-b + rm -f $(CODE_OF_CONDUCT_RST) rm -rf prrte-rst-content schizo-ompi-rst-content rm -rf ompi-prrte-objects.inv opal-pmix-objects.inv if test "$(srcdir)" != "$(builddir)"; then \ @@ -1150,7 +1230,7 @@ clean-local: # macro. This hooks into the normal Automake build mechanisms, and # will ultimately cause the invocation of the above rule that runs # Sphinx to build the HTML and man pages. -BUILT_SOURCES = $(ALL_MAN_BUILT) +BUILT_SOURCES = $(CODE_OF_CONDUCT_RST) $(ALL_MAN_BUILT) endif OPAL_BUILD_DOCS @@ -1166,9 +1246,6 @@ endif if OMPI_BUILD_ANY_FORTRAN_BINDINGS man1_MANS += $(OMPI_MAN1_FORTRAN_REDIRECTS) endif -if OMPI_WANT_JAVA_BINDINGS -man1_MANS += $(OMPI_MAN1_JAVA_REDIRECTS) -endif man3_MANS = $(OMPI_MAN3_INSTALL_FROM) man7_MANS = $(OMPI_MAN7_INSTALL_FROM) diff --git a/docs/app-debug/index.rst b/docs/app-debug/index.rst index 91bf934b6c4..b3b18faa3bd 100644 --- a/docs/app-debug/index.rst +++ b/docs/app-debug/index.rst @@ -8,7 +8,7 @@ logic errors, uninitialized variables, storage overlays and timing problems. Debugging a parallel application can be further complicated -by problems that can include additional race conditions and aysynchronous +by problems that can include additional race conditions and asynchronous events, as well as understanding execution of multiple application processes running simultaneously. diff --git a/docs/app-debug/lost-output.rst b/docs/app-debug/lost-output.rst index 57af5cd5f25..9093d9f4dd0 100644 --- a/docs/app-debug/lost-output.rst +++ b/docs/app-debug/lost-output.rst @@ -18,7 +18,7 @@ MPI process when it displays the error message. If the process's memory is already corrupted, Open MPI's attempt to allocate memory may fail and the process will simply terminate, possibly silently. When Open MPI does not attempt to aggregate error messages, most of its setup -work is done when the MPI library is initiaized and no memory is allocated +work is done when the MPI library is initialized and no memory is allocated during the "print the error" routine. It therefore almost always successfully outputs error messages in real time |mdash| but at the expense that you'll potentially see the same error message for *each* MPI process that diff --git a/docs/building-apps/abi-compatibility.rst b/docs/building-apps/abi-compatibility.rst index 458362ca0ef..325379d97bf 100644 --- a/docs/building-apps/abi-compatibility.rst +++ b/docs/building-apps/abi-compatibility.rst @@ -34,7 +34,7 @@ Open MPI v4.x might not execute correctly with Open MPI |ompi_series|. compilers (e.g., GNU Fortran >= v4.9), Open MPI v5.0.0 removed the names from the MPI interfaces when there is only a single subroutine in the interface, and that subroutine name exactly - matches the iterface name. This change is likely to make Open MPI + matches the interface name. This change is likely to make Open MPI |ompi_series|'s ``mpi`` module bindings *less* restrictive than Open MPI v4.x, but it *may* also have ABI implications, depending on your Fortran compiler. diff --git a/docs/building-apps/deprecation-warnings.rst b/docs/building-apps/deprecation-warnings.rst index dabf456978d..0a0da77ea78 100644 --- a/docs/building-apps/deprecation-warnings.rst +++ b/docs/building-apps/deprecation-warnings.rst @@ -217,7 +217,7 @@ but the usage differs slightly. See the example below. // Create an info object using MPI_Info_create() ... - // Retrieve the the value of a provided key later in the code + // Retrieve the value of a provided key later in the code char key[] = "my_key"; char value[64]; int valuelen=64; @@ -248,7 +248,7 @@ Please refer to the example shown in :ref:`MPI_INFO_GET `. MPI_Sizeof ---------- -The ``MPI_SIZEOF`` construct in Fortran has been deprected since there +The ``MPI_SIZEOF`` construct in Fortran has been deprecated since there are standard Fortran language constructs such as ``c_sizeof`` and ``storage_size`` that can be used instead. diff --git a/docs/building-apps/removed-mpi-constructs.rst b/docs/building-apps/removed-mpi-constructs.rst index 4dfa5b77293..d64f7a66780 100644 --- a/docs/building-apps/removed-mpi-constructs.rst +++ b/docs/building-apps/removed-mpi-constructs.rst @@ -448,7 +448,7 @@ If we run the above, we get an output of: The ``MPI_TYPE_RESIZED`` function allows us to take any arbitrary datatype and set the lower bound and extent directly (which indirectly -sets the upper bound), without needing to setup the arrays and +sets the upper bound), without needing to set up the arrays and computing the displacements necessary to invoke ``MPI_TYPE_CREATE_STRUCT``. diff --git a/docs/conf.py b/docs/conf.py index b6e85216a5f..be5a5da9fad 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -14,12 +14,23 @@ # -- Project information ----------------------------------------------------- -import os import re import datetime import sphobjinv as soi -year = datetime.datetime.now().year +# For reproducible builds, derive the copyright year from SOURCE_DATE_EPOCH +# when it is set (mirroring config/getdate.sh and Sphinx's own format_date, +# which already honor it for the "Last updated on:" footer); otherwise fall +# back to the current wall-clock year. See https://reproducible-builds.org/. +_source_date_epoch = os.environ.get('SOURCE_DATE_EPOCH') +try: + if _source_date_epoch: + year = datetime.datetime.fromtimestamp( + int(_source_date_epoch), datetime.timezone.utc).year + else: + year = datetime.datetime.now().year +except (ValueError, OverflowError, OSError): + year = datetime.datetime.now().year project = 'Open MPI' copyright = f'2003-{year}, The Open MPI Community' @@ -309,8 +320,20 @@ def _make_intersphinx_mapping(project, name, fallback_base, entries): # Hence, listing prrte-rst-content in exclude_patterns means that # Sphinx won't complain about the .rst files in that tree that we are # not referencing from here in the OMPI docs. +# Note: llms-src/ holds the committed curated Markdown sources for the +# LLM-friendly docs, and llms-build/ is the generated LLM artifact staging +# tree. Both contain Markdown that must NOT be picked up as Sphinx source +# documents; they are published into the HTML output by a separate copy step +# in docs/Makefile.am (not via html_extra_path, which exclude_patterns would +# also suppress). +# +# The html/ and man/ directories are the pre-rendered output copies that ship +# in distribution tarballs (see html-local / man in Makefile.am). Now that +# html/llms/ contains generated Markdown, html/ (and man/, for symmetry) must +# also be excluded so Sphinx does not re-parse those copies as source. exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', 'venv', 'py*/**', - 'prrte-rst-content' ] + 'tuning-apps/_include', 'prrte-rst-content', + 'llms-src', 'llms-build', 'html', 'man' ] # Clarify the language for verbatim blocks (::) @@ -429,3 +452,48 @@ def _doit(topdir): .wy-table-responsive table td,.wy-table-responsive table th{white-space:normal} """ + +# -- LLM-friendly documentation artifacts ------------------------------------ + +# The LLM-friendly artifacts are generated into a build-tree staging directory +# (/llms-build/) by docs/generate-llm-docs.py -- run from the Makefile +# (the SENTINEL_OMPI_LLM target) for "make" builds, and from +# .readthedocs-pre-create-environment.sh for Read the Docs builds (which do not +# run "make"). They are deliberately excluded from Sphinx source discovery +# (see exclude_patterns above), so we publish them by copying the staging tree +# into the HTML output in a "build-finished" hook. Doing the copy here (rather +# than in the Makefile) means it happens inside sphinx-build and therefore +# works identically under "make" and on Read the Docs. + +def _copy_llm_artifacts(app, exception): + import shutil + + if exception is not None or app.builder.name != "html": + return + + # The generator writes the staging tree to /llms-build, where + # is the docs build directory. The Makefile html recipe exports + # OMPI_LLM_BUILDDIR so we resolve it explicitly even in an out-of-tree + # (VPATH) build where the build dir differs from the source dir. Fall back + # to app.confdir, which equals the build dir under both "make" (the recipe + # copies conf.py into builddir and runs sphinx-build there) and Read the + # Docs (sphinx-build runs in the source dir, where the generator also ran). + builddir = os.environ.get("OMPI_LLM_BUILDDIR", app.confdir) + staging = os.path.join(builddir, "llms-build") + src_tree = os.path.join(staging, "llms") + if not os.path.isdir(src_tree): + # Generator was not run (e.g., a bare sphinx-build); nothing to publish. + return + + dst_tree = os.path.join(app.outdir, "llms") + if os.path.isdir(dst_tree): + shutil.rmtree(dst_tree) + shutil.copytree(src_tree, dst_tree) + + src_txt = os.path.join(staging, "llms.txt") + if os.path.isfile(src_txt): + shutil.copy2(src_txt, os.path.join(app.outdir, "llms.txt")) + + +def setup(app): + app.connect("build-finished", _copy_llm_artifacts) diff --git a/docs/developers/autogen.rst b/docs/developers/autogen.rst index 8239719623e..a740be9ddd1 100644 --- a/docs/developers/autogen.rst +++ b/docs/developers/autogen.rst @@ -4,7 +4,7 @@ Running ``autogen.pl`` You can now run OMPI's top-level ``autogen.pl`` script. This script will invoke the GNU Autoconf, Automake, and Libtool commands in the proper order and do a bunch of component discovery and housekeeping to -setup to run OMPI's top-level ``configure`` script. +set up to run OMPI's top-level ``configure`` script. Running ``autogen.pl`` may take a few minutes, depending on your system. It's not very exciting to watch. diff --git a/docs/developers/building-open-mpi.rst b/docs/developers/building-open-mpi.rst index ee09d294e20..e53b5a2d767 100644 --- a/docs/developers/building-open-mpi.rst +++ b/docs/developers/building-open-mpi.rst @@ -1,9 +1,107 @@ Building Open MPI ================= +General +------- + Once you have run ``autogen.pl`` successfully, you can configure and build Open MPI just like end users do with official distribution Open MPI tarballs. See the :doc:`general "Install Open MPI" documentation for more details. ` + +Building Against External OpenPMIx / PRRTE +------------------------------------------ + +One thing that developers and/or packagers may need to do is to build +Open MPI against an external OpenPMIx installation, and/or configure +Open MPI's ``mpirun`` / ``mpiexec`` launchers to use an external PRRTE +installation (i.e., installations that were not built from the +embedded copies inside the Open MPI source tree / Git submodules). + +With regards to :doc:`Open MPI's required dependent libraries +` (Hwloc, Libevent, +OpenPMIx, and PRRTE), it generally is simplest to build Open MPI in +one of two ways: + +#. Build and use all the **internal** copies of Open MPI's required + dependent libraries. + + * Specifically: use the Hwloc, Libevent, OpenPMIx, and PRRTE source + trees that are bundled in with Open MPI's source code. + +#. Build and use all **external** copies of Open MPI's required + dependent libraries. + + * Specifically: ignore the Hwloc, Libevent, OpenPMIx, and PRRTE source + trees that are bundled in with Open MPI's source code, and, + instead, build Open MPI against already-installed Hwloc, + Libevent, and OpenPMIx libraries, and configure Open MPI's + launchers to use an already-installed PRRTE. + +Other variations are possible, but can get tricky and complicated +because Open MPI, the OpenPMIx library that it uses, Hwloc, and +Libevent can be loaded into the same process. They are therefore not +recommended unless you understand the run-time linker consequences. + +Some facts that are relevant to know when building against an external +OpenPMIx / PRRTE: + +1. Open MPI and the OpenPMIx library that Open MPI links against must + be built against the **same** installation of Hwloc and Libevent. + Meaning: + + * Assumedly the external OpenPMIx was built against external Hwloc + and Libevent. Open MPI **must** compile and link against the + **same** Hwloc and Libevent that the external OpenPMIx was built + against. + + .. admonition:: Critical + :class: Danger + + Open MPI and the OpenPMIx library that it links against must + use the same Hwloc and Libevent libraries at run time (e.g., + they must resolve to the same run-time loadable libraries at + run time). + + .. important:: This statement applies regardless of whether + Open MPI -- and/or the other libraries -- are + built as static or dynamically-loadable + libraries. + + * Unless you really know what you are doing, this usually means + building and installing Open MPI against the same installation + tree(s) of Hwloc and Libevent that OpenPMIx used to build itself. + + For example, consider an environment where you install Hwloc, + Libevent, OpenPMIx, and PRRTE via the operating system's package + manager. Assuming that the package-manager install of OpenPMIx + was built against the package-manager-provided Hwloc and + Libevent, then Open MPI will *also* need to be built against the + package-manager-provided Hwloc and Libevent. To build Open MPI + this way, you may need to install the package manager's + "developer" Hwloc, Libevent, and OpenPMIx packages. + +1. PRRTE and the OpenPMIx library that PRRTE uses must be built + against the **same** installation of Hwloc and Libevent. + + This is a separate requirement from Open MPI's requirement above: + Open MPI does not link against PRRTE, and MPI applications do not + load ``libprrte``. Therefore, PRRTE's Hwloc, Libevent, and + OpenPMIx dependencies do not have to match Open MPI's dependencies + merely because Open MPI uses PRRTE as a launcher. + +1. Open MPI and PRRTE do **not** have to use the same OpenPMIx + installation. + + PMIx supports cross-version operations, so Open MPI and PRRTE can + use different OpenPMIx installations, and those installations do + not need to be the same OpenPMIx version. + + If Open MPI and PRRTE do use the **same** OpenPMIx installation, + then the requirements above mean that Open MPI, PRRTE, OpenPMIx, + Hwloc, and Libevent will all use the same Hwloc and Libevent + installations. However, this is a consequence of sharing one + OpenPMIx installation; it is not a requirement that Open MPI and + PRRTE share one OpenPMIx installation. diff --git a/docs/developers/index.rst b/docs/developers/index.rst index 6e82d253d37..ca98bea2925 100644 --- a/docs/developers/index.rst +++ b/docs/developers/index.rst @@ -23,3 +23,4 @@ probably don't need to read this section. sphinx rst-for-markdown-expats.rst bindings + llm-friendly-docs diff --git a/docs/developers/llm-friendly-docs.rst b/docs/developers/llm-friendly-docs.rst new file mode 100644 index 00000000000..0e64250752b --- /dev/null +++ b/docs/developers/llm-friendly-docs.rst @@ -0,0 +1,389 @@ +LLM-friendly documentation artifacts +==================================== + +In addition to the human-facing HTML and Unix man pages, the Open MPI +documentation build publishes machine-readable, LLM-friendly artifacts for +the public MPI APIs (C, ``mpif.h``, ``use mpi``, and ``use mpi_f08``). They +are indexed from a top-level ``llms.txt`` and published under ``llms/`` in +each documentation version (for example, +``https://docs.open-mpi.org/en//llms.txt``, where ```` is +the Read the Docs version slug, such as ``v6.1.x`` or ``main``). + +The intent is to give LLMs, retrieval systems, and coding assistants concise, +authoritative, version-correct MPI API information --- a signature, its +parameters, the right language interface, and a link back to the human docs --- +*without* scraping themed HTML or guessing from filenames, and *without* +creating a second hand-maintained API reference that could drift from the real +documentation. + +This page is for Open MPI developers and release managers who maintain these +artifacts. It records the design intent and the day-to-day maintenance rules; +the full design record (every field, every alternative considered) lives in +``specs/llms-friendly-docs/spec.md`` in the source tree. + +Background: the llms.txt convention +----------------------------------- + +``llms.txt`` is a convention proposed by Jeremy Howard in September 2024 +(`llmstxt.org `_) for exposing LLM-friendly content at a +well-known location --- a Markdown file at the site root (``/llms.txt``), +alongside the established ``robots.txt`` and ``sitemap.xml``. The bare +convention is an *index*: an H1 project name, a short summary, and +H2-delimited lists of Markdown links, optionally with companion ``.md`` +versions of HTML pages. + +`Read the Docs supports this convention +`_: it +does **not** auto-generate the file, but if a built, public, active default +version contains an ``llms.txt`` in its HTML output, Read the Docs serves it at +the project root. + +Open MPI **extends** the convention rather than merely conforming to it. A bare +link index is not enough for a tool that needs to answer signature, parameter, +and interface questions programmatically, so alongside the ``llms.txt`` index +Open MPI also publishes a structured JSONL API catalog, language-specific +Markdown corpora, per-symbol Markdown pages, and a machine-readable manifest +(see `What is generated`_). The ``llms.txt`` index itself follows the spirit of +the convention (an H1 name, a summary, and link sections) but adds prose that an +automated consumer needs --- most importantly, a description of the URL scheme +for *other* versions (see `Read the Docs and the version-neutral llms.txt`_). + +Design principles +----------------- + +Five cross-cutting principles explain most of the decisions below. When in +doubt, preserve these properties. + +* **The existing RST man pages and the MPI Forum JSON binding metadata are the + only source of truth.** A core goal is to *avoid a second hand-maintained + API reference*. The generated artifacts are derived, never authored: the set + of documented procedures comes from the ``man-openmpi/man3/MPI_*.3.rst`` man + pages and the command corpus from the ``man-openmpi/man1/*.rst`` pages, while + standard-API signatures/parameters come from the MPI Forum metadata + (``mpi-standard-apis.json`` via the embedded ``pympistandard`` library). This + is why adding a new API is a matter of adding a man page, not editing the + generator (see `Updating the documentation when APIs change`_), and why the + curated examples reuse the top-level ``examples/`` tree instead of copying it. + +* **Each artifact's content hash is a pure function of its semantic content.** + Build-identity fields (``git_commit``, ``git_describe``, ``generated_at``) + live **only** in the manifest --- never in the catalog records, corpora, or + per-symbol pages. If a commit hash were embedded in every record, every + artifact's hash would change on every repository commit, even one that touches + no documentation, making the manifest's per-artifact hashes useless as change + detectors. Keeping build identity in one place gives consumers the clean + property *"hash changed* ⇔ *documentation content changed."* This is also why + the versioned ``llms.txt`` is timestamp-free (it carries the version, not a + generation time) and why the manifest is the one artifact that does not + inventory itself. + +* **Reproducible under Open MPI's existing knob.** The build fits the project's + established reproducible-builds model rather than inventing a new one: the + semantic artifacts contain no wall-clock data and are byte-identical across + reruns at a given commit; only the manifest's ``generated_at`` varies, and it + is derived from ``SOURCE_DATE_EPOCH`` when set (see `Reproducibility and the + per-release manifest`_). + +* **The schemas are simultaneously the published contract and the CI + validator.** ``docs/llms-src/*.schema.json`` are shipped as artifacts *and* + used directly by ``make check``, so the contract and the check cannot drift. + They are "open" objects (no ``additionalProperties: false``) so that an + additive field never breaks a consumer validating against an older cached + copy of the schema (see `Schema evolution`_). + +* **The artifacts ride inside the HTML output tree.** Rather than a parallel + distribution mechanism, the ``llms/`` tree and ``llms.txt`` are copied into + the Sphinx HTML output, so they inherit the existing HTML packaging, + installation, and tarball machinery for free, and the link strategy follows + the build type (see `How they are built`_ and `Link strategy: relative vs. + absolute links`_). + +What is generated +----------------- + +* ``llms/openmpi-mpi-api.jsonl`` --- one JSON record per documented MPI + procedure (standard, extension, and deprecated/removed). A record is + per *procedure*, not per page. +* ``llms/openmpi-mpi-api.md`` and the four per-interface corpora (C, ``mpif.h``, + ``use mpi``, ``use mpi_f08``). ``mpif.h`` and ``use mpi`` share the same + ``f90`` signature and differ only in the access preamble, so those two + corpora are near-duplicates by design; they are kept separate for audience + clarity and possible future divergence. +* ``llms/man-openmpi/man3/MPI_.3.md`` --- one Markdown page per man page, + 1:1 with the human man pages so canonical URLs line up. Overview/non-procedure + pages (e.g. ``MPI_T.3``, ``MPI_Errors.3``) get a Markdown page but no JSONL + record. +* ``llms/man-openmpi/man1/.1.md`` --- one Markdown page per command man + page (``mpirun``, ``ompi_info``, the wrapper compilers, ...). These document + Open MPI *commands*, not MPI APIs, so they are a Markdown corpus only --- + there are no JSONL catalog records for them. +* ``llms/openmpi-docs-manifest.json`` --- the artifact inventory; the only + artifact that carries build identity (git commit/describe and + ``generated_at``). +* The curated ``llms/openmpi-mpi-interface-guide.md``, + ``llms/openmpi-mpi-examples.md``, and + ``llms/openmpi-runtime-introspection.md`` (hand-written sources under + ``docs/llms-src/``), plus the two published ``*.schema.json`` files. The + runtime-introspection guide tells a consumer how to query an *installed* + Open MPI with ``ompi_info`` --- its version, build configuration, available + MCA components, and the run-time MCA parameters those components expose. That + surface is **installation-specific** (it depends on which components were + built), so it is intentionally *not* snapshotted into the corpus; an + ``ompi_info --all --parsable`` snapshot would bake in one machine's paths and + component set, immediately drift, and could not be produced at all on Read the + Docs (which builds the docs without a full Open MPI install). The corpus + therefore points consumers at the live, self-describing command output + instead. + +How they are built +------------------ + +``docs/generate-llm-docs.py`` produces the artifacts into a build-tree staging +directory (``docs/llms-build/``). It is run in both documentation build paths, +just like the man-page bindings generator: from a sentinel target in +``docs/Makefile.am`` (after the man3 bindings, before Sphinx) for ``make`` +builds, and from ``.readthedocs-pre-create-environment.sh`` for Read the Docs +builds (which run ``sphinx-build`` directly and never run ``make``). Shared MPI +metadata logic (binding rendering, ``VERSION`` parsing, build identity) lives in +``docs/ompi_docs_common.py``, which is also used by the man-page bindings +generator. No separate command is needed: building the docs builds the LLM +artifacts. + +A ``build-finished`` hook in ``docs/conf.py`` then copies the staging tree +(``llms-build/llms`` → ``/llms`` and ``llms-build/llms.txt`` → +``/llms.txt``) into the Sphinx HTML output. The copy lives in a Sphinx +hook --- rather than ``html_extra_path`` or a Makefile step --- for two +reasons: (1) the staged Markdown is deliberately excluded from Sphinx source +discovery (``exclude_patterns``), which would *also* suppress an +``html_extra_path`` entry pointing at it; and (2) the hook runs inside +``sphinx-build``, so publication works identically under ``make`` and on Read +the Docs. Because the artifacts ride inside the HTML output tree, the existing +``html-local`` / ``EXTRA_DIST`` (tarballs), ``install-data-hook`` (install), and +``uninstall-hook`` (uninstall) machinery handles them with no new rules. + +Link strategy: relative vs. absolute links +------------------------------------------- + +Every generated link --- in ``llms.txt``, in the JSONL records' ``urls``, in the +manifest ``url`` fields, and in the per-symbol/man1 page Canonical-HTML headers +--- follows the build type: + +* A **local build from git, or a release tarball,** produces the artifacts for a + *local* tool that reads the files straight off disk. There is no + ``docs.open-mpi.org`` site in play, so every link is made **relative to the + file that contains it**. The result is a self-contained, portable tree that + resolves no matter where it lives (or is unpacked from a tarball), and which + needs no network access to follow internal links. +* A **Read the Docs build** publishes under ``https://docs.open-mpi.org/en//``, + so every link is **absolute** and uses that published version slug + (``.../en/main/...`` for the ``main`` branch, ``.../en/v6.1.x/...`` for a + release branch, ``.../en/v6.1.0/...`` for a tagged release). Absolute links + mean a record copied *out* of the published site --- into a vector store, a + prompt, a cache --- still resolves back to the correct version's + documentation. + +The base URL is resolved in this order: an explicit ``--url-base`` / +``OMPI_LLM_URL_BASE`` override; then ``READTHEDOCS_CANONICAL_URL`` / +``READTHEDOCS_VERSION`` (set by Read the Docs); otherwise relative. This logic +lives in the ``LinkMaker`` class in ``docs/generate-llm-docs.py``. The one +exception: the version-slug *scheme-documentation* URLs printed inside +``llms.txt`` (the ``.../en/VERSION_SLUG/`` examples) are always literal absolute +``docs.open-mpi.org`` text, even in a local build, so a local consumer still +learns where the published versions live. + +Because both passes (local and Read the Docs) each pick one strategy and hold it +constant, the determinism check (which generates twice and diffs) is unaffected. + +Read the Docs and the version-neutral llms.txt +---------------------------------------------- + +Read the Docs serves the version-neutral ``https://docs.open-mpi.org/llms.txt`` +by serving **the default version's own** ``llms.txt`` at the site root. There is +no separate, hand-maintained top-level index file: the root URL is simply +whichever ``llms.txt`` the current default version produced. Each documentation +version emits exactly one self-describing ``llms.txt`` for itself. + +This RTD behavior directly drove the *content* of ``llms.txt``. Because the file +served at the root is just some version's file --- and the default version may +be a series that does not even carry these artifacts yet --- ``llms.txt`` cannot +assume it is authoritative for the whole project. So it **self-describes the +version-slug URL scheme**: + +* it states the ``https://docs.open-mpi.org/en/VERSION_SLUG/`` scheme and how to + read a slug (``main`` = the main-branch build; ``vA.B.x`` = a release branch; + ``vA.B.C`` = a specific tagged release), worded so an LLM that wants a + *different* version can construct the URL itself; +* it notes that documentation for Open MPI versions older than v5.0.0 is not + published in this format, and points at the legacy README/FAQ/doc pages; +* for a Read the Docs ``main`` build it uses **dual attribution** --- naming both + the ``main`` slug and the ``vA.B.x`` series that build currently represents --- + because the same file is both "the development tip" and "the current + pre-release series." + +If a future need arises for the root ``/llms.txt`` to be something other than the +default version's copy, Read the Docs exact redirects can point ``/llms.txt`` at +a chosen versioned path; no generator change would be required. + +Validation +---------- + +``make check`` builds the artifacts if they are not already present and then +runs ``docs/validate-llm-docs.py``, which validates the catalog and manifest +against the published JSON Schemas (``docs/llms-src/*.schema.json``), checks +cross-field invariants (for example, that a record's ``languages`` equals the +distinct set of its ``bindings[].language``) and manifest integrity (hashes, +byte sizes, coverage), confirms the generated Markdown is free of unresolved RST +(``.. include::`` directives, ``:ref:`` roles, Sphinx-only substitutions), +confirms the versioned ``llms.txt`` carries no generation timestamp, verifies the +committed sample records (``specs/llms-friendly-docs/sample-records.jsonl``) +match the generated catalog, and confirms the generator is deterministic at a +fixed ``SOURCE_DATE_EPOCH``. + +Reproducibility and the per-release manifest +-------------------------------------------- + +The artifacts are regenerated by the normal documentation build, so there is no +separate regeneration step for release managers, and no separate command for +developers: ``make`` (or a Read the Docs build) regenerates everything, +including the manifest, every time. + +The manifest (``llms/openmpi-docs-manifest.json``) is rebuilt on every build and +is the single place that carries build identity: ``git_commit``, +``git_describe``, ``generated_at``, the Open MPI version/series (from the +top-level ``VERSION`` file), the Read the Docs slug when present, and one entry +per artifact (path, URL, media type, SHA-256, byte size, estimated token count, +and the symbols/languages it covers). It does **not** inventory itself --- a file +cannot record its own hash and size without changing them. Consumers do not need +a separately incremented "docs release number": they compare the manifest's +identity fields and per-artifact hashes. If, say, the ``v6.1.x`` branch gets a +documentation fix before ``v6.1.1`` ships, the Open MPI version may be unchanged +but the git identity and the hashes of the *affected* artifacts change, while +everything else stays byte-identical. + +For a reproducible release tarball, set ``SOURCE_DATE_EPOCH`` (as already +documented for reproducible Open MPI builds): the generator honors it for the +manifest ``generated_at`` timestamp, exactly as ``config/getdate.sh`` and +Sphinx's ``format_date`` do for the rest of the docs build. With +``SOURCE_DATE_EPOCH`` set, the whole documentation build (HTML, man pages, and +LLM artifacts) is reproducible. In a from-tarball build with no ``.git`` and no +``SOURCE_DATE_EPOCH``, the generator degrades gracefully rather than failing: +the git fields become ``unknown``/omitted and ``generated_at`` falls back to the +build date, mirroring ``config/opal_get_version.sh``. Distribution tarballs ship +the already-rendered ``llms/`` tree inside ``html/``, so installing or packaging +them never requires Sphinx; Sphinx is needed only to *regenerate* them in a +developer clone. + +Schema evolution +---------------- + +The catalog and manifest each conform to a published JSON Schema that doubles as +the CI validator. + +* Changes are **additive within a** ``schema_version``: new fields must be + optional, and a field may be marked deprecated (still emitted) for one Open + MPI release series before removal. Never remove, rename, repurpose, or change + the meaning of an existing field without bumping the schema version. The + published schemas use open objects so an additive field does not break a + consumer validating against an older cached schema. +* ``schema_version`` / ``artifact_schema_version`` are bumped **only on a + breaking change**, a decision owned by the documentation maintainers. The + ``schema_version`` is independent of the Open MPI release number, so the + schema can stay fixed across many releases. +* A schema-version bump or notable field change gets a changelog entry under + ``docs/release-notes/changelog/``. Routine regeneration (content that flows + automatically from changed RST or metadata) does not. + +Updating the documentation when APIs change +------------------------------------------- + +The generated artifacts cannot drift, because they regenerate from the RST man +pages and the MPI Forum JSON binding metadata. The practical consequence is that +**you update +the LLM docs by updating the ordinary documentation**, not by editing the +generator. + +**Adding or changing an MPI API function.** When a new MPI Standard function is +added (for example, when a new MPI version lands), the LLM artifacts pick it up +automatically *once its man page exists*: + +#. Add or edit ``docs/man-openmpi/man3/MPI_.3.rst`` --- the same + hand-written man page that produces the human HTML/man output. This man page + is what makes the function "documented"; both the man3 binding generator and + the LLM generator enumerate the man3 ``.rst`` files (via ``os.listdir``), so + a function that is in the metadata but has no ``.rst`` page is silently *not* + documented anywhere. +#. Add the page to the explicit ``OMPI_MAN3`` list in ``docs/Makefile.am``. The + generators auto-discover the file and the ``RST_SOURCE_FILES`` wildcard + already makes it a rebuild dependency, but the *installation* list is + explicit (Automake installs man pages by name), so a new page must be listed + there to be installed. +#. For standard APIs there is **nothing to author for the bindings**: the C, + ``mpif.h``/``use mpi``, and ``use mpi_f08`` signatures (and any large-count + "embiggened" variant) are rendered from ``pympistandard`` + + ``mpi-standard-apis.json``. +#. If one man page documents several procedures, mark them with a + ``.. mpi-bindings: MPI_Foo, MPI_Bar`` comment line so each co-documented + procedure gets its own bindings, catalog record, and ``documented_with`` + linkage. + +No change to ``generate-llm-docs.py``, the schemas, or the validator is needed +for a routine new function: a new ``.rst`` automatically yields a new man page, +a per-symbol Markdown page, and a JSONL catalog record. + +**Open MPI extensions** (``MPIX_*``, ``OMPI_*``) are not in ``pympistandard``, so +their signatures are taken **verbatim** from the RST ``SYNTAX`` block and their +structured parameter fields are best-effort (``unknown`` where they cannot be +extracted reliably). The catalog's ``kind`` field lets consumers tell standard +records from extension records. + +**Upgrading the MPI Standard metadata** (for example, replacing the 4.1 +``apis.json`` with a 5.0 one): update ``docs/mpi-standard-apis.json`` (it is a +symlink to the versioned ``mpi-standard--apis.json``). Note that +``load_pympistandard`` calls ``use_api_version(1, ...)`` --- the ``1`` is +``pympistandard``'s *data-format* version, **not** the MPI version. A new MPI +metadata file is only loadable if the vendored ``3rd-party/pympistandard`` can +parse that format; if the MPI Forum bumped the JSON format, update the vendored +library in lockstep and smoke-test that the new JSON loads. + +**Coverage gaps are currently silent.** There is no check that flags a procedure +present in the metadata but missing a man page (or vice versa); such a procedure +is simply absent from the artifacts. (The spec envisions a lightweight CI +"drift hint" for this; it is not yet implemented.) When adding a batch of new +functions, cross-check that every intended function actually has an +``MPI_*.3.rst`` page. + +**Curated docs and samples.** The generated artifacts cannot drift, but the +curated ``docs/llms-src/`` files (interface guide, examples, and +runtime-introspection guide) can. A pull request that changes public MPI +documentation should also update the affected curated files when relevant --- +for example, the runtime-introspection guide if the ``ompi_info`` interface or +the MCA parameter-setting conventions change (this expectation is also recorded +in the top-level ``AGENTS.md``). When the curated examples or a schema change, regenerate +``specs/llms-friendly-docs/sample-records.jsonl`` so ``make check`` continues to +pass. + +Verifying publication +--------------------- + +After a merge that affects the artifacts, confirm Read the Docs published them +for the version you merged to: fetch that version's ``llms.txt`` (for example +``https://docs.open-mpi.org/en/main/llms.txt`` for the ``main`` branch) and one +versioned artifact (for example the manifest, +``.../en//llms/openmpi-docs-manifest.json``) and confirm they resolve +and parse. Because the version-neutral ``https://docs.open-mpi.org/llms.txt`` +follows Read the Docs' *default* version --- which may not be the version you +just merged --- always spot-check the specific version slug you published. + +Alternatives considered and deferred +------------------------------------ + +For the historical record (details in ``specs/llms-friendly-docs/spec.md``): + +* **OpenSHMEM** public APIs can later reuse this same artifact model, but support + is **deferred until there is concrete demand**; this effort covers MPI only. +* An ``llms-full.txt`` single-payload entry point, compressed downloadable + bundles, and stable redirects for renamed artifacts were each considered and + **dropped** --- the aggregate Markdown corpora, the release tarballs plus the + manifest, and ad hoc Read the Docs redirects respectively make them + unnecessary. The JSON Schema files, by contrast, were promoted *into* scope as + the shared contract/validator. diff --git a/docs/developers/prerequisites.rst b/docs/developers/prerequisites.rst index b4971635b52..18d4ab531a4 100644 --- a/docs/developers/prerequisites.rst +++ b/docs/developers/prerequisites.rst @@ -8,8 +8,7 @@ Although it should probably be assumed, you'll need a C compiler that supports C11. You'll also need a Fortran compiler if you want to build the Fortran -MPI bindings (the more recent the Fortran compiler, the better), and a -Java compiler if you want to build the (unofficial) Java MPI bindings. +MPI bindings (the more recent the Fortran compiler, the better). GNU Autotools ------------- diff --git a/docs/developers/rst-for-markdown-expats.rst b/docs/developers/rst-for-markdown-expats.rst index af09f7dee33..e4531f62822 100644 --- a/docs/developers/rst-for-markdown-expats.rst +++ b/docs/developers/rst-for-markdown-expats.rst @@ -152,7 +152,7 @@ Multi-line code/fixed-width font case, the example code block will be rendered in the bulleted item. -Whereas this parargraph and code block will be outside of the +Whereas this paragraph and code block will be outside of the above bulleted list: .. code-block:: sh @@ -243,12 +243,11 @@ Including files .. code-block:: rst .. include:: features-extensions.rst - .. include:: features-java.rst - Those directives include those 2 files right here in this RST file. + That directive includes that file right here in this RST file. .. important:: Chapter/section/subsection delimiters will be - continued in those files as part of rendering this + continued in that file as part of rendering this file. Hyperlinks to URLs diff --git a/docs/developers/source-code.rst b/docs/developers/source-code.rst index a2108e62446..484cd88a183 100644 --- a/docs/developers/source-code.rst +++ b/docs/developers/source-code.rst @@ -226,8 +226,7 @@ identical) directory structures under them: There are other top-level directories in each of the sub-projects, each having to do with specific logic and code for that project. For example, the MPI API implementations can be found under -``ompi/mpi/LANGUAGE``, where ``LANGUAGE`` is ``c``, ``fortran``, or -``java``. +``ompi/mpi/LANGUAGE``, where ``LANGUAGE`` is ``c`` or ``fortran``. The layout of the ``mca`` trees are strictly defined. They are of the form: diff --git a/docs/developers/sphinx.rst b/docs/developers/sphinx.rst index 0e0b2c7231e..74a53b9625b 100644 --- a/docs/developers/sphinx.rst +++ b/docs/developers/sphinx.rst @@ -131,7 +131,7 @@ under ``$HOME/Library/Python/PYTHON_VERSION/bin/sphinx-build``). Running Sphinx -------------- -Open MPI's build environment is setup to invoke Sphinx automatically; +Open MPI's build environment is set up to invoke Sphinx automatically; you should not need to invoke Sphinx manually. .. important:: You will need to ensure that Sphinx is in your ``PATH`` diff --git a/docs/features/extension-affinity.rst b/docs/features/extension-affinity.rst new file mode 100644 index 00000000000..ca8450e062d --- /dev/null +++ b/docs/features/extension-affinity.rst @@ -0,0 +1,49 @@ +.. _ompi-features-extension-affinity-label: + +Affinity extension +================== + +Overview +-------- + +The ``affinity`` extension provides a single Open MPI-specific +function, :ref:`OMPI_Affinity_str(3) `. Given a +format selector, it fills in three human-readable strings describing: + +* where Open MPI bound the calling process at launch time (or that it + was not bound); +* where the process is *currently* bound (or that it is unbound); and +* which processors exist on the local host. + +The strings can be requested either as resource descriptions (for +example, ``socket 0, core 0``) or as an ASCII-art layout of the +machine (for example, ``[. B][. .]``). This is an Open +MPI-specific convenience API for applications that want to introspect +their own binding; it is not part of the MPI standard. + +When it is built +---------------- + +The ``affinity`` extension has no build-time prerequisites, so it is +built by default on every platform. Like all extensions, it can be +excluded by configuring with ``--disable-mpi-ext``, or by naming an +explicit list that omits it (for example, +``--enable-mpi-ext=cuda,rocm``). See :ref:`the extensions overview +` for the full set of +``--enable-mpi-ext`` / ``--disable-mpi-ext`` options. + +Availability at run time +------------------------ + +Because the extension has no external dependencies, it is available at +run time whenever it was compiled in. Applications should still guard +their use of it with the preprocessor macro +``OMPI_HAVE_MPI_EXT_AFFINITY``, which ```` defines to ``1`` +when the extension is present. This keeps application code portable +across Open MPI builds that omitted the extension and across other MPI +implementations. + +Functions +--------- + +* :ref:`OMPI_Affinity_str(3) ` diff --git a/docs/features/extension-example.rst b/docs/features/extension-example.rst new file mode 100644 index 00000000000..36cdc4c2f17 --- /dev/null +++ b/docs/features/extension-example.rst @@ -0,0 +1,50 @@ +.. _ompi-features-extension-example-label: + +Example extension +================= + +Overview +-------- + +The ``example`` extension is **non-functional**: it exists solely as a +worked template for developers who want to create a new Open MPI +extension. It defines a demonstration API named ``OMPI_Progress()`` in +all four MPI binding types (C, the Fortran ``mpif.h`` bindings, the +Fortran ``mpi`` module, and the Fortran ``mpi_f08`` module), showing +how each piece is wired into the publicly-available ``mpi-ext.h`` header +and the ``mpi_ext`` / ``mpi_f08_ext`` Fortran modules. + +It is not intended for use by applications. Anyone writing a real +extension should read the heavily-commented source and the +``README.md`` in the source tree +(``ompi/mpiext/example/README.md``), which walk through the required +directory layout, file naming, and ``configure.m4`` conventions. + +When it is built +---------------- + +Unlike the other extensions, ``example`` is not built as part of a +normal build. Because it is a developer template, it is only compiled +when it is explicitly requested at configure time: + +.. code-block:: sh + + shell$ ./configure --enable-mpi-ext=example + +See :ref:`the extensions overview ` for +the full ``--enable-mpi-ext`` syntax. + +Availability at run time +------------------------ + +When the extension is compiled in, ```` defines the +preprocessor macro ``OMPI_HAVE_MPI_EXT_EXAMPLE`` to ``1``. Since the +extension is a demonstration only, applications have no reason to depend +on it; it is documented here for completeness because it appears in the +list of available extensions. + +Functions +--------- + +This extension provides only a demonstration ``OMPI_Progress()`` entry +point and has no manual pages. diff --git a/docs/features/extension-shortfloat.rst b/docs/features/extension-shortfloat.rst new file mode 100644 index 00000000000..44ff053a2d4 --- /dev/null +++ b/docs/features/extension-shortfloat.rst @@ -0,0 +1,57 @@ +.. _ompi-features-extension-shortfloat-label: + +Short float extension +===================== + +Overview +-------- + +The ``shortfloat`` extension provides MPI datatypes corresponding to +short / half-precision floating point language types. Depending on +which types the compiler supports, it defines some or all of: + +* ``MPIX_C_FLOAT16`` |mdash| for the C type ``_Float16`` (ISO/IEC TS + 18661-3:2015). This name and meaning match MPICH. +* ``MPIX_SHORT_FLOAT`` |mdash| for the C/C++ type ``short float``. +* ``MPIX_C_SHORT_FLOAT_COMPLEX`` |mdash| for the C type ``short float + _Complex``. +* ``MPIX_CXX_SHORT_FLOAT_COMPLEX`` |mdash| for the C++ type + ``std::complex``. + +These datatypes were proposed for (but not accepted into) the MPI +standard, so they carry the ``MPIX_`` prefix. See the extension's +``README.md`` in the source tree +(``ompi/mpiext/shortfloat/README.md``) and `MPI Forum issue 65 +`_ for background. + +Because this extension only adds datatype handles (constants), it does +not provide any functions, and therefore has no manual pages. + +When it is built +---------------- + +The ``shortfloat`` extension is built by default, but *only* when the +compiler provides a suitable short / half-precision floating point +type. Specifically, Open MPI's ``configure`` builds it when either: + +* the compiler natively supports a ``short float`` type; or +* Open MPI can provide an equivalent 16-bit type (``opal_short_float_t``, + typically mapped to ``_Float16``). + +If neither is available, the extension is silently omitted |mdash| there +is no dedicated configure option to force it on, since it depends +entirely on compiler support. As with all extensions, it can be +excluded explicitly with ``--disable-mpi-ext`` or by omitting it from +an explicit ``--enable-mpi-ext=LIST``. See :ref:`the extensions +overview ` for details. + +Availability at run time +------------------------ + +When the extension is compiled in, ```` defines the +preprocessor macro ``OMPI_HAVE_MPI_EXT_SHORTFLOAT`` to ``1``. +Applications should test this macro before using any of the ``MPIX_`` +datatypes above, both because the extension may have been omitted (if +the compiler lacked a short float type) and to remain portable to other +MPI implementations. Note that the individual datatype handles that are +defined depend on which language types were available at build time. diff --git a/docs/features/extensions.rst b/docs/features/extensions.rst index 592d56e7687..adf9dcd6ae8 100644 --- a/docs/features/extensions.rst +++ b/docs/features/extensions.rst @@ -1,4 +1,4 @@ -.. _ompi-features-extentions-label: +.. _ompi-features-extensions-label: Open MPI extensions =================== @@ -20,37 +20,74 @@ to MPI applications. Available extensions -------------------- -The following extensions are included in this version of Open MPI: - -#. ``shortfloat``: Provides MPI datatypes ``MPIX_C_FLOAT16``, - ``MPIX_SHORT_FLOAT``, ``MPIX_SHORT_FLOAT``, and - ``MPIX_CXX_SHORT_FLOAT_COMPLEX`` if corresponding language types are - available. See ``ompi/mpiext/shortfloat/README.txt`` for details. -#. ``affinity``: Provides the ``OMPI_Affinity_str()`` API, which returns - a string indicating the resources which a process is bound. For - more details, see its man page. -#. ``cuda``: When the library is compiled with CUDA-aware support, it - provides two things. First, a macro - ``MPIX_CUDA_AWARE_SUPPORT``. Secondly, the function - ``MPIX_Query_cuda_support()`` that can be used to query for support. -#. ``example``: A non-functional extension; its only purpose is to - provide an example for how to create other extensions. -#. ``ftmpi``: An implementation of the User Level Fault Mitigation - (ULFM) proposal. :ref:`See its documentation section ` - for more details. +The following extensions are included in this version of Open MPI. +Follow the link on each name for a full description of what it +provides, when it is built, when it is available at run time, and the +functions it offers: + +* :doc:`affinity `: Provides the + ``OMPI_Affinity_str()`` API, which returns human-readable strings + describing how the calling process is bound to processor resources. + +* :doc:`cuda `: Provides the + ``MPIX_CUDA_AWARE_SUPPORT`` compile-time macro and the + ``MPIX_Query_cuda_support()`` run-time function for detecting whether + the library has NVIDIA CUDA-aware support. + +* :doc:`rocm `: Provides the + ``MPIX_ROCM_AWARE_SUPPORT`` compile-time macro and the + ``MPIX_Query_rocm_support()`` run-time function for detecting whether + the library has AMD ROCm-aware support. + +* :doc:`ftmpi `: An implementation of the MPI Forum's + User-Level Failure Mitigation (ULFM) proposal, providing the + ``MPIX_Comm_*`` functions and ``MPIX_ERR_*`` error codes for writing + fault-tolerant MPI applications. + +* :doc:`shortfloat `: Provides MPI + datatypes corresponding to short / half-precision floating point C + and C++ language types, when such types are available. + +* :doc:`example `: A non-functional + extension whose only purpose is to demonstrate how to create a new + Open MPI extension. + +.. toctree:: + :hidden: + + extension-affinity + extension-shortfloat + extension-example Compiling the extensions ------------------------ -Open MPI extensions are all enabled by default; they can be disabled -via the ``--disable-mpi-ext`` command line switch. +Most Open MPI extensions are enabled by default; the exceptions are +extensions that require functionality not present in your build +environment (for example, ``shortfloat`` is only built when the +compiler provides a suitable short / half-precision floating point +type) and the developer-only ``example`` extension (which is only +built when explicitly requested). + +The set of extensions to build is selected at configure time: + +* ``--enable-mpi-ext`` (the default) builds all available extensions. +* ``--enable-mpi-ext=LIST`` builds only the comma-separated extensions + named in ``LIST`` |mdash| for example, + ``--enable-mpi-ext=cuda,rocm``. +* ``--disable-mpi-ext`` builds none of the extensions. + +Each extension's own page (linked above) documents any additional +build-time prerequisites and the configure options needed to satisfy +them. -Since extensions are meant to be used by advanced users only, this -file does not document which extensions are available or what they do. -Look in the ``ompi/mpiext`` directory in a distribution Open MPI -tarball to see the extensions; each subdirectory of that directory -contains an extension. Each has a ``README`` file that describes what -it does. +You can confirm which extensions were compiled into a given Open MPI +installation with ``ompi_info``: + +.. code-block:: sh + + shell$ ompi_info | grep "MPI extensions" + MPI extensions: affinity, cuda, ftmpi, rocm Using the extensions -------------------- @@ -75,7 +112,7 @@ prototypes, constant declarations, etc. For example: char exists[OMPI_AFFINITY_STRING_MAX]; OMPI_Affinity_str(OMPI_AFFINITY_LAYOUT_FMT, ompi_bound, - current_bindings, exists); + current_binding, exists); #endif MPI_Finalize(); @@ -85,6 +122,15 @@ prototypes, constant declarations, etc. For example: Notice that the Open MPI-specific code is surrounded by the ``#if`` statement to ensure that it is only ever compiled by Open MPI. +Including ```` defines the preprocessor macro +``OMPI_HAVE_MPI_EXT`` to ``1``. In addition, for each extension that +is present, it defines a macro named ``OMPI_HAVE_MPI_EXT_`` (with +```` being the uppercased extension name, e.g., +``OMPI_HAVE_MPI_EXT_AFFINITY``) to ``1``. Applications can test these +macros to portably guard their use of a given extension |mdash| both +against Open MPI builds that omitted the extension and against other +MPI implementations that do not provide ```` at all. + The Open MPI wrapper compilers (``mpicc`` and friends) should automatically insert all relevant compiler and linker flags necessary to use the extensions. No special flags or steps should be necessary diff --git a/docs/features/index.rst b/docs/features/index.rst index 3afb117dabb..0662a60cf20 100644 --- a/docs/features/index.rst +++ b/docs/features/index.rst @@ -14,4 +14,3 @@ categories of Open MPI-specific features. profiling extensions ulfm - java diff --git a/docs/features/java.rst b/docs/features/java.rst deleted file mode 100644 index 81fedd72bb7..00000000000 --- a/docs/features/java.rst +++ /dev/null @@ -1,350 +0,0 @@ -.. _open-mpi-java-label: - -Open MPI Java bindings -====================== - -Open MPI |ompi_ver| provides support for Java-based MPI applications. - -.. warning:: The Open MPI Java bindings are provided on a - "provisional" basis |mdash| i.e., they are not part of the current or - proposed MPI standards. Thus, inclusion of Java support is not - required by the standard. Continued inclusion of the Java bindings - is contingent upon active user interest and continued developer - support. - -The rest of this document provides step-by-step instructions on -building OMPI with Java bindings, and compiling and running Java-based -MPI applications. Also, part of the functionality is explained with -examples. Further details about the design, implementation and usage -of Java bindings in Open MPI can be found in its canonical reference -paper [#ompijava]_. The bindings follow a JNI approach, that is, we do -not provide a pure Java implementation of MPI primitives, but a thin -layer on top of the C implementation. This is the same approach as in -mpiJava [#mpijava]_; in fact, mpiJava was taken as a starting point -for Open MPI Java bindings, but they were later totally rewritten. - -Building the Java bindings --------------------------- - -Java support requires that Open MPI be built at least with shared -libraries (i.e., ``--enable-shared``). Note that this is the default -for Open MPI, so you don't have to explicitly add the option. The Java -bindings will build only if ``--enable-mpi-java`` is specified, and a -JDK is found in a typical system default location. - -If the JDK is not in a place where we automatically find it, you can -specify the location. For example, this is required on the Mac -platform as the JDK headers are located in a non-typical location. Two -options are available for this purpose: - -#. ``--with-jdk-bindir=``: the location of ``javac`` and ``javah`` -#. ``--with-jdk-headers=``: the directory containing ``jni.h`` - -Some example configurations are provided in Open MPI configuration -platform files under ``contrib/platform/hadoop``. These examples can -provide a starting point for your own custom configuration. - -In summary, therefore, you can configure the system using the -following Java-related options:: - - $ ./configure --with-platform=contrib/platform/hadoop/ ... - -or:: - - $ ./configure --enable-mpi-java --with-jdk-bindir= --with-jdk-headers= ... - -or simply:: - - $ ./configure --enable-mpi-java ... - -if JDK is in a "standard" place that ``configure`` can automatically -find. - -Building Java MPI applications ------------------------------- - -The ``mpijavac`` wrapper compiler is available for compiling -Java-based MPI applications. It ensures that all required Open MPI -libraries and classpaths are defined. For example: - -.. code-block:: - - $ mpijavac Hello.java - -You can use the ``--showme`` option to see the full command line of -the Java compiler that is invoked: - -.. code-block:: - - $ mpijavac Hello.java --showme - /usr/bin/javac -cp /opt/openmpi/lib/mpi.jar Hello.java - -Note that if you are specifying a ``-cp`` argument on the command line -to pass your application-specific classpaths, Open MPI will *extend* -that argument to include the ``mpi.jar``: - -.. code-block:: - - $ mpijavac -cp /path/to/my/app.jar Hello.java --showme - /usr/bin/javac -cp /path/to/my/app.jar:/opt/openmpi/lib/mpi.jar Hello.java - -Similarly, if you have a ``CLASSPATH`` environment variable defined, -``mpijavac`` will convert that into a ``-cp`` argument and extend it -to include the ``mpi.jar``: - -.. code-block:: - - $ export CLASSPATH=/path/to/my/app.jar - $ mpijavac Hello.java --showme - /usr/bin/javac -cp /path/to/my/app.jar:/opt/openmpi/lib/mpi.jar Hello.java - - -Running Java MPI applications ------------------------------ - -Once your application has been compiled, you can run it with the -standard ``mpirun`` command line:: - - $ mpirun java - -``mpirun`` will detect the ``java`` token and ensure that the required -MPI libraries and class paths are defined to support execution. You -therefore do **not** need to specify the Java library path to the MPI -installation, nor the MPI classpath. Any classpath definitions -required for your application should be specified either on the -command line or via the ``CLASSPATH`` environment variable. Note that -the local directory will be added to the classpath if nothing is -specified. - -.. note:: The ``java`` executable, all required libraries, and your - application classes must be available on all nodes. - -Basic usage of the Java bindings --------------------------------- - -There is an MPI package that contains all classes of the MPI Java -bindings: ``Comm``, ``Datatype``, ``Request``, etc. These classes have a -direct correspondence with handle types defined by the MPI standard. MPI -primitives are just methods included in these classes. The convention -used for naming Java methods and classes is the usual camel-case -convention, e.g., the equivalent of ``MPI_File_set_info(fh,info)`` is -``fh.setInfo(info)``, where ``fh`` is an object of the class ``File``. - -Apart from classes, the MPI package contains predefined public -attributes under a convenience class ``MPI``. Examples are the -predefined communicator ``MPI.COMM_WORLD`` and predefined datatypes such -as ``MPI.DOUBLE``. Also, MPI initialization and finalization are methods -of the ``MPI`` class and must be invoked by all MPI Java -applications. The following example illustrates these concepts: - -.. code-block:: java - - import mpi.*; - - class ComputePi { - - public static void main(String args[]) throws MPIException { - - MPI.Init(args); - - int rank = MPI.COMM_WORLD.getRank(), - size = MPI.COMM_WORLD.getSize(), - nint = 100; // Intervals. - double h = 1.0/(double)nint, sum = 0.0; - - for (int i=rank+1; i<=nint; i+=size) { - double x = h * ((double)i - 0.5); - sum += (4.0 / (1.0 + x * x)); - } - - double sBuf[] = { h * sum }, - rBuf[] = new double[1]; - - MPI.COMM_WORLD.reduce(sBuf, rBuf, 1, MPI.DOUBLE, MPI.SUM, 0); - - if (rank == 0) { - System.out.println("PI: " + rBuf[0]); - } - MPI.Finalize(); - } - } - -Exception handling ------------------- - -The Java bindings in Open MPI support exception handling. By default, -errors are fatal, but this behavior can be changed. The Java API will -throw exceptions if the ``MPI.ERRORS_RETURN`` error handler is set: - -.. code-block:: java - - MPI.COMM_WORLD.setErrhandler(MPI.ERRORS_RETURN); - -If you add this statement to your program, it will show the line -where it breaks, instead of just crashing in case of an error. -Error-handling code can be separated from main application code by -means of try-catch blocks, for instance: - -.. code-block:: java - - try - { - File file = new File(MPI.COMM_SELF, "filename", MPI.MODE_RDONLY); - } - catch(MPIException ex) - { - System.err.println("Error Message: "+ ex.getMessage()); - System.err.println(" Error Class: "+ ex.getErrorClass()); - ex.printStackTrace(); - System.exit(-1); - } - -How to specify buffers ----------------------- - -In MPI primitives that require a buffer (either send or receive), the -Java API admits a Java array. Since Java arrays can be relocated by -the Java runtime environment, the MPI Java bindings need to make a -copy of the contents of the array to a temporary buffer, then pass the -pointer to this buffer to the underlying C implementation. From the -practical point of view, this implies an overhead associated to all -buffers that are represented by Java arrays. The overhead is small for -small buffers but increases for large arrays. - -There is a pool of temporary buffers with a default capacity of 64K. -If a temporary buffer of 64K or less is needed, then the buffer will -be obtained from the pool. But if the buffer is larger, then it will -be necessary to allocate the buffer and free it later. - -The default capacity of pool buffers can be modified with an Open MPI -MCA parameter:: - - $ mpirun --mca ompi_mpi_java_eager SIZE ... - -The value of ``SIZE`` can be: - -* ``N``: An integer number of bytes -* ``Nk``: An integer number (suffixed with ``k``) of kilobytes -* ``Nm``: An integer number (suffixed with ``m``) of megabytes - -An alternative is to use "direct buffers" provided by standard classes -available in the Java SDK such as ``ByteBuffer``. For convenience, -Open MPI provides a few static methods ``new[Type]Buffer`` in the -``MPI`` class to create direct buffers for a number of basic -datatypes. Elements of the direct buffer can be accessed with methods -``put()`` and ``get()``, and the number of elements in the buffer can -be obtained with the method ``capacity()``. This example illustrates -its use: - -.. code-block:: java - - int myself = MPI.COMM_WORLD.getRank(); - int tasks = MPI.COMM_WORLD.getSize(); - - IntBuffer in = MPI.newIntBuffer(MAXLEN * tasks), - out = MPI.newIntBuffer(MAXLEN); - - for (int i = 0; i < MAXLEN; i++) - out.put(i, myself); // fill the buffer with the rank - - Request request = MPI.COMM_WORLD.iAllGather( - out, MAXLEN, MPI.INT, in, MAXLEN, MPI.INT); - request.waitFor(); - request.free(); - - for (int i = 0; i < tasks; i++) { - for (int k = 0; k < MAXLEN; k++) { - if (in.get(k + i * MAXLEN) != i) - throw new AssertionError("Unexpected value"); - } - } - -Direct buffers are available for: ``BYTE``, ``CHAR``, ``SHORT``, -``INT``, ``LONG``, ``FLOAT``, and ``DOUBLE``. - -.. note:: There is no direct buffer for booleans. - -Direct buffers are not a replacement for arrays, because they have -higher allocation and deallocation costs than arrays. In some cases -arrays will be a better choice. You can easily convert a buffer into -an array and vice versa. - -.. important:: All non-blocking methods *must* use direct buffers. - Only blocking methods can choose between arrays and - direct buffers. - -The above example also illustrates that it is necessary to call the -``free()`` method on objects whose class implements the ``Freeable`` -interface. Otherwise, a memory leak will occur. - -Specifying offsets in buffers ------------------------------ - -In a C program, it is common to specify an offset in a array with -``&array[i]`` or ``array+i`` to send data starting from a given -position in the array. The equivalent form in the Java bindings is to -``slice()`` the buffer to start at an offset. Making a ``slice()`` on -a buffer is only necessary, when the offset is not zero. Slices work -for both arrays and direct buffers. - -.. code-block:: java - - import static mpi.MPI.slice; - // ... - int numbers[] = new int[SIZE]; - // ... - MPI.COMM_WORLD.send(slice(numbers, offset), count, MPI.INT, 1, 0); - - -Supported APIs --------------- - -Complete MPI-3.1 coverage is provided in the Open MPI Java bindings, -with a few exceptions: - -* The bindings for the ``MPI_Neighbor_alltoallw`` and - ``MPI_Ineighbor_alltoallw`` functions are not implemented. - -* Also excluded are functions that incorporate the concepts of - explicit virtual memory addressing, such as - ``MPI_Win_shared_query``. - - -Known issues ------------- - -There exist issues with the Omnipath (PSM2) interconnect involving -Java. The problems definitely exist in PSM2 v10.2; we have not tested -previous versions. - -As of November 2016, there is not yet a PSM2 release that completely -fixes the issue. - -The following ``mpirun`` command options will disable PSM2:: - - shell$ mpirun ... --mca mtl ^psm2 java ...your-java-options... your-app-class - - -Questions? Problems? ---------------------- - -The Java API documentation is generated at build time in -``$prefix/share/doc/openmpi/javadoc``. - -Additionally, `this Cisco blog post -`_ has -quite a bit of information about the Open MPI Java bindings. - -If you have any problems, or find any bugs, please feel free to report -them to `Open MPI user's mailing list -`_. - -.. rubric:: Footnotes - -.. [#ompijava] O. Vega-Gisbert, J. E. Roman, and J. M. Squyres. "Design - and implementation of Java bindings in Open MPI". Parallel Comput. - 59: 1-20 (2016). - -.. [#mpijava] M. Baker et al. "mpiJava: An object-oriented Java - interface to MPI". In Parallel and Distributed Processing, LNCS - vol. 1586, pp. 748-762, Springer (1999). diff --git a/docs/features/profiling.rst b/docs/features/profiling.rst index e0de0597cad..8b6001d11cf 100644 --- a/docs/features/profiling.rst +++ b/docs/features/profiling.rst @@ -1,11 +1,10 @@ -.. _open-mpi-profileing-label: +.. _open-mpi-profiling-label: Open MPI profiling interface ============================ -Open MPI |ompi_ver| supportings the "PMPI" profiling interface as -perscribed by the MPI standard for the C and Fortran bindings (*not* -the :ref:`Open MPI Java binding extensions `). +Open MPI |ompi_ver| supports the "PMPI" profiling interface as +prescribed by the MPI standard for the C and Fortran bindings. Per MPI-4.0 section 15.2.1, MPI implementations must document which bindings layer on top of each other, so that profile developers know @@ -15,7 +14,7 @@ level routines. In general, Open MPI's Fortran bindings are implemented on top of the C bindings. Hence, a profile developer who implements ``MPI_Init()`` -in C will also intecept all Fortran calls to ``MPI_INIT`` regardless +in C will also intercept all Fortran calls to ``MPI_INIT`` regardless of whether the user is utilizing the ``mpif.h``, ``use mpi``, or ``use mpi_f08`` Fortran interfaces. @@ -78,4 +77,4 @@ interfaces. Indeed, that is the most portable way to implement a profiling interface. Since Open MPI's Fortran bindings are |mdash| for the most part |mdash| implemented on top of its C bindings, profile developers can ignore all Fortran interfaces except for the -ones enumated above. +ones enumerated above. diff --git a/docs/features/ulfm.rst b/docs/features/ulfm.rst index 0ec3570c927..cbf8c1201a5 100644 --- a/docs/features/ulfm.rst +++ b/docs/features/ulfm.rst @@ -190,7 +190,7 @@ mpi_ft_foo `` for Open MPI options, and with ``--prtemca errmgr_detector_bar `` for PRTE options. .. important:: The main control for enabling/disabling fault tolerance - at runtime is the ``--with-ft ulfm`` (or its synomym ``--with-ft mpi``) + at runtime is the ``--with-ft ulfm`` (or its synonym ``--with-ft mpi``) ``mpirun`` CLI option. This option sets up multiple subsystems in Open MPI to enable fault tolerance. The options described below are best used to override the default behavior after the ``--with-ft ulfm`` @@ -305,7 +305,7 @@ three classifications: after a failure. 3. **Disabled:** This framework/component will cause unspecified behavior when fault tolerance is enabled. As a consequence, it will be disabled when the - ``--with-ft ulfm`` option is used (see above for defails about implicit + ``--with-ft ulfm`` option is used (see above for details about implicit parameters loaded from the ``ft-mpi`` aggregate param file). Any framework or component not listed below are categorized as **Unmodified**, @@ -371,7 +371,7 @@ ULFM Integrated in Open MPI As of |ompi_ver|, ULFM is now integrated directly in to the community release of Open MPI. The following sections describe previous ULFM -standlone releases. +standalone releases. ULFM Standalone Release 4.0.2u1 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -434,7 +434,7 @@ main (November 2018). * Bugfixes: * Correct the behavior of process placement during an MPI_COMM_SPAWN - when some slots were occcupied by failed processes. + when some slots were occupied by failed processes. * MPI_COMM_SPAWN accepts process placement directives in the Info object. * Fixed deadlocks in some NBC collective operations. * Crashes and deadlocks in MPI_FINALIZE have been resolved. diff --git a/docs/generate-code-of-conduct-rst.py b/docs/generate-code-of-conduct-rst.py new file mode 100755 index 00000000000..a0f12316664 --- /dev/null +++ b/docs/generate-code-of-conduct-rst.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# + +"""Convert Open MPI's Markdown CODE_OF_CONDUCT.md to reStructuredText.""" + +import argparse +import re +from pathlib import Path + +HEADING_RE = re.compile(r"^(#{1,6})\s+(.+?)\s*#*\s*$") +REF_DEF_RE = re.compile(r"^\[([^\]]+)\]:\s*(\S+)\s*$") +REF_LINK_RE = re.compile(r"\[([^\]]+)\]\[([^\]]+)\]") +INLINE_LINK_RE = re.compile(r"\[([^\]]+)\]\(([^)]+)\)") +BULLET_RE = re.compile(r"^\s*[*+-]\s+") +UNDERLINES = ["=", "-", "~", "^", '"', "'"] + + +def _rst_link(text, url): + return "`{} <{}>`_".format(text, url) + + +def _convert_links(line, refs): + def repl_ref(match): + text = match.group(1) + ref = match.group(2) + return _rst_link(text, refs.get(ref, ref)) + + def repl_inline(match): + return _rst_link(match.group(1), match.group(2)) + + line = REF_LINK_RE.sub(repl_ref, line) + line = INLINE_LINK_RE.sub(repl_inline, line) + return line + + +def convert(lines): + refs = {} + for line in lines: + match = REF_DEF_RE.match(line.strip()) + if match: + refs[match.group(1)] = match.group(2) + + out = [ + "..", + " This file was generated from CODE_OF_CONDUCT.md by docs/Makefile.am.", + " Do not edit directly.", + "", + ] + + skip_next_blank = False + prev_bullet = False + for line in lines: + if REF_DEF_RE.match(line.strip()): + continue + if skip_next_blank and not line.strip(): + skip_next_blank = False + continue + skip_next_blank = False + + match = HEADING_RE.match(line) + if match: + if out and out[-1] != "": + out.append("") + title = _convert_links(match.group(2), refs) + level = min(len(match.group(1)) - 1, len(UNDERLINES) - 1) + out.extend([title, UNDERLINES[level] * len(title), ""]) + skip_next_blank = True + prev_bullet = False + else: + is_bullet = bool(BULLET_RE.match(line)) + if is_bullet and out and out[-1] != "" and not prev_bullet: + out.append("") + out.append(_convert_links(line, refs)) + prev_bullet = is_bullet + + while out and out[-1] == "": + out.pop() + return "\n".join(out) + "\n" + + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("--input", required=True) + parser.add_argument("--output", required=True) + args = parser.parse_args() + + input_path = Path(args.input) + output_path = Path(args.output) + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text( + convert(input_path.read_text(encoding="utf-8").splitlines()), + encoding="utf-8") + + +if __name__ == "__main__": + main() diff --git a/docs/generate-llm-docs.py b/docs/generate-llm-docs.py new file mode 100755 index 00000000000..d79a3ea7805 --- /dev/null +++ b/docs/generate-llm-docs.py @@ -0,0 +1,1431 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# + +"""Generate LLM-friendly documentation artifacts for the Open MPI MPI APIs. + +This produces, under a build-tree staging directory, the artifact tree that a +copy step in docs/Makefile.am publishes into the Sphinx HTML output after the +Sphinx run (not via ``html_extra_path``, since the staged Markdown must be +excluded from Sphinx source discovery and ``exclude_patterns`` would also +suppress an ``html_extra_path`` entry; see specs/llms-friendly-docs/spec.md): + + / + llms.txt + llms/ + openmpi-mpi-api.jsonl + openmpi-mpi-api.md + openmpi-mpi-api-c.md + openmpi-mpi-api-fortran-mpifh.md + openmpi-mpi-api-fortran-use-mpi.md + openmpi-mpi-api-fortran-use-mpi-f08.md + openmpi-mpi-interface-guide.md (curated; copied from llms-src) + openmpi-mpi-examples.md (curated; copied from llms-src) + openmpi-mpi-api.schema.json (curated; copied from llms-src) + openmpi-docs-manifest.schema.json (curated; copied from llms-src) + openmpi-docs-manifest.json + man-openmpi/man3/MPI_.3.md + man-openmpi/man1/.1.md (command corpus; no JSONL records) + +Build identity (git commit/describe, generation timestamp) lives only in the +manifest; every other artifact is wall-clock-free and deterministic at a given +commit. Shared metadata logic comes from ompi_docs_common.py. + +Targets the Python 3.6 floor (see python_min_version in VERSION). +""" + +import argparse +import hashlib +import json +import os +import posixpath +import re + +import ompi_docs_common as common + +SCHEMA_VERSION = 1 +ARTIFACT_SCHEMA_VERSION = 1 + + +class LinkMaker: + """Builds the URLs that the generated artifacts use to link to each other + and to the human-facing HTML documentation. + + There are two consumers, and they want different link styles: + + * A **Read the Docs** build publishes to ``docs.open-mpi.org/en//``. + Links must be absolute and use that version slug, so a reader of the + published site (or a record copied out of it) can resolve them. Pass the + absolute base (e.g. ``https://docs.open-mpi.org/en/main``) and every link + becomes ``/``. + + * A **local build from git** produces the artifacts for a *local* LLM/tool + that reads the files straight off disk. There is no docs.open-mpi.org + site in play, so the RTD URL scheme is irrelevant; what matters is that + the tree is self-contained and portable. Pass ``None`` and every link + becomes relative to the file that contains it, so the local tree resolves + no matter where it lives. + + Always feed :meth:`link` *logical* paths relative to the documentation + version root (e.g. ``"llms/openmpi-mpi-api.jsonl"``, + ``"man-openmpi/man3/MPI_Init.3.html"``) -- never real on-disk paths. This + keeps output independent of the output directory, which the determinism + check relies on (it generates into two different directories and diffs). + """ + + def __init__(self, url_base): + # Absolute base (no trailing slash) for an RTD/explicit build, or None + # for a local build (relative links). + self._base = url_base.rstrip('/') if url_base else None + + @property + def absolute(self): + return self._base is not None + + def link(self, target, source): + """URL to ``target`` from the file ``source`` (both root-relative).""" + if self._base is not None: + return "{}/{}".format(self._base, target) + return posixpath.relpath(target, posixpath.dirname(source) or '.') + +LANG_C = "c" +LANG_MPIFH = "fortran_mpifh" +LANG_USE_MPI = "fortran_use_mpi" +LANG_F08 = "fortran_mpi_f08" + +# Curated source files (committed under docs/llms-src/) copied verbatim into +# the published llms/ tree. +CURATED_FILES = [ + "openmpi-mpi-interface-guide.md", + "openmpi-mpi-examples.md", + "openmpi-runtime-introspection.md", + "openmpi-mpi-api.schema.json", + "openmpi-docs-manifest.schema.json", +] + +MEDIA_TYPES = { + ".md": "text/markdown", + ".jsonl": "application/jsonl", + ".json": "application/json", + ".txt": "text/plain", +} + +# Heuristic API-family categorization by name prefix. Matching is by list +# order (the first matching prefix wins), so list more-specific prefixes +# before less-specific ones. +CATEGORY_PREFIXES = [ + ("MPI_T_", "tools"), + ("MPI_File_", "io"), + ("MPI_Win_", "rma"), + ("MPI_Comm_", "communicator"), + ("MPI_Group_", "group"), + ("MPI_Type_", "datatype"), + ("MPI_Session_", "sessions"), + ("MPI_Info_", "info"), + ("MPI_Cart_", "topology"), + ("MPI_Graph_", "topology"), + ("MPI_Dist_graph_", "topology"), + ("MPI_Errhandler_", "error-handling"), + ("MPI_Add_error", "error-handling"), + ("MPI_Error", "error-handling"), +] +_COLLECTIVE_BASE = set(""" + MPI_Barrier MPI_Bcast MPI_Gather MPI_Gatherv MPI_Scatter MPI_Scatterv + MPI_Allgather MPI_Allgatherv MPI_Alltoall MPI_Alltoallv MPI_Alltoallw + MPI_Reduce MPI_Allreduce MPI_Reduce_scatter MPI_Reduce_scatter_block + MPI_Scan MPI_Exscan + MPI_Neighbor_allgather MPI_Neighbor_allgatherv MPI_Neighbor_alltoall + MPI_Neighbor_alltoallv MPI_Neighbor_alltoallw +""".split()) + + +def _expand_collectives(bases): + """Expand blocking collective names to their nonblocking (MPI_I...) and + persistent (..._init) variants. Over-generation is harmless: names that do + not correspond to a real procedure simply never match.""" + out = set() + for n in bases: + out.add(n) + out.add("MPI_I" + n[4].lower() + n[5:]) + out.add(n + "_init") + return out + + +COLLECTIVE_NAMES = _expand_collectives(_COLLECTIVE_BASE) +POINT_TO_POINT_NAMES = set(""" + MPI_Send MPI_Recv MPI_Sendrecv MPI_Sendrecv_replace MPI_Bsend MPI_Ssend + MPI_Rsend MPI_Isend MPI_Irecv MPI_Ibsend MPI_Issend MPI_Irsend + MPI_Probe MPI_Iprobe MPI_Mprobe MPI_Improbe MPI_Mrecv MPI_Imrecv + MPI_Wait MPI_Waitall MPI_Waitany MPI_Waitsome MPI_Test MPI_Testall + MPI_Testany MPI_Testsome MPI_Get_count MPI_Cancel MPI_Start MPI_Startall + MPI_Send_init MPI_Bsend_init MPI_Ssend_init MPI_Rsend_init MPI_Recv_init + MPI_Isendrecv MPI_Isendrecv_replace +""".split()) +# Process-management / environment procedures. +PROCESS_NAMES = set(""" + MPI_Init MPI_Init_thread MPI_Finalize MPI_Initialized MPI_Finalized + MPI_Query_thread MPI_Is_thread_main MPI_Abort MPI_Get_processor_name + MPI_Get_version MPI_Get_library_version MPI_Wtime MPI_Wtick MPI_Pcontrol + MPI_Get_hw_resource_info +""".split()) +# One-sided (RMA) communication procedures whose names do not start with +# MPI_Win_ (window-management names are matched by the MPI_Win_ prefix). +RMA_NAMES = set(""" + MPI_Get MPI_Put MPI_Accumulate MPI_Get_accumulate MPI_Fetch_and_op + MPI_Compare_and_swap MPI_Rget MPI_Rput MPI_Raccumulate MPI_Rget_accumulate +""".split()) + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + +def setup_cli(): + parser = argparse.ArgumentParser( + description="Generate LLM-friendly docs for the Open MPI MPI APIs") + parser.add_argument('--srcdir', required=True, help='docs source dir') + parser.add_argument('--builddir', required=True, help='docs build dir') + parser.add_argument('--outdir', default=None, + help='staging output dir (default: /llms-build)') + parser.add_argument('--url-base', default=None, + help='absolute public docs URL base (e.g. ' + 'https://docs.open-mpi.org/en/main); forces ' + 'absolute links. Default: absolute from the Read ' + 'the Docs environment if present, otherwise ' + 'relative links for a local build') + return parser.parse_args() + + +# --------------------------------------------------------------------------- +# RST -> Markdown conversion (closed vocabulary; see spec) +# --------------------------------------------------------------------------- + +HEADING_LEVELS = {'=': 1, '-': 2, '^': 3, '~': 4, '"': 5, '+': 6, '#': 1, '*': 2} +UNDERLINE_RE = re.compile(r'^([=\-^~"+#*])\1{2,}\s*$') +DIRECTIVE_RE = re.compile(r'^(\s*)\.\.\s+([a-zA-Z0-9_-]+)::\s*(.*)$') +COMMENT_RE = re.compile(r'^(\s*)\.\.\s+(.*)$') +INCLUDE_RE = re.compile(r'^\s*\.\.\s+include::\s*(\S+)\s*$') +OPTION_RE = re.compile(r'^\s+:[a-zA-Z-]+:.*$') + +# Substitutions used in the man pages (whitelist; values filled from VERSION). +STATIC_SUBS = { + 'mdash': '—', + 'rarrow': '→', + 'deprecated_favor': 'this routine is deprecated in favor of', +} + + +def build_substitutions(version_info): + subs = dict(STATIC_SUBS) + if 'mpi_standard_version' in version_info: + subs['mpi_standard_version'] = version_info['mpi_standard_version'] + subs['mpi_standard_major_version'] = version_info['mpi_standard_major_version'] + subs['mpi_standard_minor_version'] = version_info['mpi_standard_minor_version'] + subs['ompi_ver'] = version_info['ompi_version'] + subs['ompi_series'] = version_info['ompi_series'] + return subs + + +def resolve_include_path(target, including_file, srcdir, builddir): + """Resolve an ``.. include::`` target to an existing file path or None.""" + base = os.path.dirname(including_file) + cand = os.path.normpath(os.path.join(base, target)) + candidates = [cand] + # Generated bindings live in the build tree; map srcdir->builddir. + srcdir = os.path.abspath(srcdir) + builddir = os.path.abspath(builddir) + ac = os.path.abspath(cand) + if ac.startswith(srcdir): + candidates.append(os.path.join(builddir, os.path.relpath(ac, srcdir))) + if ac.startswith(builddir): + candidates.append(os.path.join(srcdir, os.path.relpath(ac, builddir))) + for c in candidates: + if os.path.isfile(c): + return c + return None + + +def expand_includes(text, including_file, srcdir, builddir, _seen=None): + """Recursively inline ``.. include::`` directives, honoring :start-after:.""" + if _seen is None: + _seen = set() + out = [] + lines = text.split('\n') + i = 0 + while i < len(lines): + line = lines[i] + m = INCLUDE_RE.match(line) + if not m: + out.append(line) + i += 1 + continue + + target = m.group(1) + # Collect option lines (e.g., :start-after:) that follow the include. + start_after = None + i += 1 + while i < len(lines) and OPTION_RE.match(lines[i]): + opt = lines[i].strip() + mo = re.match(r':start-after:\s*(.*)$', opt) + if mo: + start_after = mo.group(1).strip() + i += 1 + + path = resolve_include_path(target, including_file, srcdir, builddir) + if path is None or path in _seen: + # Unresolvable or cyclic include: drop it (do not leak the directive). + continue + with open(path, encoding='utf-8') as fp: + inc = fp.read() + if start_after is not None: + idx = inc.find(start_after) + if idx >= 0: + inc = inc[idx + len(start_after):] + nested = expand_includes(inc, path, srcdir, builddir, + _seen | {path}) + out.append(nested) + return '\n'.join(out) + + +def make_ref_resolver(known_pages, linkmaker, source): + """Return a function that turns :ref: roles into Markdown links/text. + + ``source`` is the root-relative path of the file whose body is being + rendered (e.g. ``"llms/man-openmpi/man3/x"``), so that in a local + (relative-link) build the man-page links resolve correctly from it. + """ + # O(1) case-insensitive lookup: labels are often lowercase (e.g. mpi_send). + lower_map = {p.lower(): p for p in known_pages} + + def html_url(name): + return linkmaker.link("man-openmpi/man3/{}.3.html".format(name), source) + + ref_re = re.compile(r':ref:`([^`<]+?)(?:\s*<([^`>]+)>)?`') + + def repl(m): + text = m.group(1).strip() + target = (m.group(2) or text).strip() + # Normalize a target like "MPI_Send(3)" or label "mpi_send". + cand = target.replace('(3)', '').strip() + page = cand if cand in known_pages else lower_map.get(cand.lower()) + if page is not None: + return "[{}]({})".format(text, html_url(page)) + return text + + def resolve(text): + return ref_re.sub(repl, text) + + return resolve + + +HYPERLINK_RE = re.compile(r'`([^`<]+?)\s*<([^`>]+)>`_+') +# :doc:`Title ` or :doc:`/path` -- the target doc is human-facing RST, +# not part of the LLM corpus, so reduce the role to its display text. +DOC_RE = re.compile(r':doc:`([^`<]+?)(?:\s*<[^`>]+>)?`') + + +def resolve_inline(text, subs, ref_resolve): + # :ref: roles first (may span wrapped lines). + text = ref_resolve(text) + # :doc: roles -> display text (the target is not in the LLM corpus). + text = DOC_RE.sub(lambda m: m.group(1).strip(), text) + # RST external hyperlink: `text `_ (or `__`) -> [text](url). + text = HYPERLINK_RE.sub( + lambda m: '[{}]({})'.format(m.group(1).strip(), m.group(2).strip()), text) + # Substitutions (whitelist only -> literal text like MPI_{Wait|Test} is safe). + def sub_repl(m): + name = m.group(1) + return subs.get(name, m.group(0)) + text = re.sub(r'\|([a-zA-Z_]+)\|', sub_repl, text) + # RST inline literal ``x`` -> Markdown `x` (collapse double backticks). + text = text.replace('``', '`') + return text + + +def _dedent(block_lines): + indents = [len(l) - len(l.lstrip(' ')) for l in block_lines if l.strip()] + n = min(indents) if indents else 0 + return [l[n:] if len(l) >= n else l for l in block_lines] + + +def rst_to_markdown(text, subs, ref_resolve): + """Convert the (already include-expanded) RST text to Markdown. + + Prose lines are buffered and resolved as a paragraph so that inline + constructs that wrap across source lines (multi-line :ref: roles, RST + hyperlinks) are handled correctly. Code-block bodies are emitted verbatim + and never passed through inline resolution. + """ + lines = text.split('\n') + out = [] + para = [] + + def flush_para(): + if para: + joined = '\n'.join(para) + out.append(resolve_inline(joined, subs, ref_resolve)) + del para[:] + + i = 0 + n = len(lines) + while i < n: + line = lines[i] + + # Blank line: paragraph boundary. + if not line.strip(): + flush_para() + out.append('') + i += 1 + continue + + # Section heading: text line followed by an underline of punctuation. + if (i + 1 < n and UNDERLINE_RE.match(lines[i + 1]) + and not line.lstrip().startswith(('.. ', '* ', '- '))): + flush_para() + level = HEADING_LEVELS.get(lines[i + 1].strip()[0], 2) + title = resolve_inline(line.strip(), subs, ref_resolve) + out.append('#' * level + ' ' + title) + out.append('') + i += 2 + continue + + # Directives with a "::" (code-block, seealso, note, etc.). + dm = DIRECTIVE_RE.match(line) + if dm: + flush_para() + indent, name, arg = dm.group(1), dm.group(2), dm.group(3).strip() + i += 1 + # Skip any directive option lines, then a single blank line. + while i < n and OPTION_RE.match(lines[i]): + i += 1 + if i < n and not lines[i].strip(): + i += 1 + # Gather the indented body of the directive. + body = [] + base = len(indent) + while i < n: + cur = lines[i] + if not cur.strip(): + body.append('') + i += 1 + continue + cur_indent = len(cur) - len(cur.lstrip(' ')) + if cur_indent <= base: + break + body.append(cur) + i += 1 + while body and not body[-1].strip(): + body.pop() + body = _dedent(body) + + if name in ('code-block', 'code'): + lang = arg if arg else '' + out.append('```' + lang) + out.extend(body) + out.append('```') + out.append('') + elif name == 'math': + out.append('```math') + out.extend(body) + out.append('```') + out.append('') + elif name in ('note', 'admonition', 'warning', 'important'): + if name == 'admonition': + label = arg if arg else 'Note' + inline = None + else: + label = name.capitalize() + inline = arg if arg else None # content on the directive line + out.append('> **{}:**'.format(label)) + # Resolve the whole body as one block (joined with newlines) so + # inline constructs that wrap across source lines -- multi-line + # :ref: roles and `text `_ hyperlinks -- are handled, just + # like the top-level paragraph buffer above. + block = ([inline] if inline else []) + body + resolved = resolve_inline('\n'.join(block), subs, ref_resolve) + for b in resolved.split('\n'): + out.append('> ' + b if b.strip() else '>') + out.append('') + elif name == 'seealso': + out.append('## See also') + out.append('') + for b in body: + if b.strip(): + out.append(resolve_inline(b, subs, ref_resolve)) + out.append('') + elif name in ('toctree',): + pass # navigation: drop entirely + else: + # Unknown directive (e.g., list-table): keep its body as text. + for b in body: + if b.strip(): + out.append(resolve_inline(b, subs, ref_resolve)) + if body: + out.append('') + continue + + # Bare comment / anchor (".. _label:", ".. include_body", + # ".. mpi-bindings:", ".. some prose"): drop the comment and its + # indented continuation. + cm = COMMENT_RE.match(line) + if cm and not line.strip().startswith('.. |'): + flush_para() + base = len(cm.group(1)) + i += 1 + while i < n and (not lines[i].strip() + or (len(lines[i]) - len(lines[i].lstrip(' ')) > base)): + if not lines[i].strip(): + break + i += 1 + continue + + # RST literal block: a line ending in '::' introduces an indented + # verbatim block. Emit it as a fenced code block (verbatim, no inline + # resolution) rather than leaking the '::' marker into the Markdown. + stripped = line.rstrip() + if stripped.endswith('::'): + k = i + 1 + while k < n and not lines[k].strip(): + k += 1 + cur_indent = len(line) - len(line.lstrip(' ')) + if k < n and (len(lines[k]) - len(lines[k].lstrip(' '))) > cur_indent: + lead = stripped[:-2] + lead = lead.rstrip() if (not lead or lead.endswith(' ')) \ + else lead + ':' + if lead: + para.append(lead) + flush_para() + j = k + body = [] + while j < n: + cur = lines[j] + if not cur.strip(): + body.append('') + j += 1 + continue + if (len(cur) - len(cur.lstrip(' '))) <= cur_indent: + break + body.append(cur) + j += 1 + while body and not body[-1].strip(): + body.pop() + out.append('```') + out.extend(_dedent(body)) + out.append('```') + out.append('') + i = j + continue + + # Ordinary prose line: buffer until a paragraph boundary. + para.append(line) + i += 1 + + flush_para() + + # Collapse 3+ blank lines to a single blank line. + md = '\n'.join(out) + md = re.sub(r'\n{3,}', '\n\n', md) + return md.strip() + '\n' + + +# --------------------------------------------------------------------------- +# Per-symbol Markdown page +# --------------------------------------------------------------------------- + +def page_markdown(page_name, expanded, subs, ref_resolve, linkmaker, source_rel, + page_rel): + """Render a per-symbol man3 page. ``page_rel`` is the root-relative path of + the file this Markdown will live in, so its Canonical-HTML link resolves + correctly in a local (relative-link) build.""" + body = rst_to_markdown(expanded, subs, ref_resolve) + html = linkmaker.link("man-openmpi/man3/{}.3.html".format(page_name), + page_rel) + header = ( + "\n\n" + .format(src=source_rel, html=html)) + return header + body + + +# --------------------------------------------------------------------------- +# Parameter / binding extraction +# --------------------------------------------------------------------------- + +def parse_param_descriptions(rst_text): + """Return {param_name: description} from INPUT/OUTPUT PARAMETER bullets.""" + descs = {} + bullet = re.compile(r'^\*\s+``([^`]+)``:\s*(.*)$') + for line in rst_text.split('\n'): + m = bullet.match(line.strip()) + if m: + descs[m.group(1).strip()] = m.group(2).strip() + return descs + + +def _normalize_direction(d): + if d is None: + return 'unknown' + val = d.value if hasattr(d, 'value') else str(d) + val = val.split('.')[-1].lower() + return val if val in ('in', 'out', 'inout') else 'unknown' + + +# Maps a binding language to the suppress-token that hides a parameter from +# that language's signature, so each binding can be given only its own +# parameters (e.g. ierror appears in the Fortran bindings but not in C; +# argc/argv appear only in C). +_LANG_SUPPRESS_TOKEN = { + LANG_C: 'c_parameter', + LANG_MPIFH: 'f90_parameter', + LANG_USE_MPI: 'f90_parameter', + LANG_F08: 'f08_parameter', +} + + +def _fortran_intent(param_direction): + """Map a semantic direction to a Fortran INTENT attribute string.""" + d = (param_direction or '').lower() + if d in ('in', 'out', 'inout'): + return 'INTENT({})'.format(d.upper()) + return None + + +def normalized_parameters(proc, param_descs, subs, ref_resolve): + """Build the normalized parameter list for a procedure. + + Iterates the *full* per-procedure parameter metadata rather than the LIS + view: the LIS view (``express.lis``) suppresses parameters that do not + appear in the language-independent signature (e.g. MPI_Init's argc/argv + and ierror are all LIS-suppressed), which would drop real parameters and + yield empty/partial lists. Using the full set keeps the catalog + signature/parameter authoritative. + + Each returned entry carries the public schema fields plus, under a + ``_meta`` key, the upstream per-parameter facts (``suppress`` set and the + F08 ``optional`` flag, plus C const-ness) that :func:`standard_bindings` + needs to render an accurate *per-binding* parameter list. ``_meta`` is + stripped before serialization (see :func:`_public_param`). + + At the record level, ``type`` is the MPI semantic kind name (language + neutral) and ``direction`` is the semantic direction. ``intent`` and + ``optional`` are left non-committal at the record level (``null`` and the + schema-sanctioned ``"unknown"`` sentinel respectively) because both are + per-binding properties; the precise values are emitted per binding. + + Human descriptions come from the RST parameter bullets when present, + falling back to the upstream metadata description (and its kind default). + """ + # The full parameter set (with semantic kinds) is only available on the + # raw parseset; the public binding views each filter a different subset. + # KINDS / the kind default-description map come from the same embedded + # pympistandard package that ``proc`` belongs to. + from pympistandard.storage import KINDS + from pympistandard.lis import _DEFAULT_DESCRIPTIONS + + params = [] + for p in proc._parseset.get('parameters', []): + name = p['name'] + direction = _normalize_direction(p.get('lis_direction')) + kind_key = p.get('kind') + kind = KINDS[kind_key] if kind_key is not None else None + kind_name = getattr(kind, 'name', None) if kind is not None else None + desc = param_descs.get(name) + if not desc: + desc = p.get('desc') or None + if not desc and kind_name is not None: + desc = _DEFAULT_DESCRIPTIONS[kind_name] or None + if desc: + desc = resolve_inline(desc, subs, ref_resolve) + params.append({ + "name": name, + "direction": direction, + "type": kind_name, + "intent": None, + "optional": "unknown", + "description": desc, + "_meta": { + "suppress": set((p.get('suppress') or '').split()), + "optional": bool(p.get('optional')), + "constant": bool(p.get('constant')), + "param_direction": p.get('param_direction'), + }, + }) + return params + + +def _public_param(param): + """Return a copy of a normalized parameter without the internal ``_meta``.""" + return {k: v for k, v in param.items() if k != '_meta'} + + +def _binding_parameters(params, language): + """Project the normalized parameter list onto a single binding. + + Drops parameters suppressed for ``language`` and fills in the per-binding + ``intent`` (Fortran INTENT / C const-ness) and ``optional`` (the F08 + OPTIONAL attribute) that are ambiguous on the language-neutral list. + """ + token = _LANG_SUPPRESS_TOKEN.get(language) + out = [] + for p in params: + meta = p['_meta'] + if token is not None and token in meta['suppress']: + continue + bp = _public_param(p) + if language == LANG_C: + bp['intent'] = 'const' if meta['constant'] else None + # C has no optional-argument concept for these parameters. + bp['optional'] = False + elif language == LANG_F08: + bp['intent'] = _fortran_intent(meta['param_direction']) + bp['optional'] = meta['optional'] + else: # mpif.h / use mpi: positional, no OPTIONAL attribute + bp['intent'] = _fortran_intent(meta['param_direction']) + bp['optional'] = False + out.append(bp) + return out + + +def standard_bindings(proc, params, name): + """Build the bindings array for a standard procedure (proper-case name). + + Each binding gets only its own parameters (projected from the + language-neutral ``params`` via :func:`_binding_parameters`): e.g. the C + binding excludes the Fortran-only ``ierror`` and the Fortran bindings + exclude the C-only ``argc``/``argv``. + """ + bindings = [] + + c = common.c_binding(proc) + if c is not None: + c_params = _binding_parameters(params, LANG_C) + bindings.append({"language": LANG_C, "procedure": name, + "signature": c, "large_count": False, + "return_convention": "int return code", + "parameters": c_params}) + clarge = common.c_binding_large(proc) + if clarge is not None: + bindings.append({"language": LANG_C, "procedure": name + "_c", + "signature": clarge, "large_count": True, + "return_convention": "int return code", + "parameters": c_params}) + + f90 = common.f90_binding(proc) + if f90 is not None: + for lang in (LANG_MPIFH, LANG_USE_MPI): + bindings.append({"language": lang, "procedure": name, + "signature": f90, "large_count": False, + "return_convention": "ierror argument", + "parameters": _binding_parameters(params, lang)}) + + f08 = common.f08_binding(proc) + if f08 is not None: + f08_params = _binding_parameters(params, LANG_F08) + bindings.append({"language": LANG_F08, "procedure": name, + "signature": f08, "large_count": False, + "return_convention": "ierror argument", + "parameters": f08_params}) + f08large = common.f08_binding_large(proc) + if f08large is not None: + bindings.append({"language": LANG_F08, "procedure": name, + "signature": f08large, "large_count": True, + "return_convention": "ierror argument", + "parameters": f08_params}) + return bindings + + +def categorize(name): + if name in COLLECTIVE_NAMES: + return "collective" + if name in POINT_TO_POINT_NAMES: + return "point-to-point" + if name in PROCESS_NAMES: + return "process" + if name in RMA_NAMES: + return "rma" + for prefix, cat in CATEGORY_PREFIXES: + if name.startswith(prefix): + return cat + if name.startswith(("MPIX_", "OMPI_")): + return "extension" + return "other" + + +def _extract_declaration(code_lines, name): + """Return only the C declaration of ``name`` from a code block's lines. + + Extension/deprecated man pages sometimes put several function declarations + in one SYNTAX code block (e.g. MPIX_Comm_agree and MPIX_Comm_iagree); a + per-procedure record should carry only its own signature, not all of them. + Falls back to the whole block if ``name`` is not found. + """ + pat = re.compile(r'\b' + re.escape(name) + r'\s*\(') + start = None + for i, ln in enumerate(code_lines): + if pat.search(ln): + start = i + break + if start is None: + return '\n'.join(code_lines) + out = [] + depth = 0 + for ln in code_lines[start:]: + out.append(ln) + depth += ln.count('(') - ln.count(')') + if depth <= 0: + break + return '\n'.join(out) + + +def extension_bindings_from_rst(rst_text, name=None): + """Best-effort: pull the C signature out of an extension page's code block. + + If ``name`` is given and the block declares several procedures, return only + the declaration matching ``name``. + """ + lines = rst_text.split('\n') + for i, line in enumerate(lines): + m = DIRECTIVE_RE.match(line) + if m and m.group(2) in ('code-block', 'code'): + base = len(m.group(1)) + j = i + 1 + while j < len(lines) and not lines[j].strip(): + j += 1 + # Body lines are those indented deeper than the directive itself + # (man-page code blocks use a 3-space indent, not necessarily 4). + body = [] + while j < len(lines): + cur = lines[j] + if not cur.strip(): + j += 1 + continue + indent = len(cur) - len(cur.lstrip(' \t')) + if indent <= base: + break + body.append(cur.strip()) + j += 1 + code_lines = [b for b in body if not b.startswith('#')] + if name: + sig = _extract_declaration(code_lines, name).strip() + else: + sig = '\n'.join(code_lines).strip() + if sig: + return [{"language": LANG_C, "procedure": None, + "signature": sig, "large_count": False, + "return_convention": "int return code", + "parameters": []}] + return [] + + +def parse_seealso(rst_text): + """Collect the symbol names referenced in a page's .. seealso:: block.""" + names = [] + lines = rst_text.split('\n') + in_block = False + for line in lines: + if line.strip().startswith('.. seealso::'): + in_block = True + continue + if in_block: + if line.strip() and not line.startswith((' ', '\t')): + break + for m in re.finditer(r':ref:`([^`<]+?)(?:\s*<[^`>]+>)?`', line): + nm = m.group(1).strip().replace('(3)', '').strip() + if nm and nm not in names: + names.append(nm) + return names + + +def short_description(rst_text, subs, ref_resolve): + """Extract the one-line short description that follows the page title.""" + lines = rst_text.split('\n') + i = 0 + n = len(lines) + # Skip to past the title underline. + while i + 1 < n and not (lines[i].strip() and UNDERLINE_RE.match(lines[i + 1])): + i += 1 + i += 2 + # Find the first non-blank, non-directive paragraph. + buf = [] + while i < n: + s = lines[i].strip() + if not s: + if buf: + break + i += 1 + continue + if s.startswith('.. ') or s == '::': + # Skip directives and a bare RST literal-block marker; the real + # short description may follow inside the literal block. + i += 1 + continue + buf.append(s) + i += 1 + text = ' '.join(buf) + text = resolve_inline(text, subs, ref_resolve) + # Drop a leading "NAME — " prefix for brevity. + text = re.sub(r'^\[?[A-Za-z_0-9]+\]?(\([^)]*\))?\s*[—-]\s*', '', text) + return text.strip() + + +def best_effort_record(name, kind, category, expanded, docset, urls, + source_rel, errors_html, documented_with, bindings=None): + """Build a best-effort catalog record for a procedure whose metadata is not + in pympistandard (Open MPI extensions, or deprecated/removed MPI APIs). + + The signature is recovered from the man-page RST rather than the structured + metadata, so structured parameter fields are left empty (``unknown``). + """ + if bindings is None: + bindings = extension_bindings_from_rst(expanded, name) + for b in bindings: + b["procedure"] = name + languages = sorted({b['language'] for b in bindings}) + return { + "schema_version": SCHEMA_VERSION, + "project": "open-mpi", + "docset": docset, + "name": name, + "kind": kind, + "standard": None, + "category": category, + "languages": languages, + "bindings": bindings, + "parameters": [], + "semantics": None, + "errors": {"convention": None, "url": errors_html}, + "seealso": parse_seealso(expanded), + "documented_with": documented_with, + "urls": urls, + "sources": {"rst": source_rel, "binding_metadata": "rst"}, + } + + +# --------------------------------------------------------------------------- +# Manifest helpers +# --------------------------------------------------------------------------- + +def media_type_for(path): + return MEDIA_TYPES.get(os.path.splitext(path)[1], 'application/octet-stream') + + +def estimate_tokens(data_bytes): + # Coarse heuristic: ~4 bytes per token. + return (len(data_bytes) + 3) // 4 + + +# Absolute paths of every artifact written this run; used to build the +# manifest and to prune stale files left from previous runs. +WRITTEN_PATHS = [] + + +def write_file(path, text): + path = os.path.abspath(path) + os.makedirs(os.path.dirname(path), exist_ok=True) + data = text.encode('utf-8') + WRITTEN_PATHS.append(path) + # Only write when changed (deterministic, avoids needless churn / mtime + # bumps so incremental docs builds don't see spurious changes). + if os.path.exists(path): + with open(path, 'rb') as fp: + if fp.read() == data: + return + with open(path, 'wb') as fp: + fp.write(data) + + +def prune_stale(outdir): + """Remove files under outdir that were not written this run, plus empties.""" + keep = set(WRITTEN_PATHS) + for root, _dirs, files in os.walk(outdir): + for fn in files: + full = os.path.abspath(os.path.join(root, fn)) + if full not in keep: + os.remove(full) + for root, _dirs, _files in os.walk(outdir, topdown=False): + if os.path.abspath(root) != os.path.abspath(outdir) and not os.listdir(root): + os.rmdir(root) + + +# --------------------------------------------------------------------------- +# Command (man1) man pages +# --------------------------------------------------------------------------- + +def command_man_pages(srcdir, builddir, llms_dir, subs, ref_resolve, linkmaker): + """Convert the section-1 command man pages (man1) to Markdown. + + These document Open MPI commands (mpirun, ompi_info, the wrapper + compilers, ...), not MPI APIs, so they get a Markdown corpus only -- no + JSONL catalog records. Returns the sorted list of page names written + (e.g. "mpirun.1") for indexing in llms.txt. The bare ".so" redirect + stubs (mpicc.1 -> ompi-wrapper-compiler.1) are not RST and are skipped. + + ``ref_resolve`` must resolve :ref: links from the man1 page location, and + the Canonical-HTML header link is built from that same location, so a local + (relative-link) build resolves correctly. + """ + man1_src = os.path.join(srcdir, 'man-openmpi', 'man1') + if not os.path.isdir(man1_src): + return [] + pages = [] + for fn in sorted(os.listdir(man1_src)): + if fn == 'index.rst' or not fn.endswith('.1.rst'): + continue + name = fn[:-len('.rst')] # "mpirun.1.rst" -> "mpirun.1" + rst_path = os.path.join(man1_src, fn) + source_rel = os.path.relpath(rst_path, srcdir) + page_rel = "llms/man-openmpi/man1/{}.md".format(name) + with open(rst_path, encoding='utf-8') as fp: + raw = fp.read() + expanded = expand_includes(raw, rst_path, srcdir, builddir) + body = rst_to_markdown(expanded, subs, ref_resolve) + html = linkmaker.link("man-openmpi/man1/{}.html".format(name), page_rel) + header = ("\n\n" + .format(src=source_rel, html=html)) + write_file(os.path.join(llms_dir, 'man-openmpi', 'man1', + '{}.md'.format(name)), header + body) + pages.append(name) + return pages + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- + +def main(): + args = setup_cli() + srcdir = os.path.abspath(args.srcdir) + builddir = os.path.abspath(args.builddir) + top_srcdir = os.path.dirname(srcdir) + outdir = os.path.abspath(args.outdir) if args.outdir \ + else os.path.join(builddir, 'llms-build') + llms_dir = os.path.join(outdir, 'llms') + + version_info = common.ompi_version_info(top_srcdir) + + # Resolve the link strategy for this build (see the LinkMaker docstring). + # + # A local build from git produces the artifacts for a *local* LLM/tool that + # reads the files straight off disk: there is no docs.open-mpi.org site in + # play, so the RTD URL scheme is irrelevant and links are made RELATIVE to + # each file (a self-contained, portable tree). A Read the Docs build + # publishes to docs.open-mpi.org/en//, so there links are ABSOLUTE and + # use that published version slug (e.g. .../en/main/ for the "main" branch). + # An explicit --url-base / OMPI_LLM_URL_BASE override forces absolute links. + rtd_slug = os.environ.get('READTHEDOCS_VERSION') + explicit_base = args.url_base or os.environ.get('OMPI_LLM_URL_BASE') + if explicit_base: + url_base = explicit_base + elif os.environ.get('READTHEDOCS_CANONICAL_URL'): + url_base = os.environ['READTHEDOCS_CANONICAL_URL'] + elif rtd_slug: + url_base = "https://docs.open-mpi.org/en/{}".format(rtd_slug) + else: + url_base = None # local build -> relative links + lm = LinkMaker(url_base) + + subs = build_substitutions(version_info) + std = common.load_pympistandard(srcdir) + + man3_src = os.path.join(srcdir, 'man-openmpi', 'man3') + page_re = re.compile(r'^((?:MPI_|MPIX_|OMPI_).*)\.3\.rst$') + page_files = {} + for fn in sorted(os.listdir(man3_src)): + m = page_re.match(fn) + if m: + page_files[m.group(1)] = os.path.join(man3_src, fn) + known_pages = set(page_files.keys()) + # :ref: links must resolve from the location of the file they end up in. + # The per-symbol man3 pages, the man1 pages, and the files that live + # directly under llms/ (aggregate corpus, language corpora, JSONL records) + # are at different depths, so each gets a resolver bound to its own + # location. (In an absolute/RTD build all three produce identical links.) + res_man3 = make_ref_resolver(known_pages, lm, "llms/man-openmpi/man3/x") + res_man1 = make_ref_resolver(known_pages, lm, "llms/man-openmpi/man1/x") + res_llms = make_ref_resolver(known_pages, lm, "llms/x") + + # Map each procedure (lowercase) to its co-documented procedures. + directives = common.read_rst_man_pages(srcdir) + lower_to_actual = {p.lower(): p for p in known_pages} + co_docs = {} + for _page, procs in directives.items(): + if not procs: + continue + for p in procs: + co_docs[p] = [lower_to_actual.get(q, q) for q in procs if q != p] + + docset = {"ompi_version": version_info['ompi_version'], + "ompi_series": version_info['ompi_series'], + "schema_version": SCHEMA_VERSION} + # The JSONL catalog lives at llms/openmpi-mpi-api.jsonl, so its record URLs + # are resolved from there. + jsonl_rel = "llms/openmpi-mpi-api.jsonl" + errors_html = lm.link("man-openmpi/man3/MPI_Errors.3.html", jsonl_rel) + + records = [] + page_md = {} # page_name -> per-symbol-page markdown (man3 depth) + agg_md = {} # page_name -> same body rendered for the llms/ corpus + short_desc = {} # page_name -> short description + + for name in sorted(page_files): + rst_path = page_files[name] + source_rel = os.path.relpath(rst_path, srcdir) + with open(rst_path, encoding='utf-8') as fp: + raw = fp.read() + expanded = expand_includes(raw, rst_path, srcdir, builddir) + + page_rel = "llms/man-openmpi/man3/{}.3.md".format(name) + page_md[name] = page_markdown(name, expanded, subs, res_man3, + lm, source_rel, page_rel) + # The same body is inlined into the aggregate corpus at llms/, a + # different depth, so in a relative-link build it must be re-rendered + # from that location; in an absolute build the links are identical, so + # reuse the per-symbol render. + if lm.absolute: + agg_md[name] = page_md[name] + else: + agg_md[name] = page_markdown(name, expanded, subs, res_llms, + lm, source_rel, + "llms/openmpi-mpi-api.md") + short_desc[name] = short_description(expanded, subs, res_llms) + + low = name.lower() + urls = { + "html": lm.link("man-openmpi/man3/{}.3.html".format(name), + jsonl_rel), + "markdown": lm.link("llms/man-openmpi/man3/{}.3.md".format(name), + jsonl_rel), + } + if low in std.PROCEDURES: + proc = std.PROCEDURES[low] + params = normalized_parameters( + proc, parse_param_descriptions(expanded), subs, res_llms) + bindings = standard_bindings(proc, params, name) + params = [_public_param(p) for p in params] + languages = sorted({b['language'] for b in bindings}) + rec = { + "schema_version": SCHEMA_VERSION, + "project": "open-mpi", + "docset": docset, + "name": name, + "kind": "standard", + "standard": {"version": None}, + "category": categorize(name), + "languages": languages, + "bindings": bindings, + "parameters": params, + "semantics": { + "blocking": None, + "collective": name in COLLECTIVE_NAMES, + "local": None, + "initialization_required": None, + "threading_notes": None, + }, + "errors": { + "convention": "C: int return code; Fortran: ierror argument.", + "url": errors_html, + }, + "seealso": parse_seealso(expanded), + "documented_with": co_docs.get(low, []), + "urls": urls, + "sources": {"rst": source_rel, "binding_metadata": "pympistandard"}, + } + records.append(rec) + elif name.startswith(("MPIX_", "OMPI_")): + records.append(best_effort_record( + name, "extension", "extension", expanded, docset, urls, + source_rel, errors_html, co_docs.get(low, []))) + else: + # An MPI_* page not known to pympistandard documents a + # deprecated/removed procedure when a signature can be recovered + # from its RST; otherwise it is an overview/non-procedure page + # (e.g. MPI_T.3, MPI_Errors.3) and gets Markdown only, no record. + ext_bindings = (extension_bindings_from_rst(expanded, name) + if name.startswith("MPI_") else []) + # Only a real procedure page: its recovered signature must actually + # declare this name (filters overview pages such as MPI_T.3 whose + # code blocks contain unrelated example code). + declares = ext_bindings and re.search( + r'\b' + re.escape(name) + r'\s*\(', ext_bindings[0]['signature']) + if declares: + records.append(best_effort_record( + name, "deprecated", categorize(name), expanded, docset, + urls, source_rel, errors_html, co_docs.get(low, []), + bindings=ext_bindings)) + + records.sort(key=lambda r: r['name']) + + # --- Write per-symbol Markdown pages --- + for name in sorted(page_md): + write_file(os.path.join(llms_dir, 'man-openmpi', 'man3', + '{}.3.md'.format(name)), page_md[name]) + + # --- Write command (man1) Markdown pages (corpus only, no records) --- + man1_pages = command_man_pages(srcdir, builddir, llms_dir, subs, + res_man1, lm) + + # --- Write the JSONL catalog --- + jsonl = ''.join(json.dumps(r, ensure_ascii=False, sort_keys=True) + '\n' + for r in records) + write_file(os.path.join(llms_dir, 'openmpi-mpi-api.jsonl'), jsonl) + + # --- Aggregate Markdown corpora --- + header = ("# Open MPI MPI API reference ({ver})\n\n" + "Generated for LLM and retrieval consumers. The MPI Standard is " + "authoritative for portable MPI semantics; `MPIX_*` and `OMPI_*` " + "are Open MPI extensions.\n\n" + .format(ver=version_info['ompi_version'])) + all_md = header + '\n\n---\n\n'.join(agg_md[n] for n in sorted(agg_md)) + write_file(os.path.join(llms_dir, 'openmpi-mpi-api.md'), all_md) + + lang_corpora = { + LANG_C: 'openmpi-mpi-api-c.md', + LANG_MPIFH: 'openmpi-mpi-api-fortran-mpifh.md', + LANG_USE_MPI: 'openmpi-mpi-api-fortran-use-mpi.md', + LANG_F08: 'openmpi-mpi-api-fortran-use-mpi-f08.md', + } + lang_titles = { + LANG_C: 'C', LANG_MPIFH: "Fortran (mpif.h)", + LANG_USE_MPI: "Fortran (use mpi)", LANG_F08: "Fortran (use mpi_f08)", + } + lang_fence = {LANG_C: 'c', LANG_MPIFH: 'fortran', + LANG_USE_MPI: 'fortran', LANG_F08: 'fortran'} + for lang, fname in lang_corpora.items(): + parts = ["# Open MPI MPI API reference — {} interface ({})\n" + .format(lang_titles[lang], version_info['ompi_version'])] + for rec in records: + blist = [b for b in rec['bindings'] if b['language'] == lang] + if not blist: + continue + parts.append("## {}\n".format(rec['name'])) + sd = short_desc.get(rec['name']) + if sd: + parts.append(sd + "\n") + for b in blist: + parts.append("```{}\n{}\n```\n".format(lang_fence[lang], + b['signature'])) + parts.append("Documentation: {}\n".format(rec['urls']['html'])) + write_file(os.path.join(llms_dir, fname), '\n'.join(parts) + '\n') + + # --- Copy curated source files (those that exist) --- + llms_src = os.path.join(srcdir, 'llms-src') + present_curated = set() + for fn in CURATED_FILES: + src = os.path.join(llms_src, fn) + if os.path.isfile(src): + with open(src, 'rb') as fp: + write_file(os.path.join(llms_dir, fn), + fp.read().decode('utf-8')) + present_curated.add(fn) + + # --- versioned llms.txt (timestamp-free, listable in the manifest) --- + llms_txt = build_llms_txt(version_info, lm, rtd_slug, present_curated, + man1_pages) + write_file(os.path.join(outdir, 'llms.txt'), llms_txt) + + # --- Manifest (the only artifact carrying build identity) --- + manifest = build_manifest(outdir, lm, version_info, top_srcdir) + write_file(os.path.join(llms_dir, 'openmpi-docs-manifest.json'), + json.dumps(manifest, ensure_ascii=False, indent=1, + sort_keys=True) + '\n') + + # Remove any stale files from a previous run so the published tree matches + # the manifest exactly. + prune_stale(outdir) + + print(" GENERATE LLM docs ({} records, {} pages)" + .format(len(records), len(page_md))) + + +def _this_build_description(version_info, linkmaker, rtd_slug): + """One sentence identifying which build this llms.txt belongs to. + + For a Read the Docs "main" build the slug is "main" but the version it + represents is the VERSION-file series, so both are named (the dual + attribution requested in the spec). A local build has no published slug; + it is identified by its version and noted as relative/local. + """ + series = version_info['ompi_series'] # e.g. "v6.1.x" + version = version_info['ompi_version'] # e.g. "6.1.0" + if not linkmaker.absolute: + return ("a local build of Open MPI {} (the {} series), built from a git " + "checkout. The links in this file are relative to this file's " + "own location: this build targets a local tool that reads these " + "files directly off disk, so it deliberately does not use the " + "docs.open-mpi.org URL scheme.".format(version, series)) + if rtd_slug == 'main': + return ('the "main" git branch (the development tip), which currently ' + 'identifies itself as the Open MPI {} series. It is published ' + 'at https://docs.open-mpi.org/en/main/ .'.format(series)) + if rtd_slug: + return ('Open MPI {slug}, published at ' + 'https://docs.open-mpi.org/en/{slug}/ .'.format(slug=rtd_slug)) + return 'Open MPI {} (the {} series).'.format(version, series) + + +def build_llms_txt(version_info, linkmaker, rtd_slug, present_curated, + man1_pages=None): + v = version_info['ompi_version'] + + def link(target): + # All links in llms.txt are resolved from the file's own location + # (the documentation version root, where llms.txt lives). + return linkmaker.link(target, "llms.txt") + + lines = [ + "# Open MPI {}".format(v), "", + "Open MPI is an open source implementation of the Message Passing " + "Interface (MPI) specification. This file indexes LLM-friendly " + "documentation artifacts for this documentation version.", "", + "## About this file", "", + "This is the llms.txt for {}".format( + _this_build_description(version_info, linkmaker, rtd_slug)), "", + "Open MPI publishes a separate set of these LLM-friendly artifacts for " + "each documentation version. For Open MPI v5.0.0 and later they all " + "follow the same URL scheme:", "", + " https://docs.open-mpi.org/en/VERSION_SLUG/", "", + "where VERSION_SLUG is either:", "", + "- `main` --- the build from Open MPI's `main` git branch (the " + "development tip); or", + "- `vA.B.C` --- where A and B are the major and minor version numbers. " + "If C is `x` (e.g. `v6.1.x`) it is the build from that release branch; " + "if C is an integer (e.g. `v6.1.0`) it is the build from that specific " + "git-tagged release.", "", + "If you found this file but are looking for the LLM documentation of a " + "different Open MPI version, substitute the desired VERSION_SLUG into " + "the scheme above (for example, " + "https://docs.open-mpi.org/en/main/llms.txt).", "", + "Documentation for Open MPI versions older than v5.0.0 is not published " + "in this format; for those, see " + "https://github.com/open-mpi/ompi/blob/v4.1.x/README , " + "https://www.open-mpi.org/faq/ , and https://www.open-mpi.org/doc/ .", + "", + "- The MPI Standard (https://www.mpi-forum.org/docs/) is authoritative " + "for portable MPI semantics.", + "- `MPIX_*` and `OMPI_*` APIs are Open MPI extensions, not portable " + "MPI Standard APIs.", "", + "## API catalog and corpora", "", + "- [Machine-readable API catalog (JSONL)]({})".format( + link("llms/openmpi-mpi-api.jsonl")), + "- [Aggregate Markdown corpus]({})".format( + link("llms/openmpi-mpi-api.md")), + "- [C interface]({})".format(link("llms/openmpi-mpi-api-c.md")), + "- [Fortran mpif.h interface]({})".format( + link("llms/openmpi-mpi-api-fortran-mpifh.md")), + "- [Fortran use mpi interface]({})".format( + link("llms/openmpi-mpi-api-fortran-use-mpi.md")), + "- [Fortran use mpi_f08 interface]({})".format( + link("llms/openmpi-mpi-api-fortran-use-mpi-f08.md")), + ] + # Only advertise curated artifacts that are actually published. + if 'openmpi-mpi-interface-guide.md' in present_curated: + lines.append("- [Interface selection guide]({})".format( + link("llms/openmpi-mpi-interface-guide.md"))) + if 'openmpi-mpi-examples.md' in present_curated: + lines.append("- [Curated examples]({})".format( + link("llms/openmpi-mpi-examples.md"))) + lines += [ + "- [Artifact manifest]({})".format( + link("llms/openmpi-docs-manifest.json")), "", + ] + # Runtime introspection: the artifacts above describe the API for this + # version; a *specific installation* (version, build config, available + # components, and the run-time MCA parameters its components expose) is + # discovered from that installation with the ompi_info command. + if 'openmpi-runtime-introspection.md' in present_curated: + lines += [ + "## Runtime introspection (installed Open MPI)", "", + "The artifacts above describe the MPI API for this version. To " + "inspect a *specific* installation --- its exact version, build " + "configuration, available MCA components, and the full set of " + "run-time tunables (MCA parameters) --- run the `ompi_info` command " + "on that installation; `ompi_info --all --parsable` emits a " + "comprehensive, self-describing, machine-readable dump.", "", + "- [Runtime introspection with ompi_info]({})".format( + link("llms/openmpi-runtime-introspection.md")), "", + ] + # Command (man1) man pages: a Markdown corpus, no catalog records. + if man1_pages: + lines += ["## Command man pages", ""] + lines += ["- [{n}]({u})".format( + n=n, u=link("llms/man-openmpi/man1/{}.md".format(n))) + for n in man1_pages] + lines += [""] + lines += [ + "## Human-facing documentation", "", + "- [Building MPI applications]({})".format( + link("building-apps/index.html")), + "- [Launching MPI applications]({})".format( + link("launching-apps/index.html")), + "- [Tuning MPI applications]({})".format( + link("tuning-apps/index.html")), + ] + return '\n'.join(lines) + '\n' + + +ALL_LANGS_LIST = [LANG_C, LANG_MPIFH, LANG_USE_MPI, LANG_F08] +# Artifacts whose content is specific to a language/interface set. +ARTIFACT_LANGUAGES = { + 'llms/openmpi-mpi-api.md': ALL_LANGS_LIST, + 'llms/openmpi-mpi-api.jsonl': ALL_LANGS_LIST, + 'llms/openmpi-mpi-api-c.md': [LANG_C], + 'llms/openmpi-mpi-api-fortran-mpifh.md': [LANG_MPIFH], + 'llms/openmpi-mpi-api-fortran-use-mpi.md': [LANG_USE_MPI], + 'llms/openmpi-mpi-api-fortran-use-mpi-f08.md': [LANG_F08], +} + + +def build_manifest(outdir, linkmaker, version_info, top_srcdir): + manifest_rel = 'llms/openmpi-docs-manifest.json' + # Inventory exactly the artifacts written this run (so stale leftovers are + # never listed). The manifest is not yet written here, so it is naturally + # excluded; guard against it anyway. + artifacts = [] + seen = set() + for full in WRITTEN_PATHS: + rel = os.path.relpath(full, outdir) + if rel == manifest_rel or rel in seen: + continue + seen.add(rel) + with open(full, 'rb') as fp: + data = fp.read() + entry = { + # path stays root-relative (the validator locates files by it); + # only the URL follows the build's link strategy. + "path": rel, + "url": linkmaker.link(rel, manifest_rel), + "media_type": media_type_for(full), + "sha256": hashlib.sha256(data).hexdigest(), + "bytes": len(data), + "tokens_estimate": estimate_tokens(data), + } + mm = re.match(r'llms/man-openmpi/man3/(.+)\.3\.md$', rel) + if mm: + entry["symbols"] = [mm.group(1)] + if rel in ARTIFACT_LANGUAGES: + entry["languages"] = ARTIFACT_LANGUAGES[rel] + artifacts.append(entry) + artifacts.sort(key=lambda e: e['path']) + + manifest = { + "artifact_schema_version": ARTIFACT_SCHEMA_VERSION, + "project": "open-mpi", + "ompi_version": version_info['ompi_version'], + "ompi_series": version_info['ompi_series'], + "rtd_version_slug": os.environ.get('READTHEDOCS_VERSION'), + "git_commit": common.git_commit(top_srcdir), + "git_describe": common.git_describe(top_srcdir), + "generated_at": common.generated_at_utc(top_srcdir), + "artifacts": artifacts, + } + return manifest + + +if __name__ == "__main__": + main() diff --git a/docs/generate-mpi-man3-bindings.py b/docs/generate-mpi-man3-bindings.py index cc922a0af42..33713163565 100755 --- a/docs/generate-mpi-man3-bindings.py +++ b/docs/generate-mpi-man3-bindings.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 # -# Copyright (c) 2025 Jeffrey M. Squyres. All rights reserved. +# Copyright (c) 2025-2026 Jeffrey M. Squyres. All rights reserved. # # $COPYRIGHT$ # @@ -17,16 +17,16 @@ # # Using this method, we can emit both "regular" and "embiggened" # versions of each API (if an "embiggened" version exists). +# +# Logic that is shared with the LLM-friendly documentation generator +# (parsing the .. mpi-bindings: directives, loading pympistandard, and +# rendering the C/F90/F08 binding strings) lives in ompi_docs_common.py. -import re import os -import sys import textwrap import argparse -from pathlib import Path -from collections import defaultdict -from pprint import pprint +import ompi_docs_common as common #---------------- @@ -94,8 +94,8 @@ def generate(func_name_arg, output_dir, directives): # C bindings emitted_header = False for data in func_names_data: - binding = str(data.express.iso_c) - if binding and len(binding) > 0 and binding != 'None': + binding = common.c_binding(data) + if binding is not None: have_binding = True if not emitted_header: out.append('C Syntax') @@ -105,23 +105,15 @@ def generate(func_name_arg, output_dir, directives): out.append(blank) emitted_header = True - # Per - # https://github.com/mpi-forum/pympistandard/issues/25, - # there's a bug that the binding for MPI_Pcontrol returns - # a string containing "\ldots", not "...". Do a manual - # replacement here. - binding = binding.replace(r'\ldots', '...') - line = textwrap.fill(binding, width=72, initial_indent=' ', subsequent_indent = ' ') out.append(line) out.append(blank) - if data.has_embiggenment(): - binding = str(data.express.embiggen.iso_c) - binding = binding.replace(r'\ldots', '...') - line = textwrap.fill(binding, width=72, + large = common.c_binding_large(data) + if large is not None: + line = textwrap.fill(large, width=72, initial_indent=' ', subsequent_indent = ' ') out.append(line) @@ -131,8 +123,8 @@ def generate(func_name_arg, output_dir, directives): # Note: the f90 bindings were not embiggened emitted_header = False for data in func_names_data: - binding = str(data.express.f90) - if binding and len(binding) > 0 and binding != 'None': + binding = common.f90_binding(data) + if binding is not None: have_binding = True if not emitted_header: out.append('Fortran Syntax') @@ -152,8 +144,8 @@ def generate(func_name_arg, output_dir, directives): # F08 bindings emitted_header = False for data in func_names_data: - binding = str(data.express.f08) - if binding and len(binding) > 0 and binding != 'None': + binding = common.f08_binding(data) + if binding is not None: have_binding = True if not emitted_header: out.append('Fortran 2008 Syntax') @@ -169,9 +161,9 @@ def generate(func_name_arg, output_dir, directives): out.append(f' {line}') out.append(blank) - if data.has_embiggenment(): - binding = str(data.express.embiggen.f08) - lines = binding.split('\n') + large = common.f08_binding_large(data) + if large is not None: + lines = large.split('\n') for line in lines: out.append(f' {line}') out.append(blank) @@ -199,44 +191,6 @@ def generate(func_name_arg, output_dir, directives): #---------------- -# Some existing .3.rst man pages actually contain the docs for -# multiple MPI_* API functions. Read the .3.rst files and look for -# directives that mean "this file contains documentation for all these -# MPI API functions". -def read_rst_man_pages(src_dir): - directives = {} - prog = re.compile(r'^MPI_.*\.3\.rst$') - - man3_dir = Path(os.path.join(src_dir, 'man-openmpi', 'man3')).resolve() - for file in os.listdir(man3_dir): - # Only want MPI man pages - if not prog.match(file): - continue - - with open(os.path.join(man3_dir, file)) as fp: - lines = fp.readlines() - - file_api_name = file.replace('.3.rst', '').lower() - - # Make an initial/empty list for every MPI API man page that - # we find - directives[file_api_name] = list() - - prefix = '.. mpi-bindings:' - for line in lines: - line = line.strip() - if not line.startswith(prefix): - continue - - bindings = line[len(prefix):].split(',') - for binding in bindings: - binding = binding.strip() - directives[file_api_name].append(binding.lower()) - - return directives - -#---------------- - def main(): args = setup_cli() @@ -245,26 +199,12 @@ def main(): # Read existing srcdir/man-openmpi/man3/MPI_*.3.rst files and look # for directives to guide this generation process. - directives = read_rst_man_pages(src_dir) - - # A bit of a hack to load the pympistandard module, which is in - # the Open MPI '3rd-party" tree in the source dir. - pympistandard_dir = Path(os.path.join(src_dir, '..', '3rd-party', - 'pympistandard', 'src')).resolve() + directives = common.read_rst_man_pages(src_dir) - sys.path.insert(0, str(pympistandard_dir)) + # Load the embedded pympistandard library plus the MPI Standard API + # JSON (docs/mpi-standard-apis.json). global std - import pympistandard as std - - # This is the JSON file with all the MPI standard APIs. This is - # not currently officially distributed by the MPI Forum, so it was - # obtained by checking out the relevant branch from - # https://github.com/mpi-forum/mpi-standard/ and doing a build. - # This will create a file named apis.json. Copy that here to this - # tree. - mpi_standard_json = os.path.abspath(os.path.join(src_dir, - 'mpi-standard-apis.json')) - std.use_api_version(1, given_path=mpi_standard_json) + std = common.load_pympistandard(src_dir) # We need to write all of these into the build tree. See # docs/Makefile.am for a fuller explaination: all RST files are diff --git a/docs/getting-help.rst b/docs/getting-help.rst index ca9823f8a57..04596c6826a 100644 --- a/docs/getting-help.rst +++ b/docs/getting-help.rst @@ -249,8 +249,6 @@ of the following additional information: #. If you are experiencing networking problems, include detailed information about your network. - .. error:: TODO Update link to IB FAQ entry. - #. For RoCE- or InfiniBand-based networks, include the information :ref:`in this FAQ entry `. diff --git a/docs/index.rst b/docs/index.rst index 50bd1c412e4..e8526858bea 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -26,15 +26,19 @@ Documentation for Open MPI can be found in the following locations: - Documentation location * - v5.0.0 and later - - Web: https://docs.open-mpi.org/ + - * Web: https://docs.open-mpi.org/ - Included in tarball: ``docs/html/index.html`` + * Included in tarball: ``docs/html/index.html`` - Built in source tree (if Sphinx available): ``docs/_build/html/index.html`` + * Built in source tree (if Sphinx available): ``docs/_build/html/index.html`` - Installed: ``$docdir/html/index.html`` + * Installed: ``$docdir/html/index.html`` + (which defaults to: ``$prefix/share/doc/openmpi/html/index.html``) - (which defaults to: ``$prefix/share/doc/openmpi/html/index.html``) + * Machine-readable, LLM-friendly artifacts for the MPI APIs (a + Markdown API corpus, a JSONL API catalog, curated examples, and an + interface guide) for this documentation version: + `llms.txt `_ * - v4.1.x and earlier - See the `legacy Open MPI FAQ `_ @@ -42,7 +46,8 @@ Documentation for Open MPI can be found in the following locations: For example: - * `v4.1.x README file `_ + * `v4.1.x README file `_, + `v4.1.x FAQ `_ * `v4.0.x README file `_ Release announcements @@ -78,6 +83,7 @@ Table of contents app-debug/index developers/index contributing + code-of-conduct license/index history man-openmpi/index diff --git a/docs/installing-open-mpi/configure-cli-options/index.rst b/docs/installing-open-mpi/configure-cli-options/index.rst index e80812e4799..90c974f2909 100644 --- a/docs/installing-open-mpi/configure-cli-options/index.rst +++ b/docs/installing-open-mpi/configure-cli-options/index.rst @@ -11,6 +11,7 @@ below. .. toctree:: :maxdepth: 1 + what-to-install conventions installation networking diff --git a/docs/installing-open-mpi/configure-cli-options/installation.rst b/docs/installing-open-mpi/configure-cli-options/installation.rst index c23877adb6b..69bd05186d4 100644 --- a/docs/installing-open-mpi/configure-cli-options/installation.rst +++ b/docs/installing-open-mpi/configure-cli-options/installation.rst @@ -147,7 +147,7 @@ be used with ``configure``: ` to build Open MPI/OpenSHMEM applications. -.. _building-ompi-cli-options-diable-dlopen-label: +.. _building-ompi-cli-options-disable-dlopen-label: * ``--enable-dlopen``: Enable Open MPI to load components as standalone Dynamic Shared Objects (DSOs) at run-time. This option diff --git a/docs/installing-open-mpi/configure-cli-options/mpi.rst b/docs/installing-open-mpi/configure-cli-options/mpi.rst index dcc35670e72..8db0baf593d 100644 --- a/docs/installing-open-mpi/configure-cli-options/mpi.rst +++ b/docs/installing-open-mpi/configure-cli-options/mpi.rst @@ -29,19 +29,6 @@ MPI API behaviors that can be used with ``configure``: :ref:`See the ULFM section ` for more information. -* ``--enable-mpi-java``: - Enable building of an **EXPERIMENTAL** Java MPI interface (disabled - by default). You may also need to specify ``--with-jdk-dir``, - ``--with-jdk-bindir``, and/or ``--with-jdk-headers``. - - .. warning:: Note that this Java interface is **INCOMPLETE** - (meaning: it does not support all MPI functionality) and **LIKELY - TO CHANGE**. The Open MPI developers would very much like to - hear your feedback about this interface. - - :ref:`See the Java section ` for many more - details. - * ``--enable-mpi-fortran[=VALUE]``: By default, Open MPI will attempt to build all 3 Fortran bindings: ``mpif.h``, the ``mpi`` module, and the ``mpi_f08`` module. The following @@ -67,7 +54,7 @@ MPI API behaviors that can be used with ``configure``: * ``--enable-mpi-ext[=LIST]``: Enable Open MPI's non-portable API extensions. ``LIST`` is a - comma-delmited list of extensions. If no ``LIST`` is specified, all + comma-delimited list of extensions. If no ``LIST`` is specified, all of the extensions are enabled. See the "Open MPI API Extensions" section for more details. diff --git a/docs/installing-open-mpi/configure-cli-options/networking.rst b/docs/installing-open-mpi/configure-cli-options/networking.rst index d5ff8108929..7cc5842a4f9 100644 --- a/docs/installing-open-mpi/configure-cli-options/networking.rst +++ b/docs/installing-open-mpi/configure-cli-options/networking.rst @@ -32,7 +32,7 @@ can be used with ``configure``: compiler/linker search paths. Libfabric is the support library for OpenFabrics Interfaces-based - network adapters, such as Cisco usNIC, Intel True Scale PSM, etc. + network adapters, such as Cisco usNIC, etc. * ``--with-libfabric-libdir=DIR``: Look in directory for the libfabric libraries. By default, Open MPI @@ -58,27 +58,13 @@ can be used with ``configure``: ``--with-portals4-max-va-size=SIZE``: Set configuration values for Portals 4 -* ``--with-psm=``: - Specify the directory where the QLogic InfiniPath / Intel True Scale - PSM library and header files are located. This option is generally - only necessary if the PSM headers and libraries are not in default - compiler/linker search paths. - - PSM is the support library for QLogic InfiniPath and Intel True Scale - network adapters. - -* ``--with-psm-libdir=DIR``: - Look in directory for the PSM libraries. By default, Open MPI will - look in ``DIR/lib`` and ``DIR/lib64``, which covers most cases. This - option is only needed for special configurations. - * ``--with-psm2=DIR``: - Specify the directory where the Intel Omni-Path PSM2 library and + Specify the directory where the Cornelis Networks Omni-Path PSM2 library and header files are located. This option is generally only necessary if the PSM2 headers and libraries are not in default compiler/linker search paths. - PSM is the support library for Intel Omni-Path network adapters. + PSM2 is the support library for Cornelis Networks Omni-Path network adapters. * ``--with-psm2-libdir=DIR``: Look in directory for the PSM2 libraries. By default, Open MPI will diff --git a/docs/installing-open-mpi/configure-cli-options/what-to-install.rst b/docs/installing-open-mpi/configure-cli-options/what-to-install.rst new file mode 100644 index 00000000000..2e6f3563094 --- /dev/null +++ b/docs/installing-open-mpi/configure-cli-options/what-to-install.rst @@ -0,0 +1,68 @@ +Deciding what to install +======================== + +Open MPI's ``configure`` script will, by default, search for support +and build every component that it can. This is convenient, and +helpful for a good out-of-the-box experience for many HPC +environments. + +However, HPC clusters rarely change from day-to-day, and large +clusters rarely change at all. If you know your cluster's +configuration, there are several steps you can take to reduce the +Open MPI installation footprint and component selection overhead. +These steps use a combination of build-time configuration options to +eliminate components |mdash| thus eliminating their libraries and +avoiding unnecessary component open/close operations |mdash| as well +as run-time MCA parameters to specify what modules to use by default +for most users. + +.. caution:: This is somewhat advanced functionality, and is only + recommended for users who are deeply familiar with what + components are actually used by Open MPI in their + environments. + + Most users should just allow building whatever components + Open MPI's ``configure`` script finds. + +Build/install-time choices +-------------------------- + +One way to save memory is to avoid building components that will +actually never be selected by the system. Unless MCA parameters +specify which components to open, installed components are *always* +opened and tested as to whether or not they should be selected for +use. If you know that a component can build on your system, but due to +your cluster's configuration will never actually be selected, then it +is best to simply configure Open MPI to not build that component by +using the ``--enable-mca-no-build`` CLI option to ``configure``. + +For example, if you know that your system will only utilize the +``ob1`` component of the PML framework, then you can "no build" all +the others: + +.. code:: sh + + # See what directories (i.e., components) exist in the PML + # framework + shell$ ls -1 ompi/mca/pml + + # Do not list "base", but list all other undesired components + # (i.e., directories). For example, in Open MPI v6.0.0, to build + # *only* the OB1 PML: + shell$ ./configure --enable-mca-no-build=pml-cm,pml-monitoring,pml-ubcl,pml-ucx,pml-v + + +This not only reduces the size of the Open MPI libraries, but can also +avoid unnecessary component open/selection work at run time. + +Run-time choices +---------------- + +The ``$sysconfdir/openmpi-mca-params.conf`` file in the installation +tree (which defaults to ``$prefix/etc/openmpi-mca-params.conf``) is +where a system administrator can set system-wide defaults for Open MPI +:ref:`run-time MCA parameters `. + +These values can still be overridden by end users, but the values in +this file allow the hiding of any system-specific defaults that an +administrator may want the majority of users to utilize. diff --git a/docs/installing-open-mpi/installation-location.rst b/docs/installing-open-mpi/installation-location.rst index ee90b5bb432..528a1fa713b 100644 --- a/docs/installing-open-mpi/installation-location.rst +++ b/docs/installing-open-mpi/installation-location.rst @@ -322,14 +322,14 @@ the end): #. The C constants ``MPI_F_STATUS_IGNORE`` and ``MPI_F_STATUSES_IGNORE`` will only compare properly to Fortran applications that were - created with Fortran compilers that that use the same + created with Fortran compilers that use the same name-mangling scheme as the Fortran compiler with which Open MPI was configured. #. Fortran compilers may have different values for the logical ``.TRUE.`` constant. As such, any MPI function that uses the Fortran ``LOGICAL`` type may only get ``.TRUE.`` values back that - correspond to the the ``.TRUE.`` value of the Fortran compiler with which + correspond to the ``.TRUE.`` value of the Fortran compiler with which Open MPI was configured. #. Similar to C++, linking object files that Fortran language features such diff --git a/docs/installing-open-mpi/packagers.rst b/docs/installing-open-mpi/packagers.rst index c0fb13ea23a..d0ad8e9defe 100644 --- a/docs/installing-open-mpi/packagers.rst +++ b/docs/installing-open-mpi/packagers.rst @@ -25,7 +25,7 @@ the following: # Install Sphinx so that Open MPI can re-build its docs with the # installed PRRTE's docs - virtualalenv venv + virtualenv venv . ./venv/bin/activate pip install docs/requirements.txt @@ -145,7 +145,7 @@ performance savings. .. note:: If not using a networked filesystem, or if not launching at scale, loading a large number of DSO files may not consume a noticeable amount of time during MPI process launch. Put - simply: loading DSOs as indvidual files generally only + simply: loading DSOs as individual files generally only matters when using a networked filesystem while launching at scale. diff --git a/docs/installing-open-mpi/quickstart.rst b/docs/installing-open-mpi/quickstart.rst index 3ecc172e826..412f78593e5 100644 --- a/docs/installing-open-mpi/quickstart.rst +++ b/docs/installing-open-mpi/quickstart.rst @@ -37,9 +37,11 @@ packages: .. code-block:: sh # For Homebrew - shell$ brew install openmpi + # https://formulae.brew.sh/formula/open-mpi + shell$ brew install open-mpi # For MacPorts + # https://ports.macports.org/search/?q=openmpi shell$ port install openmpi .. important:: Binary packages may or may not include support for diff --git a/docs/installing-open-mpi/required-support-libraries.rst b/docs/installing-open-mpi/required-support-libraries.rst index 983608a3fb9..2e8944bc51f 100644 --- a/docs/installing-open-mpi/required-support-libraries.rst +++ b/docs/installing-open-mpi/required-support-libraries.rst @@ -29,7 +29,7 @@ system. compared to later versions. Other than the Hwloc restriction about v3.0.0 and beyond - (see below), the Open MPI community generally recomends + (see below), the Open MPI community generally recommends using the latest available version of Hwloc unless there is a specific reason not to. @@ -88,7 +88,7 @@ system. may still have bugs and/or have less functionality as compared to later versions. - The Open MPI community generally recomends using the + The Open MPI community generally recommends using the latest available version of OpenPMIx unless there is a specific reason not to. @@ -114,7 +114,7 @@ system. and run with |prte_min_version|, you will not get a fully-populated ``mpirun(1)`` man page. - The Open MPI community generally recomends using the + The Open MPI community generally recommends using the latest available version of PRRTE unless there is a specific reason not to. @@ -277,7 +277,7 @@ Build example 1 * If ``configure`` is unable to find header files and libraries for PMIx, Hwloc, and Libevent elsewhere on the build machine (i.e., - assumedly the same PMIx, Hwloc, and Libevent than the PRRTE in + presumably the same PMIx, Hwloc, and Libevent as the PRRTE in ``/usr/local`` is using), this is an error: ``configure`` will abort, and therefore refuse to build Open MPI. @@ -298,7 +298,7 @@ will cause the following to occur: * If ``configure`` is unable to find header files and libraries for Hwloc and Libevent elsewhere on the build machine (i.e., - assumedly the same Hwloc and Libevent than the PMIx in + presumably the same Hwloc and Libevent as the PMIx in ``/opt/local`` is using), this is an error: ``configure`` will abort, and therefore refuse to build Open MPI. diff --git a/docs/installing-open-mpi/supported-systems.rst b/docs/installing-open-mpi/supported-systems.rst index 45b4ecd821b..76b6ef39930 100644 --- a/docs/installing-open-mpi/supported-systems.rst +++ b/docs/installing-open-mpi/supported-systems.rst @@ -14,7 +14,7 @@ operating systems supported has changed over time (e.g., native Microsoft Windows support was added in v1.3.3, and although it was removed prior to v1.8, is still supported through Cygwin). :ref:`See the Platform Notes section ` for a -listing of the OSes that that version supports. +listing of the OSes that version supports. Open MPI is fairly POSIX-neutral, so it will run without *too* many modifications on most POSIX-like systems. Hence, if we haven't listed diff --git a/docs/launching-apps/gridengine.rst b/docs/launching-apps/gridengine.rst index 0fed2ea191b..cd684aa6f90 100644 --- a/docs/launching-apps/gridengine.rst +++ b/docs/launching-apps/gridengine.rst @@ -1,13 +1,13 @@ Launching with Grid Engine ========================== -Open MPI supports the family of run-time schedulers including the Sun -Grid Engine (SGE), Oracle Grid Engine (OGE), Grid Engine (GE), Son of -Grid Engine, and others. +Open MPI supports the Grid Engine family of run-time schedulers. This +family traces back to Sun Grid Engine (SGE) and includes its many +descendants |mdash| among them Oracle Grid Engine, Son of Grid Engine, +Univa Grid Engine (now Altair Grid Engine), and Open Cluster Scheduler. -This documentation will collectively refer to all of them as "Grid -Engine", unless a referring to a specific flavor of the Grid Engine -family. +This documentation collectively refers to all of them as "Grid Engine", +unless referring to a specific member of the family. Verify Grid Engine support -------------------------- @@ -52,11 +52,11 @@ that were allocated by Grid Engine: # Get the environment variables for Grid Engine - # (Assuming Grid Engine is installed at /opt/sge and $Grid - # Engine_CELL is 'default' in your environment) + # (Assuming Grid Engine is installed at /opt/sge and $SGE_CELL + # is 'default' in your environment) shell$ . /opt/sge/default/common/settings.sh - # Allocate an Grid Engine interactive job with 4 slots from a + # Allocate a Grid Engine interactive job with 4 slots from a # parallel environment (PE) named 'ompi' and run a 4-process Open # MPI job shell$ qrsh -pe ompi 4 -b y mpirun -n 4 mpi-hello-world @@ -130,18 +130,11 @@ that is used to send parallel tasks to the remote Grid Engine execution hosts. It will show whether the connections to the remote hosts are established successfully or not. -.. error:: TODO is this site still live? Doesn't look like it.. Jeff - emailed Dave Love on 31 Dec 2021 to ask if this is still the - correct URL. - - Update March 2022: it doesn't look like this web site is good any - more. Perhaps use https://github.com/grisu48/gridengine instead...? - -Various Grid Engine documentation with pointers to more is available -at `the Son of GridEngine site `_, and -configuration instructions can be found at `the Son of GridEngine -configuration how-to site -`_. +For more information about Grid Engine, see the actively-maintained +open-source `Open Cluster Scheduler +`_ project |mdash| +the current successor to Sun Grid Engine, Univa Grid Engine, and Son of +Grid Engine |mdash| and its documentation. Grid Engine tight integration support of the ``qsub -notify`` flag ------------------------------------------------------------------ @@ -177,9 +170,7 @@ like this batch script can be used: #$ -pe ompi 16 #$ -j y #$ -l h_rt=00:20:00 - mpirun -n 16 -mca orte_forward_job_control 1 mpi-hello-world - -.. error:: Ralph: Does ``orte_forward_job_control`` still exist? + mpirun -n 16 mpi-hello-world However, one has to make one of two changes to this script for things to work properly. By default, a SIGUSR1 signal will kill a shell @@ -196,7 +187,7 @@ to handle it: #$ -pe ompi 16 #$ -j y #$ -l h_rt=00:20:00 - exec mpirun -n 16 -mca orte_forward_job_control 1 mpi-hello-world + exec mpirun -n 16 mpi-hello-world Alternatively, one can catch the signals in the script instead of doing an exec on the mpirun: @@ -225,7 +216,7 @@ an exec on the mpirun: trap sigusr1handler SIGUSR1 trap sigusr2handler SIGUSR2 - mpirun -n 16 -mca orte_forward_job_control 1 mpi-hello-world + mpirun -n 16 mpi-hello-world Grid Engine job suspend / resume support ---------------------------------------- @@ -236,16 +227,14 @@ To suspend the job, you send a SIGTSTP (not SIGSTOP) signal to a SIGCONT signal to :ref:`mpirun(1) ` which will be caught and forwarded to the ``mpi-hello-world``. -By default, this feature is not enabled. This means that both the -SIGTSTP and SIGCONT signals will simply be consumed by the :ref:`mpirun(1) ` -process. To have them forwarded, you have to run the job with ``--mca -orte_forward_job_control 1``. Here is an example on Solaris: - -.. error:: TODO Ralph: does ``orte_forward_job_control`` still exist? +By default, ``mpirun`` forwards the SIGTSTP and SIGCONT signals to the +application processes (delivering SIGTSTP as SIGSTOP so the processes +suspend), so no special option is required to enable this behavior. +Here is an example on Solaris: .. code-block:: sh - shell$ mpirun -mca orte_forward_job_control 1 -n 2 mpi-hello-world + shell$ mpirun -n 2 mpi-hello-world In another window, we suspend and continue the job: diff --git a/docs/launching-apps/lsf.rst b/docs/launching-apps/lsf.rst index 159a85a84a4..988a1a02582 100644 --- a/docs/launching-apps/lsf.rst +++ b/docs/launching-apps/lsf.rst @@ -1,7 +1,12 @@ Launching with LSF ================== -Open MPI supports the LSF resource manager. +Open MPI supports some versions of the LSF resource manager. + +Problems have been reported with using the most recent releases of LSF, in particular +the version supplied with IBM Spectrum LSF Version 10.1 Fix Pack 15. +The suggested workaround is to use an older release of the LSF 10.1 package or to +configure Open MPI without LSF support. Verify LSF support ------------------ diff --git a/docs/launching-apps/pmix-and-prrte.rst b/docs/launching-apps/pmix-and-prrte.rst index 11183f57925..61dcb96766b 100644 --- a/docs/launching-apps/pmix-and-prrte.rst +++ b/docs/launching-apps/pmix-and-prrte.rst @@ -28,7 +28,7 @@ abstractions and configuration options belong to Open MPI vs. PMIx vs. PRRTE. Advanced users can peek into the PMIx and PRRTE internals and tweak -additional configuration settings if necessary, but we hope that that +additional configuration settings if necessary, but we hope that will rarely be necessary. PMIx @@ -48,7 +48,7 @@ PMIx presents a unified API that hides many of the complexities of communication with these back-end run-time environments. Open MPI uses the PMIx API to discover, communicate, and coordinate with any supported back-end run-time system without needing to know the -intimiate details of that system. +intimate details of that system. PRRTE ----- diff --git a/docs/launching-apps/prerequisites.rst b/docs/launching-apps/prerequisites.rst index 036ba915a64..3ad824cbbff 100644 --- a/docs/launching-apps/prerequisites.rst +++ b/docs/launching-apps/prerequisites.rst @@ -66,7 +66,7 @@ For example: Additionally, Open MPI requires that jobs can be started on remote nodes without any input from the keyboard. For example, if using -``ssh`` as the remote agent, you must have your environment setup to +``ssh`` as the remote agent, you must have your environment set up to allow execution on remote nodes without entering a password or passphrase. @@ -249,8 +249,3 @@ uses ``--prefix``: .. code-block:: shell$ /opt/openmpi-VERSION/bin/mpirun -n 4 a.out - -.. error:: TODO Josh H points out that we might also want to mention - ``OMPIHOME`` for PRRTE's ``.ini`` file here. Leaving this - as a future to-do item, since PRRTE's ``.ini`` file support - does not exist yet. diff --git a/docs/launching-apps/ssh.rst b/docs/launching-apps/ssh.rst index 39bd04a2732..3251bfd9f74 100644 --- a/docs/launching-apps/ssh.rst +++ b/docs/launching-apps/ssh.rst @@ -50,7 +50,7 @@ There are three mechanisms for specifying the hosts that an MPI job will run on: Non-interactive ``ssh`` logins ------------------------------ -SSH keys must be setup such that the following can be executed without +SSH keys must be set up such that the following can be executed without being prompted for password or passphrase: .. code-block:: sh @@ -60,7 +60,7 @@ being prompted for password or passphrase: shell$ Consult instructions and tutorials from around the internet to learn -how to setup SSH keys. Try Google search terms like "passwordless +how to set up SSH keys. Try Google search terms like "passwordless SSH" or "SSH key authentication". For simplicity, it may be desirable to configure your SSH keys @@ -75,12 +75,12 @@ comfortable with. or passphrase |mdash| *to any node* in the host list *from any node* in the host list. - It may *not* be sufficient to only setup an SSH key from the node + It may *not* be sufficient to only set up an SSH key from the node where you are invoking :ref:`mpirun(1) ` to all other nodes. If you have a shared ``$HOME`` filesystem between your nodes, you can -setup a single SSH key that is used to login to all nodes. +set up a single SSH key that is used to login to all nodes. Finding Open MPI executables and libraries ------------------------------------------ diff --git a/docs/launching-apps/troubleshooting.rst b/docs/launching-apps/troubleshooting.rst index e5ed1618a48..029bcef575b 100644 --- a/docs/launching-apps/troubleshooting.rst +++ b/docs/launching-apps/troubleshooting.rst @@ -61,9 +61,11 @@ why this can happen. linker is smart enough to not load ``libmpi`` twice |mdash| but it does keeps ``libmpi`` in a public scope. * Use the ``--disable-dlopen`` or ``--disable-mca-dso`` options to - Open MPI's ``configure`` script (see this TODO NONEXISTENT FAQ entry - for more details on these - options). These options slurp all of Open MPI's plugins up in to + Open MPI's ``configure`` script (see the + :ref:`--disable-dlopen ` + and :ref:`--enable-mca-dso / --enable-mca-static + ` option descriptions for + more details). These options slurp all of Open MPI's plugins up in to ``libmpi`` |mdash| meaning that the plugins physically reside in ``libmpi`` and will not be dynamically opened at run time. * Build Open MPI as a static library by configuring Open MPI with @@ -77,7 +79,7 @@ Errors about missing libraries When building Open MPI with the compilers that have libraries in non-default search path locations, you may see errors about those compiler's support libraries when trying to launch MPI applications if -their corresponding environments were not setup properly. +their corresponding environments were not set up properly. For example, you may see warnings similar to the following: @@ -107,13 +109,13 @@ Specifically, Open MPI first attempts to launch a "helper" daemon libraries shown above (``libimf.so``, ``libpgcc.so``, and ``libmv.so``) are specific to their compiler suites (Intel, PGI, and PathScale, respectively). As such, it is likely that the user did not -setup the compiler library in their environment properly on this node. +set up the compiler library in their environment properly on this node. -Double check that you have setup the appropriate compiler environment +Double check that you have set up the appropriate compiler environment on the target node, for both interactive and non-interactive logins. .. note:: It is a common error to ensure that the compiler environment - is setup properly for *interactive* logins, but not for + is set up properly for *interactive* logins, but not for *non-interactive* logins. Here's an example of a user-compiled MPI application working fine @@ -142,7 +144,7 @@ locally, but failing when invoked non-interactively on a remote node: mpi_hello: error while loading shared libraries: libimf.so: cannot open shared object file: No such file or directory In cases like this, check your shell script startup files and verify -that the appropriate compiler environment is setup properly for +that the appropriate compiler environment is set up properly for non-interactive logins. Problems when running across multiple hosts @@ -162,7 +164,7 @@ them across multiple hosts, try the following: remotehost If you are unable to launch across multiple hosts, check that your - SSH keys are setup properly. Or, if you are running in a managed + SSH keys are set up properly. Or, if you are running in a managed environment, such as in a Slurm, Torque, or other job launcher, check that you have reserved enough hosts, are running in an allocated job, etc. diff --git a/docs/llms-src/openmpi-docs-manifest.schema.json b/docs/llms-src/openmpi-docs-manifest.schema.json new file mode 100644 index 00000000000..366961be0c9 --- /dev/null +++ b/docs/llms-src/openmpi-docs-manifest.schema.json @@ -0,0 +1,100 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://docs.open-mpi.org/llms/openmpi-docs-manifest.schema.json", + "title": "Open MPI LLM-friendly documentation manifest", + "description": "Schema for openmpi-docs-manifest.json. The manifest is the single artifact that carries build identity, and it does not inventory itself. See specs/llms-friendly-docs/spec.md.", + "type": "object", + "additionalProperties": true, + "required": [ + "artifact_schema_version", + "project", + "ompi_version", + "generated_at", + "artifacts" + ], + "properties": { + "artifact_schema_version": { + "type": "integer", + "const": 1, + "description": "Version of the manifest schema." + }, + "project": { + "type": "string", + "const": "open-mpi" + }, + "ompi_version": { + "type": "string", + "description": "Open MPI version derived from the top-level VERSION file." + }, + "ompi_series": { + "type": ["string", "null"], + "description": "Open MPI documentation series, e.g. v6.1.x." + }, + "rtd_version_slug": { + "type": ["string", "null"], + "description": "Read the Docs version slug when available." + }, + "git_commit": { + "type": ["string", "null"], + "description": "Source commit used for the documentation build, when available." + }, + "git_describe": { + "type": ["string", "null"], + "description": "git describe --tags --always output, when available." + }, + "generated_at": { + "type": "string", + "description": "UTC generation timestamp (ISO 8601). Always present: derived from SOURCE_DATE_EPOCH when set, else the build commit date, else the tarball/build date." + }, + "artifacts": { + "type": "array", + "description": "One entry per generated and curated artifact, EXCEPT the manifest itself.", + "items": { + "type": "object", + "additionalProperties": true, + "required": ["path", "media_type", "sha256", "bytes"], + "properties": { + "path": { + "type": "string", + "description": "Path relative to the published version root, e.g. llms/openmpi-mpi-api.jsonl." + }, + "url": { + "type": ["string", "null"], + "description": "Absolute published URL, when the URL base is known." + }, + "media_type": { + "type": "string", + "description": "Media type, e.g. application/jsonl, text/markdown, application/json." + }, + "sha256": { + "type": "string", + "pattern": "^[0-9a-f]{64}$", + "description": "SHA-256 of the artifact bytes (artifacts are wall-clock-free, so this is stable at a commit)." + }, + "bytes": { + "type": "integer", + "minimum": 0 + }, + "tokens_estimate": { + "type": ["integer", "null"], + "minimum": 0, + "description": "Estimated token count for the artifact." + }, + "symbols": { + "type": ["array", "null"], + "items": { "type": "string" }, + "description": "Symbol names covered by the artifact, when applicable." + }, + "languages": { + "type": ["array", "null"], + "items": { + "type": "string", + "enum": ["c", "fortran_mpifh", "fortran_use_mpi", "fortran_mpi_f08"] + }, + "description": "Language/interface set covered by the artifact, when applicable." + } + } + } + } + } +} diff --git a/docs/llms-src/openmpi-mpi-api.schema.json b/docs/llms-src/openmpi-mpi-api.schema.json new file mode 100644 index 00000000000..05e6ef00c41 --- /dev/null +++ b/docs/llms-src/openmpi-mpi-api.schema.json @@ -0,0 +1,240 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://docs.open-mpi.org/llms/openmpi-mpi-api.schema.json", + "title": "Open MPI LLM-friendly API catalog record", + "description": "Schema for one JSON object (one line) in openmpi-mpi-api.jsonl. Schema version 1. See specs/llms-friendly-docs/spec.md.", + "type": "object", + "additionalProperties": true, + "required": [ + "schema_version", + "project", + "docset", + "name", + "kind", + "standard", + "category", + "languages", + "bindings", + "parameters", + "semantics", + "errors", + "seealso", + "urls", + "sources" + ], + "properties": { + "schema_version": { + "type": "integer", + "const": 1, + "description": "Version of this JSONL record schema, independent of the Open MPI release." + }, + "project": { + "type": "string", + "const": "open-mpi" + }, + "docset": { + "type": "object", + "additionalProperties": true, + "description": "Semantic-only build descriptor. Build identity (git/timestamp) lives only in the manifest.", + "required": ["ompi_version", "schema_version"], + "properties": { + "ompi_version": { + "type": "string", + "description": "Open MPI version derived from the top-level VERSION file, e.g. v6.1.0a1." + }, + "ompi_series": { + "type": "string", + "description": "Open MPI documentation series, e.g. v6.1.x." + }, + "schema_version": { + "type": "integer", + "const": 1 + } + } + }, + "name": { + "type": "string", + "description": "Primary API name, e.g. MPI_Send.", + "minLength": 1 + }, + "kind": { + "type": "string", + "enum": ["standard", "extension", "deprecated", "removed"] + }, + "standard": { + "type": ["object", "null"], + "description": "MPI Standard version information when known.", + "additionalProperties": true, + "properties": { + "version": { + "type": ["string", "null"], + "description": "MPI Standard version string, e.g. 4.1, when known." + } + } + }, + "category": { + "type": ["string", "null"], + "enum": [ + "point-to-point", + "collective", + "communicator", + "group", + "datatype", + "rma", + "io", + "topology", + "sessions", + "info", + "process", + "error-handling", + "tools", + "extension", + "other", + null + ] + }, + "languages": { + "type": "array", + "description": "The set of language interfaces for which this record provides bindings; should match the distinct `language` values across `bindings`. JSON Schema validates only enum membership and uniqueness here; the cross-field equality with `bindings[].language` is enforced separately by the CI validator.", + "items": { + "type": "string", + "enum": ["c", "fortran_mpifh", "fortran_use_mpi", "fortran_mpi_f08"] + }, + "uniqueItems": true + }, + "bindings": { + "type": "array", + "description": "One entry per binding variant. May be empty for an extension with no recoverable signature.", + "items": { + "type": "object", + "additionalProperties": true, + "required": ["language", "procedure", "signature", "large_count", "return_convention", "parameters"], + "properties": { + "language": { + "type": "string", + "enum": ["c", "fortran_mpifh", "fortran_use_mpi", "fortran_mpi_f08"] + }, + "procedure": { + "type": "string", + "description": "Procedure name for this binding, e.g. MPI_Send." + }, + "signature": { + "type": "string", + "description": "Rendered signature text." + }, + "large_count": { + "type": "boolean", + "description": "True if this is the large-count ('embiggened', _c) variant." + }, + "return_convention": { + "type": ["string", "null"], + "description": "How the binding reports errors/results, e.g. 'int return code' (C) or 'ierror argument' (Fortran)." + }, + "parameters": { + "type": "array", + "items": { "$ref": "#/$defs/parameter" } + } + } + } + }, + "parameters": { + "type": "array", + "description": "Normalized parameter metadata. For extension records, fields may be 'unknown' or omitted.", + "items": { "$ref": "#/$defs/parameter" } + }, + "semantics": { + "type": ["object", "null"], + "additionalProperties": true, + "properties": { + "blocking": { "type": ["boolean", "string", "null"] }, + "collective": { "type": ["boolean", "string", "null"] }, + "local": { "type": ["boolean", "string", "null"] }, + "initialization_required": { "type": ["boolean", "string", "null"] }, + "threading_notes": { "type": ["string", "null"] } + } + }, + "errors": { + "type": ["object", "null"], + "additionalProperties": true, + "properties": { + "convention": { + "type": ["string", "null"], + "description": "Error-return convention summary." + }, + "url": { + "type": ["string", "null"], + "description": "Link to the human error-handling documentation." + } + } + }, + "seealso": { + "type": "array", + "items": { "type": "string" }, + "description": "Related API names." + }, + "documented_with": { + "type": "array", + "items": { "type": "string" }, + "description": "Co-documented procedure names that share this API's man page." + }, + "urls": { + "type": "object", + "additionalProperties": true, + "required": ["html", "markdown"], + "properties": { + "html": { + "type": "string", + "description": "Canonical human HTML page URL for this procedure." + }, + "markdown": { + "type": "string", + "description": "Per-symbol Markdown page URL for this procedure (every documented procedure has one)." + } + } + }, + "sources": { + "type": "object", + "additionalProperties": true, + "required": ["rst"], + "properties": { + "rst": { + "type": "string", + "description": "Source RST path (relative to the docs source tree) for this procedure's man page." + }, + "binding_metadata": { + "type": ["string", "null"], + "description": "Origin of the binding metadata, e.g. 'pympistandard' or 'rst' for extensions." + } + } + } + }, + "$defs": { + "parameter": { + "type": "object", + "additionalProperties": true, + "required": ["name"], + "properties": { + "name": { "type": "string" }, + "direction": { + "type": ["string", "null"], + "enum": ["in", "out", "inout", "unknown", null] + }, + "type": { + "type": ["string", "null"], + "description": "Datatype/kind of the parameter, when known." + }, + "intent": { + "type": ["string", "null"], + "description": "Fortran intent or C const-ness, when known." + }, + "optional": { + "enum": [true, false, "unknown", null], + "description": "Whether the parameter is optional; the only permitted string is 'unknown' (when not reliably extractable)." + }, + "description": { + "type": ["string", "null"] + } + } + } + } +} diff --git a/docs/llms-src/openmpi-mpi-examples.md b/docs/llms-src/openmpi-mpi-examples.md new file mode 100644 index 00000000000..665a632afb8 --- /dev/null +++ b/docs/llms-src/openmpi-mpi-examples.md @@ -0,0 +1,323 @@ +# Open MPI examples + +A small, representative set of MPI examples for Open MPI. The goal is coverage +of common patterns, not exhaustive API coverage. The MPI Standard +(https://www.mpi-forum.org/docs/) is authoritative for portable semantics. + +Compile and run with Open MPI's wrapper compilers: + +```sh +mpicc -o example example.c # C +mpifort -o example example.f90 # Fortran +mpirun -n 4 ./example +``` + +The "hello world" and "ring" programs below are the canonical Open MPI example +programs that ship in the source tree under `examples/` (and are compiled in +CI). The remaining C examples illustrate additional common patterns. + +## C: initialize and finalize (`examples/hello_c.c`) + +```c +#include "mpi.h" +#include + +int main(int argc, char *argv[]) +{ + int rank, size, len; + char version[MPI_MAX_LIBRARY_VERSION_STRING]; + + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + MPI_Get_library_version(version, &len); + printf("Hello, world, I am %d of %d, (%s, %d)\n", rank, size, version, len); + MPI_Finalize(); + + return 0; +} +``` + +## C: point-to-point send/receive (`examples/ring_c.c`) + +```c +#include "mpi.h" +#include + +int main(int argc, char *argv[]) +{ + int rank, size, next, prev, message, tag = 201; + + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + next = (rank + 1) % size; + prev = (rank + size - 1) % size; + + if (0 == rank) { + message = 10; + MPI_Send(&message, 1, MPI_INT, next, tag, MPI_COMM_WORLD); + } + + while (1) { + MPI_Recv(&message, 1, MPI_INT, prev, tag, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); + if (0 == rank) { + --message; + } + MPI_Send(&message, 1, MPI_INT, next, tag, MPI_COMM_WORLD); + if (0 == message) { + break; + } + } + + if (0 == rank) { + MPI_Recv(&message, 1, MPI_INT, prev, tag, MPI_COMM_WORLD, + MPI_STATUS_IGNORE); + } + + MPI_Finalize(); + return 0; +} +``` + +## C: collective communication (allreduce) + +```c +#include "mpi.h" +#include + +int main(int argc, char *argv[]) +{ + int rank, size, local, total; + + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + local = rank + 1; + MPI_Allreduce(&local, &total, 1, MPI_INT, MPI_SUM, MPI_COMM_WORLD); + if (0 == rank) { + printf("Sum of ranks+1 across %d processes = %d\n", size, total); + } + + MPI_Finalize(); + return 0; +} +``` + +## C: nonblocking communication + +```c +#include "mpi.h" +#include + +int main(int argc, char *argv[]) +{ + int rank, size, send_val, recv_val, next, prev, tag = 0; + MPI_Request reqs[2]; + + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + next = (rank + 1) % size; + prev = (rank + size - 1) % size; + send_val = rank; + + MPI_Irecv(&recv_val, 1, MPI_INT, prev, tag, MPI_COMM_WORLD, &reqs[0]); + MPI_Isend(&send_val, 1, MPI_INT, next, tag, MPI_COMM_WORLD, &reqs[1]); + MPI_Waitall(2, reqs, MPI_STATUSES_IGNORE); + + printf("Rank %d received %d from rank %d\n", rank, recv_val, prev); + + MPI_Finalize(); + return 0; +} +``` + +## C: derived datatype (contiguous) + +```c +#include "mpi.h" +#include + +int main(int argc, char *argv[]) +{ + int rank, tag = 0; + double buf[4] = {0, 0, 0, 0}; + MPI_Datatype vec4; + + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + MPI_Type_contiguous(4, MPI_DOUBLE, &vec4); + MPI_Type_commit(&vec4); + + if (0 == rank) { + for (int i = 0; i < 4; i++) buf[i] = i * 1.5; + MPI_Send(buf, 1, vec4, 1, tag, MPI_COMM_WORLD); + } else if (1 == rank) { + MPI_Recv(buf, 1, vec4, 0, tag, MPI_COMM_WORLD, MPI_STATUS_IGNORE); + printf("Rank 1 received: %g %g %g %g\n", buf[0], buf[1], buf[2], buf[3]); + } + + MPI_Type_free(&vec4); + MPI_Finalize(); + return 0; +} +``` + +## C: communicators (split) + +```c +#include "mpi.h" +#include + +int main(int argc, char *argv[]) +{ + int rank, color, subrank; + MPI_Comm subcomm; + + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + color = rank % 2; /* split into even/odd groups */ + MPI_Comm_split(MPI_COMM_WORLD, color, rank, &subcomm); + MPI_Comm_rank(subcomm, &subrank); + + printf("World rank %d -> color %d, subcomm rank %d\n", rank, color, subrank); + + MPI_Comm_free(&subcomm); + MPI_Finalize(); + return 0; +} +``` + +## C: MPI-IO (collective write) + +```c +#include "mpi.h" +#include + +int main(int argc, char *argv[]) +{ + int rank; + MPI_File fh; + + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + + MPI_File_open(MPI_COMM_WORLD, "out.dat", + MPI_MODE_CREATE | MPI_MODE_WRONLY, MPI_INFO_NULL, &fh); + MPI_File_set_view(fh, rank * (MPI_Offset) sizeof(int), + MPI_INT, MPI_INT, "native", MPI_INFO_NULL); + MPI_File_write_all(fh, &rank, 1, MPI_INT, MPI_STATUS_IGNORE); + MPI_File_close(&fh); + + MPI_Finalize(); + return 0; +} +``` + +## C: one-sided / RMA (window + put) + +```c +#include "mpi.h" +#include + +int main(int argc, char *argv[]) +{ + int rank, size, value, *winbuf; + MPI_Win win; + + MPI_Init(&argc, &argv); + MPI_Comm_rank(MPI_COMM_WORLD, &rank); + MPI_Comm_size(MPI_COMM_WORLD, &size); + + MPI_Alloc_mem(sizeof(int), MPI_INFO_NULL, &winbuf); + *winbuf = 0; + MPI_Win_create(winbuf, sizeof(int), sizeof(int), MPI_INFO_NULL, + MPI_COMM_WORLD, &win); + + value = rank; + MPI_Win_fence(0, win); + /* Each rank writes its value into rank 0's window. */ + MPI_Put(&value, 1, MPI_INT, 0, 0, 1, MPI_INT, win); + MPI_Win_fence(0, win); + + if (0 == rank) { + printf("Rank 0 window holds %d after RMA\n", *winbuf); + } + + MPI_Win_free(&win); + MPI_Free_mem(winbuf); + MPI_Finalize(); + return 0; +} +``` + +## Fortran `use mpi_f08`: initialize and finalize (`examples/hello_usempif08.f90`) + +```fortran +program main + use mpi_f08 + implicit none + integer :: rank, size, len + character(len=MPI_MAX_LIBRARY_VERSION_STRING) :: version + + call MPI_INIT() + call MPI_COMM_RANK(MPI_COMM_WORLD, rank) + call MPI_COMM_SIZE(MPI_COMM_WORLD, size) + call MPI_GET_LIBRARY_VERSION(version, len) + write(*, '("Hello, world, I am ", i2, " of ", i2, ": ", a)') & + rank, size, version + call MPI_FINALIZE() +end +``` + +## Fortran `use mpi`: initialize and finalize (`examples/hello_usempi.f90`) + +```fortran +program main + use mpi + implicit none + integer :: rank, size, ierror + + call MPI_INIT(ierror) + call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierror) + call MPI_COMM_SIZE(MPI_COMM_WORLD, size, ierror) + write(*, '("Hello, world, I am ", i2, " of ", i2)') rank, size + call MPI_FINALIZE(ierror) +end +``` + +## Fortran `mpif.h`: initialize and finalize (`examples/hello_mpifh.f`) + +```fortran + program main + implicit none + include 'mpif.h' + integer rank, size, ierror + + call MPI_INIT(ierror) + call MPI_COMM_RANK(MPI_COMM_WORLD, rank, ierror) + call MPI_COMM_SIZE(MPI_COMM_WORLD, size, ierror) + print *, 'Hello, world, I am ', rank, ' of ', size + call MPI_FINALIZE(ierror) + end +``` + +## Compiling and running + +```sh +mpicc -o hello hello_c.c && mpirun -n 4 ./hello +mpicc -o ring ring_c.c && mpirun -n 4 ./ring +mpifort -o hellof hello_usempif08.f90 && mpirun -n 4 ./hellof +``` + +The canonical `hello_*` and `ring_*` programs (C, `mpif.h`, `use mpi`, and +`use mpi_f08`) live in the Open MPI source tree under `examples/` and are +compiled in CI via `examples/Makefile`. The additional C examples above are +documentation examples. diff --git a/docs/llms-src/openmpi-mpi-interface-guide.md b/docs/llms-src/openmpi-mpi-interface-guide.md new file mode 100644 index 00000000000..a9e52d98d51 --- /dev/null +++ b/docs/llms-src/openmpi-mpi-interface-guide.md @@ -0,0 +1,98 @@ +# Open MPI interface selection guide + +This guide helps tools and developers choose the correct MPI language +interface when reading or generating Open MPI application code. The MPI +Standard (https://www.mpi-forum.org/docs/) is authoritative for portable MPI +semantics; this guide describes how the interfaces map onto Open MPI. + +## The four MPI language interfaces + +Open MPI provides the MPI APIs through four interfaces: + +| Interface | Access | Use for | +|-----------|--------|---------| +| C | `#include ` | C and C++ application code | +| `mpif.h` | `INCLUDE 'mpif.h'` | legacy Fortran code (fixed/free form) | +| `use mpi` | `USE mpi` | legacy Fortran code wanting a module | +| `use mpi_f08` | `USE mpi_f08` | new Fortran code | + +## Choosing an interface + +- **C** — Choose the C interface for C or C++ code. Include ``. MPI + handles are opaque C types (`MPI_Comm`, `MPI_Datatype`, `MPI_Request`, â€Ļ) + and almost every routine returns an `int` error code. + +- **`use mpi_f08`** — **Prefer this for new Fortran code.** It is the modern, + type-safe Fortran 2008 interface: handles are derived types (for example + `TYPE(MPI_Comm)`, `TYPE(MPI_Datatype)`), procedures have explicit + interfaces so the compiler checks argument types, and the final `ierror` + argument is `OPTIONAL`. + +- **`mpif.h` and `use mpi`** — These are **legacy** interfaces, retained for + compatibility with older Fortran code. In both, MPI handles are default + `INTEGER`s and `ierror` is a required final argument. They expose the same + procedure signatures and differ only in how they are accessed + (`INCLUDE 'mpif.h'` versus `USE mpi`); the `use mpi` module additionally + provides some compile-time checking that `mpif.h` does not. Use these only + when extending existing code that already uses them. + +## Wrapper compilers + +Compile and link MPI applications with Open MPI's **wrapper compilers**, which +add the correct include and library flags automatically. Do not invoke the +underlying compiler with hand-written MPI flags. + +| Language | Wrapper | +|----------|---------| +| C | `mpicc` | +| C++ | `mpicxx` (also `mpic++`) | +| Fortran (all interfaces) | `mpifort` (also legacy `mpif77`/`mpif90`) | + +For example: + +```sh +mpicc -o hello hello.c +mpifort -o hello hello.f90 +``` + +## Large-count (`_c`) variants + +MPI-4.0 added large-count variants of routines that take element counts, so +counts can exceed the range of a C `int`. In the C interface these have a +`_c` suffix and take `MPI_Count`/`MPI_Aint` arguments instead of `int`: + +```c +int MPI_Send (const void *buf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm); +int MPI_Send_c(const void *buf, MPI_Count count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm); +``` + +Use the `_c` variant when a count (or displacement) may exceed `INT_MAX`. In +the `use mpi_f08` interface the same routine is overloaded for large counts +via `INTEGER(KIND=MPI_COUNT_KIND)` arguments (no separate name). In the +catalog (`openmpi-mpi-api.jsonl`), large-count bindings are marked with +`"large_count": true`. + +## Open MPI extensions (`MPIX_*` and `OMPI_*`) + +Symbols prefixed with `MPIX_` or `OMPI_` are **Open MPI extensions**, not part +of the MPI Standard, and are **not portable** to other MPI implementations. +Examples include the ULFM fault-tolerance routines (`MPIX_Comm_*`) and +`MPIX_Query_cuda_support`. Because the Open MPI extension header +`` is itself Open MPI-specific (other MPI implementations may not +provide it at all), simply including it and checking `OMPI_HAVE_MPI_EXT` does +not make code portable: the include can fail to compile elsewhere before any +macro is ever tested. To stay portable, gate the include itself on an +implementation check (e.g. `#if defined(OPEN_MPI)`), then test the **specific** +feature macro documented in the relevant man page (for example, +`OMPI_HAVE_MPI_EXT_CUDA`) rather than the umbrella `OMPI_HAVE_MPI_EXT`: + +```c +#if defined(OPEN_MPI) +# include +# if defined(OMPI_HAVE_MPI_EXT_CUDA) && OMPI_HAVE_MPI_EXT_CUDA + /* use MPIX_Query_cuda_support(), etc. */ +# endif +#endif +``` + +In the catalog these records have `"kind": "extension"`. diff --git a/docs/llms-src/openmpi-runtime-introspection.md b/docs/llms-src/openmpi-runtime-introspection.md new file mode 100644 index 00000000000..8a806d35f5a --- /dev/null +++ b/docs/llms-src/openmpi-runtime-introspection.md @@ -0,0 +1,137 @@ +# Open MPI runtime introspection with `ompi_info` + +The rest of this corpus describes the MPI **API** — the signatures, +parameters, and semantics that are fixed for a given Open MPI version. It +deliberately does **not** describe any single *installation*: the exact +version and build configuration, which optional components were compiled in, +or the run-time tunables (MCA parameters) those components expose. All of that +is installation-specific, so it is not snapshotted into this corpus. + +Instead, query the installation directly. If you (or the program you are +assisting) can reach an Open MPI installation, the `ompi_info` command is the +authoritative, version-correct source for everything below — far more reliable +than guessing from this corpus, which documents the API, not the local build. +`ompi_info` itself is documented by the `ompi_info(1)` command man page +(indexed in this `llms.txt`). + +## Version and build configuration + +- `ompi_info --version` prints the Open MPI version, e.g. `Open MPI v6.1.0a1`. +- `ompi_info` with no arguments prints a human-readable summary: the version, + the `./configure` command line, the compilers used, which language bindings + (C, `mpif.h`, `use mpi`, `use mpi_f08`) are enabled, and the MCA components + that were built. +- `ompi_info --parsable` prints the same information as stable, machine-readable + `key:value` lines. Useful examples: + + ``` + ompi:version:full:6.1.0a1 # the Open MPI version + mpi-api:version:full:4.1.0 # the MPI standard level this build implements + ``` + +## Available components + +Open MPI is built from MCA components (plugins) grouped into frameworks (such +as `btl` for byte transport, `coll` for collective algorithms, and `pml` for +point-to-point messaging). Which components are present depends on how the +installation was configured and built, so the only reliable way to list them +is from the installation itself, with: + +``` +ompi_info --all --parsable +``` + +Component lines are keyed `mca:::version:...`; for +example, an `mca:btl:tcp:version:...` line shows that the TCP BTL component is +present in this build. + +## Discovering MCA parameters (run-time tunables) + +MCA parameters control almost all run-time behavior — network selection, +collective algorithms, buffer sizes, and much more. The available set again +depends on which components were built, so list them from the installation +rather than expecting them in this corpus: + +``` +ompi_info --all --parsable +``` + +`--all` raises the detail to the maximum (every parameter at every "level", +1–9) and `--parsable` emits the machine-readable form. Plain `ompi_info` shows +only the small set of level-1 parameters; `--all` is what makes the listing +comprehensive. + +The output is **self-describing**: each parameter emits one line per field, +keyed `mca:::param:::`. For example: + +``` +mca:btl:tcp:param:btl_tcp_if_include:value: +mca:btl:tcp:param:btl_tcp_if_include:source:default +mca:btl:tcp:param:btl_tcp_if_include:status:read-only +mca:btl:tcp:param:btl_tcp_if_include:level:1 +mca:btl:tcp:param:btl_tcp_if_include:help:Comma-delimited list of devices and/or CIDR notation of networks to use for MPI communication ... +mca:btl:tcp:param:btl_tcp_if_include:type:string +mca:btl:tcp:param:btl_tcp_if_include:deprecated:no +``` + +Read the field names directly from the output rather than relying on a fixed +list here — fields can be added over time. The fields you will typically see +include the current `value`, where it came from (`source`), whether it can be +changed at run time (`status`), the verbosity `level` (1–3 end user, 4–6 +application tuner, 7–9 implementer), the `help` text, the value `type`, whether +it is `deprecated`, any `enumerator` lines (the allowed values of an enumerated +parameter), and any `synonym_of` (an older name that still works). + +Narrow the query to one component with `--param ` +(e.g. `ompi_info --param btl tcp --level 9`), and add `--internal` to include +parameters intended only for Open MPI developers. + +## Setting MCA parameters + +Once you know a parameter's name, it can be set several ways. They are listed +here highest precedence first (a command-line setting overrides an environment +variable, which overrides a file): + +**1. On the `mpirun` / `mpiexec` command line** with `--mca ` +(highest precedence). `mpiexec` is a synonym for `mpirun`. + +```sh +# Use only the TCP and shared-memory BTLs for MPI communication: +mpirun --mca btl tcp,self,sm -np 4 ./my_app + +# Restrict the TCP BTL to a specific network: +mpiexec --mca btl_tcp_if_include 192.168.1.0/24 -np 4 ./my_app +``` + +Quote any value that contains spaces or shell metacharacters, e.g. +`--mca "value with spaces"`. + +**2. In the environment**, by prefixing the parameter name with `OMPI_MCA_`. +This has the same effect as the command line but applies to every launch in +that environment: + +```sh +export OMPI_MCA_btl=tcp,self,sm +export OMPI_MCA_btl_tcp_if_include=192.168.1.0/24 +mpirun -np 4 ./my_app +``` + +**3. In MCA parameter files.** A tuning file passed with `mpirun --tune `, +or the default files `$HOME/.openmpi/mca-params.conf` (per user) and +`$prefix/etc/openmpi-mca-params.conf` (system-wide), with one `name = value` +per line: + +```ini +# ~/.openmpi/mca-params.conf +btl = tcp,self,sm +btl_tcp_if_include = 192.168.1.0/24 +``` + +> **Note:** PMIx and PRRTE (Open MPI's run-time dependencies) have their own +> MCA systems. Their parameters use the `--pmixmca` / `--prtemca` command-line +> options and the `PMIX_MCA_` / `PRTE_MCA_` environment-variable prefixes +> (note the single "R" in `PRTE_MCA_`), not `--mca` / `OMPI_MCA_`. + +For guidance on *which* parameters to set for a given network, accelerator, or +workload, see the human-facing "Tuning MPI applications" documentation and the +"Modular Component Architecture (MCA)" section of the Open MPI documentation. diff --git a/docs/man-openmpi/man1/mpijavac.1 b/docs/man-openmpi/man1/mpijavac.1 deleted file mode 100644 index 7b464ffa102..00000000000 --- a/docs/man-openmpi/man1/mpijavac.1 +++ /dev/null @@ -1 +0,0 @@ -.so man1/ompi-wrapper-compiler.1 diff --git a/docs/man-openmpi/man1/mpirun.1.rst b/docs/man-openmpi/man1/mpirun.1.rst index 33412896104..5eec1816613 100644 --- a/docs/man-openmpi/man1/mpirun.1.rst +++ b/docs/man-openmpi/man1/mpirun.1.rst @@ -267,10 +267,13 @@ To map processes: * ``--cpu-list ``: Comma-delimited list of processor IDs to which to bind processes [default=``NULL``]. Processor IDs are - interpreted as hwloc logical core IDs. + interpreted as `Hwloc `_ + logical core IDs. - .. note:: You can run Run the hwloc ``lstopo(1)`` command to see a - list of available cores and their logical IDs. + .. note:: You can run the `Hwloc + `_ ``lstopo(1)`` + command to see a list of available cores and their logical + IDs. To order processes' ranks in ``MPI_COMM_WORLD``: @@ -1157,7 +1160,7 @@ If only ``--bind-to OBJ`` is specified, then ``--map-by`` is determined by the n R2 hostA [../../../../../../../..][BB/../../../../../../..] R3 hostA [../../../../../../../..][../BB/../../../../../..] -The mapping pattern might be better seen if we change the default ``--rank-by`` from ``fill`` to ``span``. First, the processes are mapped by package iterating between the two marking a core at a time. Next, the processes are ranked in a spanning manner that load balances them across the object they were mapped against. Finally, the processes are bound to the core that they were mapped againast. +The mapping pattern might be better seen if we change the default ``--rank-by`` from ``fill`` to ``span``. First, the processes are mapped by package iterating between the two marking a core at a time. Next, the processes are ranked in a spanning manner that load balances them across the object they were mapped against. Finally, the processes are bound to the core that they were mapped against. .. code:: @@ -1206,12 +1209,12 @@ Means that: * Rank 1 runs on node bb, bound to logical package 0, cores 0 and 1. * Rank 2 runs on node cc, bound to logical cores 2 and 3. -Note that only logicical processor locations are supported. By default, the values specified are assumed to be cores. If you intend to specify specific hardware threads then you must add the ``:hwtcpus`` qualifier to the ``--map-by`` command line option (e.g., ``--map-by rankfile:file=myrankfile:hwtcpus``). +Note that only logical processor locations are supported. By default, the values specified are assumed to be cores. If you intend to specify specific hardware threads then you must add the ``:hwtcpus`` qualifier to the ``--map-by`` command line option (e.g., ``--map-by rankfile:file=myrankfile:hwtcpus``). If the binding specification overlaps between any two ranks then an error occurs. If you intend to allow processes to share the same logical processing unit then you must pass the ``--bind-to :overload-allowed`` command line option to tell the runtime to ignore this check. The hostnames listed above are "absolute," meaning that actual -resolveable hostnames are specified. However, hostnames can also be +resolvable hostnames are specified. However, hostnames can also be specified as "relative," meaning that they are specified in relation to an externally-specified list of hostnames (e.g., by ``mpirun``'s ``--host`` argument, a hostfile, or a job scheduler). @@ -1230,10 +1233,11 @@ hostnames, indexed from 0. For example: All package/core slot locations are specified as logical indexes. -.. note:: The Open MPI v1.6 series used physical indexes. Starting in Open MPI v5.0 only logicial indexes are supported and the ``rmaps_rank_file_physical`` MCA parameter is no longer recognized. +.. note:: The Open MPI v1.6 series used physical indexes. Starting in Open MPI v5.0 only logical indexes are supported and the ``rmaps_rank_file_physical`` MCA parameter is no longer recognized. -You can use tools such as Hwloc's `lstopo(1)` to find the logical -indexes of package and cores. +You can use tools such as `Hwloc's +`_ ``lstopo(1)`` command to +find the logical indexes of package and cores. Application Context or Executable Program? ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/man-openmpi/man1/ompi-wrapper-compiler.1.rst b/docs/man-openmpi/man1/ompi-wrapper-compiler.1.rst index 249de903ad2..ff27686f80d 100644 --- a/docs/man-openmpi/man1/ompi-wrapper-compiler.1.rst +++ b/docs/man-openmpi/man1/ompi-wrapper-compiler.1.rst @@ -2,14 +2,13 @@ .. _man1-mpic++: .. _man1-mpicxx: .. _man1-mpifort: -.. _man1-mpijavac: Open MPI Wrapper Compilers ========================== .. include_body -mpicc, mpic++, mpicxx, mpifort, mpijavac |mdash| Open MPI wrapper compilers +mpicc, mpic++, mpicxx, mpifort |mdash| Open MPI wrapper compilers SYNTAX ------ @@ -22,8 +21,6 @@ SYNTAX ``mpifort [--showme | --showme:compile | --showme:link] ...`` -``mpijavac [--showme | --showme:compile | --showme:link] ...`` - The following deprecated commands are also available |mdash| but ``mpifort`` should be used instead: @@ -117,11 +114,9 @@ Open MPI provides wrapper compilers for several languages: underlying C++ compiler with the same options. All are provided as compatibility with other MPI implementations. -* ``mpifort`` (and its legacy/deprecated aliaes ``mpif77`` and +* ``mpifort`` (and its legacy/deprecated aliases ``mpif77`` and ``mpif90``): Fortran -* ``mpijavac``: Java - The wrapper compilers for each of the languages are identical; they can be use interchangeably. The different names are provided solely for backwards compatibility. diff --git a/docs/man-openmpi/man3/MPIX_Comm_ack_failed.3.rst b/docs/man-openmpi/man3/MPIX_Comm_ack_failed.3.rst index 0302b889a5b..b02e3de4dcd 100644 --- a/docs/man-openmpi/man3/MPIX_Comm_ack_failed.3.rst +++ b/docs/man-openmpi/man3/MPIX_Comm_ack_failed.3.rst @@ -90,7 +90,7 @@ class MPI_ERR_PROC_FAILED due to this acknowledged failure. USAGE PATTERNS -------------- -One may query, without side effect, for the number of currently aknowledged +One may query, without side effect, for the number of currently acknowledged process failures *comm* by supplying 0 in *num_to_ack*. Conversely, one may unconditionally acknowledge all currently known process diff --git a/docs/man-openmpi/man3/MPIX_Comm_shrink.3.rst b/docs/man-openmpi/man3/MPIX_Comm_shrink.3.rst index 1bbf94ba44c..0b7ad568177 100644 --- a/docs/man-openmpi/man3/MPIX_Comm_shrink.3.rst +++ b/docs/man-openmpi/man3/MPIX_Comm_shrink.3.rst @@ -104,7 +104,7 @@ group of *comm* contains failed MPI processes. In particular, even when *comm* is revoked. The implementation will strive to detect all failures during the shrink -operation, but in certain circumpstances, the group of *newcomm* may still +operation, but in certain circumstances, the group of *newcomm* may still contain failed MPI processes, whose failure will be detected in subsequent MPI operations on *newcomm*. diff --git a/docs/man-openmpi/man3/MPIX_Query_cuda_support.3.rst b/docs/man-openmpi/man3/MPIX_Query_cuda_support.3.rst index 302c478b60b..9635f3f6edf 100644 --- a/docs/man-openmpi/man3/MPIX_Query_cuda_support.3.rst +++ b/docs/man-openmpi/man3/MPIX_Query_cuda_support.3.rst @@ -41,7 +41,7 @@ DESCRIPTION ----------- This function is part of an :ref:`Open MPI extension -`; it is not part of standard MPI. +`; it is not part of standard MPI. This routine returns 1 if both the MPI library was built with the NVIDIA CUDA library and the runtime supports CUDA buffers. Otherwise, diff --git a/docs/man-openmpi/man3/MPIX_Query_rocm_support.3.rst b/docs/man-openmpi/man3/MPIX_Query_rocm_support.3.rst index 59f6148079a..005a4176e50 100644 --- a/docs/man-openmpi/man3/MPIX_Query_rocm_support.3.rst +++ b/docs/man-openmpi/man3/MPIX_Query_rocm_support.3.rst @@ -41,7 +41,7 @@ DESCRIPTION ----------- This function is part of an :ref:`Open MPI extension -`; it is not part of standard MPI. +`; it is not part of standard MPI. This routine returns 1 if both the MPI library was built with the AMD ROCm library and the runtime supports ROCm buffers. Otherwise, it diff --git a/docs/man-openmpi/man3/MPI_Allgather.3.rst b/docs/man-openmpi/man3/MPI_Allgather.3.rst index 2d7926bb5cb..9013adae7ff 100644 --- a/docs/man-openmpi/man3/MPI_Allgather.3.rst +++ b/docs/man-openmpi/man3/MPI_Allgather.3.rst @@ -112,7 +112,7 @@ first group and received by all the members of the second group. Then the data is gathered from all the members of the second group and received by all the members of the first. The operation, however, need not be symmetric. The number of items sent by the processes in first -group need not be equal to the number of items sent by the the processes +group need not be equal to the number of items sent by the processes in the second group. You can move data in only one direction by giving *sendcount* a value of 0 for communication in the reverse direction. diff --git a/docs/man-openmpi/man3/MPI_Buffer_flush.3.rst b/docs/man-openmpi/man3/MPI_Buffer_flush.3.rst index e712479baac..5446825e827 100644 --- a/docs/man-openmpi/man3/MPI_Buffer_flush.3.rst +++ b/docs/man-openmpi/man3/MPI_Buffer_flush.3.rst @@ -6,7 +6,7 @@ MPI_Buffer_flush .. include_body :ref:`MPI_Buffer_flush`, :ref:`MPI_Buffer_iflush` |mdash| Wait till all messages currently in -the the MPI process specific buffer of the calling MPI process have been transmitted. +the MPI process specific buffer of the calling MPI process have been transmitted. .. The following directive tells the man page generation script to generate multiple bindings for this file. diff --git a/docs/man-openmpi/man3/MPI_Comm_set_info.3.rst b/docs/man-openmpi/man3/MPI_Comm_set_info.3.rst index e94d51e83e4..7f1892760af 100644 --- a/docs/man-openmpi/man3/MPI_Comm_set_info.3.rst +++ b/docs/man-openmpi/man3/MPI_Comm_set_info.3.rst @@ -26,7 +26,7 @@ DESCRIPTION :ref:`MPI_Comm_set_info` sets new values for the hints of the communicator associated with *comm*. :ref:`MPI_Comm_set_info` is a collective routine. The info object may be different on each process, but any info entries that -an implementation requires to be the same on all processes must appear +an implementation requires to have identical values on all processes must appear with the same value in each process's *info* object. The following info key assertions may be accepted by Open MPI: diff --git a/docs/man-openmpi/man3/MPI_Comm_split_type.3.rst b/docs/man-openmpi/man3/MPI_Comm_split_type.3.rst index 74651ea0b47..2f462fffd39 100644 --- a/docs/man-openmpi/man3/MPI_Comm_split_type.3.rst +++ b/docs/man-openmpi/man3/MPI_Comm_split_type.3.rst @@ -103,6 +103,12 @@ OMPI_COMM_TYPE_CLUSTER This type splits the communicator into subcommunicators, each of which belongs to the same cluster. +OMPI_COMM_TYPE_NVLINK + This type splits the communicator into subcommunicators based on + the NVLink domain associated with each process. It may also be + requested via ``MPI_COMM_TYPE_HW_GUIDED`` with + ``mpi_hw_resource_type`` set to ``nvlink``. + NOTES ----- diff --git a/docs/man-openmpi/man3/MPI_Dist_graph_create.3.rst b/docs/man-openmpi/man3/MPI_Dist_graph_create.3.rst index 51c98bc3fd2..6452255d2e8 100644 --- a/docs/man-openmpi/man3/MPI_Dist_graph_create.3.rst +++ b/docs/man-openmpi/man3/MPI_Dist_graph_create.3.rst @@ -48,7 +48,7 @@ this edge is stored in *weights*\ [*degrees*\ [0]+...+\ *degrees*\ [i-1]+j]. Both the *sources* and the *destinations* arrays may contain the same node more than once, and the order in which nodes are listed as destinations or sources is -not signicant. Similarly, different processes may specify edges with the +not significant. Similarly, different processes may specify edges with the same source and destination nodes. Source and destination nodes must be process ranks of comm_old. Different processes may specify different numbers of source and destination nodes, as well as different source to diff --git a/docs/man-openmpi/man3/MPI_F_sync_reg.3.rst b/docs/man-openmpi/man3/MPI_F_sync_reg.3.rst new file mode 100644 index 00000000000..678304bca23 --- /dev/null +++ b/docs/man-openmpi/man3/MPI_F_sync_reg.3.rst @@ -0,0 +1,48 @@ +.. _mpi_f_sync_reg: + + +MPI_F_sync_reg +============== + +.. include_body + +:ref:`MPI_F_sync_reg` |mdash| Prevent invalid register optimization of a Fortran buffer + +.. The following file was automatically generated +.. include:: ./bindings/mpi_f_sync_reg.rst + +INPUT/OUTPUT PARAMETERS +----------------------- +* ``buf``: Initial address of the buffer (choice). + +DESCRIPTION +----------- + +:ref:`MPI_F_sync_reg` has no executable statements; it exists only to prevent +a Fortran compiler from making invalid assumptions about the contents of a +buffer across an operation that the compiler cannot see. Passing *buf* to this +routine forces the compiler, when necessary, to flush a cached register copy +of the buffer back to memory, or to invalidate a cached register copy so that +the buffer is reloaded from memory on its next use. + +This is needed in Fortran code that aggressively optimizes register usage +around nonblocking or one-sided operations, whose completion |mdash| and +therefore whose effect on the buffer |mdash| is not visible to the compiler +from the surrounding code. + + +NOTES +----- + +This routine is provided only in the Fortran bindings; it has no C binding +because it would serve no purpose in C. It also has no *ierror* argument +because there is no operation that can fail. + +For example, after an :ref:`MPI_Wait` that completes a nonblocking receive +into *buf*, a call to ``MPI_F_sync_reg(buf)`` ensures that the compiler +reloads *buf* from memory rather than reusing a stale register copy that +predates the receive. + + +.. seealso:: + * :ref:`MPI_Wait` diff --git a/docs/man-openmpi/man3/MPI_Fetch_and_op.3.rst b/docs/man-openmpi/man3/MPI_Fetch_and_op.3.rst index 48b163f0eee..f2f5e884ca8 100644 --- a/docs/man-openmpi/man3/MPI_Fetch_and_op.3.rst +++ b/docs/man-openmpi/man3/MPI_Fetch_and_op.3.rst @@ -46,7 +46,7 @@ the associative function f(a, b) =b; that is, the current value in the target memory is replaced by the value supplied by the origin. A new predefined operation, MPI_NO_OP, is defined. It corresponds to the -assiciative function f(a, b) = a; that is the current value in the +associative function f(a, b) = a; that is the current value in the target memory is returned in the result buffer at the origin and no operation is performed on the target buffer. diff --git a/docs/man-openmpi/man3/MPI_File_get_info.3.rst b/docs/man-openmpi/man3/MPI_File_get_info.3.rst index 24f7a3f2e86..e1de4bb9c38 100644 --- a/docs/man-openmpi/man3/MPI_File_get_info.3.rst +++ b/docs/man-openmpi/man3/MPI_File_get_info.3.rst @@ -44,62 +44,7 @@ hints that can be set. HINTS ----- -The following hints can be used as values for the *info_used* argument. - -**SETTABLE HINTS** - -* ``shared_file_timeout``: Amount of time (in seconds) to wait for - access to the shared file pointer before exiting with - ``MPI_ERR_TIMEDOUT``. - -* ``rwlock_timeout``: Amount of time (in seconds) to wait for - obtaining a read or write lock on a contiguous chunk of a UNIX file - before exiting with ``MPI_ERR_TIMEDOUT``. - -* ``noncoll_read_bufsize``: Maximum size of the buffer used by MPI I/O - to satisfy read requests in the noncollective data-access - routines. - - .. note:: A buffer size smaller than the distance (in bytes) in a - UNIX file between the first byte and the last byte of the - access request causes MPI I/O to iterate and perform - multiple UNIX ``read()`` or ``write()`` calls. If the - request includes multiple noncontiguous chunks of data, - and the buffer size is greater than the size of those - chunks, then the UNIX ``read()`` or ``write()`` (made at - the MPI I/O level) will access data not requested by this - process in order to reduce the total number of ``write()`` - calls made. If this is not desirable behavior, you should - reduce this buffer size to equal the size of the - contiguous chunks within the aggregate request. - -* ``noncoll_write_bufsize``: Maximum size of the buffer used by MPI - I/O to satisfy write requests in the noncollective data-access - routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``coll_read_bufsize``: Maximum size of the buffer used by MPI I/O to - satisfy read requests in the collective data-access routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``coll_write_bufsize``: Maximum size of the buffer used by MPI I/O - to satisfy write requests in the collective data-access - routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``mpiio_concurrency``: (boolean) controls whether nonblocking - I/O routines can bind an extra thread to an LWP. - -* ``mpiio_coll_contiguous``: (boolean) controls whether subsequent - collective data accesses will request collectively contiguous - regions of the file. - -**NON-SETTABLE HINTS** - -* ``filename``: Access this hint to get the name of the file. +.. include:: /tuning-apps/_include/ompio-mpi-info-hints.rst diff --git a/docs/man-openmpi/man3/MPI_File_open.3.rst b/docs/man-openmpi/man3/MPI_File_open.3.rst index 0b0be6cba49..7a8644be276 100644 --- a/docs/man-openmpi/man3/MPI_File_open.3.rst +++ b/docs/man-openmpi/man3/MPI_File_open.3.rst @@ -83,65 +83,7 @@ hints that can be set. HINTS ----- -The following hints can be used as values for the *info* argument. - -**SETTABLE HINTS** - -* ``MPI_INFO_NULL`` - -* ``shared_file_timeout``: Amount of time (in seconds) to wait for - access to the shared file pointer before exiting with - ``MPI_ERR_TIMEDOUT``. - -* ``rwlock_timeout``: Amount of time (in seconds) to wait for - obtaining a read or write lock on a contiguous chunk of a UNIX file - before exiting with ``MPI_ERR_TIMEDOUT``. - -* ``noncoll_read_bufsize``: Maximum size of the buffer used by MPI I/O - to satisfy multiple noncontiguous read requests in the noncollective - data-access routines. - - .. note:: A buffer size smaller than the distance (in bytes) in a - UNIX file between the first byte and the last byte of the - access request causes MPI I/O to iterate and perform - multiple UNIX `read()` or `write()` calls. If the request - includes multiple noncontiguous chunks of data, and the - buffer size is greater than the size of those chunks, then - the UNIX `read()` or `write()` (made at the MPI I/O level) - will access data not requested by this process in order to - reduce the total number of `write()` calls made. If this - is not desirable behavior, you should reduce this buffer - size to equal the size of the contiguous chunks within the - aggregate request. - -* ``noncoll_write_bufsize``: Maximum size of the buffer used by MPI - I/O to satisfy multiple noncontiguous write requests in the - noncollective data-access routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``coll_read_bufsize``: Maximum size of the buffer used by MPI I/O to - satisfy multiple noncontiguous read requests in the collective - data-access routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``coll_write_bufsize``: Maximum size of the buffer used by MPI I/O - to satisfy multiple noncontiguous write requests in the collective - data-access routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``mpiio_concurrency``: (boolean) controls whether nonblocking I/O - routines can bind an extra thread to an LWP. .sp - -* ``mpiio_coll_contiguous``: (boolean) controls whether subsequent - collective data accesses will request collectively contiguous - regions of the file. - -**NON-SETTABLE HINTS** - -* ``filename``: Access this hint to get the name of the file. +.. include:: /tuning-apps/_include/ompio-mpi-info-hints.rst ERRORS diff --git a/docs/man-openmpi/man3/MPI_File_set_info.3.rst b/docs/man-openmpi/man3/MPI_File_set_info.3.rst index 76253fe2b5a..b8bb4e24533 100644 --- a/docs/man-openmpi/man3/MPI_File_set_info.3.rst +++ b/docs/man-openmpi/man3/MPI_File_set_info.3.rst @@ -42,63 +42,7 @@ for a list of hints that can be set. HINTS ----- -The following hints can be used as values for the *info* argument. - -**SETTABLE HINTS** - -* ``shared_file_timeout``: Amount of time (in seconds) to wait for - access to the shared file pointer before exiting with - ``MPI_ERR_TIMEDOUT``. - -* ``rwlock_timeout``: Amount of time (in seconds) to wait for - obtaining a read or write lock on a contiguous chunk of a UNIX file - before exiting with ``MPI_ERR_TIMEDOUT``. - -* ``noncoll_read_bufsize``: Maximum size of the buffer used by MPI I/O - to satisfy read requests in the noncollective data-access - routines. - - .. note:: A buffer size smaller than the distance (in bytes) in a - UNIX file between the first byte and the last byte of the - access request causes MPI I/O to iterate and perform - multiple UNIX ``read()`` or ``write()`` calls. If the request - includes multiple noncontiguous chunks of data, and the - buffer size is greater than the size of those chunks, then - the UNIX ``read()`` or ``write()`` (made at the MPI I/O level) - will access data not requested by this process in order to - reduce the total number of ``write()`` calls made. If this is - not desirable behavior, you should reduce this buffer size - to equal the size of the contiguous chunks within the - aggregate request. - -* ``noncoll_write_bufsize``: Maximum size of the buffer used by MPI - I/O to satisfy write requests in the noncollective data-access - routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``coll_read_bufsize``: Maximum size of the buffer used by MPI I/O to - satisfy read requests in the collective data-access routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``coll_write_bufsize``: Maximum size of the buffer used by MPI I/O - to satisfy write requests in the collective data-access - routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``mpiio_concurrency``: (boolean) controls whether nonblocking I/O - routines can bind an extra thread to an LWP. - -* ``mpiio_coll_contiguous``: (boolean) controls whether subsequent - collective data accesses will request collectively contiguous - regions of the file. - - - **NON-SETTABLE HINTS** - -* ``filename``: Access this hint to get the name of the file. +.. include:: /tuning-apps/_include/ompio-mpi-info-hints.rst ERRORS diff --git a/docs/man-openmpi/man3/MPI_File_set_view.3.rst b/docs/man-openmpi/man3/MPI_File_set_view.3.rst index d2d00ec229e..f211e99d4cd 100644 --- a/docs/man-openmpi/man3/MPI_File_set_view.3.rst +++ b/docs/man-openmpi/man3/MPI_File_set_view.3.rst @@ -59,62 +59,7 @@ set. HINTS ----- -The following hints can be used as values for the *info* argument. - -**SETTABLE HINTS** - -* ``MPI_INFO_NULL`` - -* ``shared_file_timeout``: Amount of time (in seconds) to wait for - access to the shared file pointer before exiting with - ``MPI_ERR_TIMEDOUT``. - -* ``rwlock_timeout``: Amount of time (in seconds) to wait for - obtaining a read or write lock on a contiguous chunk of a UNIX file - before exiting with ``MPI_ERR_TIMEDOUT``. - -* ``noncoll_read_bufsize``: Maximum size of the buffer used by MPI I/O - to satisfy read requests in the noncollective data-access routines. - - .. note:: A buffer size smaller than the distance (in bytes) in a - UNIX file between the first byte and the last byte of the - access request causes MPI I/O to iterate and perform - multiple UNIX ``read()`` or ``write()`` calls. If the - request includes multiple noncontiguous chunks of data, - and the buffer size is greater than the size of those - chunks, then the UNIX ``read()`` or ``write()`` (made at - the MPI I/O level) will access data not requested by this - process in order to reduce the total number of ``write()`` - calls made. If this is not desirable behavior, you should - reduce this buffer size to equal the size of the - contiguous chunks within the aggregate request. - -* ``noncoll_write_bufsize``: Maximum size of the buffer used by MPI - I/O to satisfy write requests in the noncollective data-access - routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``coll_read_bufsize``: Maximum size of the buffer used by MPI I/O to - satisfy read requests in the collective data-access routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``coll_write_bufsize``: Maximum size of the buffer used by MPI I/O - to satisfy write requests in the collective data-access routines. - - See the above note in ``noncoll_read_bufsize``. - -* ``mpiio_concurrency``: (boolean) controls whether nonblocking I/O - routines can bind an extra thread to an LWP. - -* ``mpiio_coll_contiguous``: (boolean) controls whether subsequent - collective data accesses will request collectively contiguous - regions of the file. - -**NON-SETTABLE HINTS** - -* ``filename``: Access this hint to get the name of the file. +.. include:: /tuning-apps/_include/ompio-mpi-info-hints.rst ERRORS diff --git a/docs/man-openmpi/man3/MPI_Finalize.3.rst b/docs/man-openmpi/man3/MPI_Finalize.3.rst index bf52247e205..faad83e83dc 100644 --- a/docs/man-openmpi/man3/MPI_Finalize.3.rst +++ b/docs/man-openmpi/man3/MPI_Finalize.3.rst @@ -65,7 +65,7 @@ some other verification of completion. For example, a successful return from a blocking communication operation or from one of the :ref:`MPI_Wait` or :ref:`MPI_Test` -varients means that the communication is completed by the user and the +variants means that the communication is completed by the user and the buffer can be reused, but does not guarantee that the local process has no more work to do. Similarly, a successful return from :ref:`MPI_Request_free` with a request handle generated by an diff --git a/docs/man-openmpi/man3/MPI_Get_accumulate.3.rst b/docs/man-openmpi/man3/MPI_Get_accumulate.3.rst index 5a717ef5836..68e47135c71 100644 --- a/docs/man-openmpi/man3/MPI_Get_accumulate.3.rst +++ b/docs/man-openmpi/man3/MPI_Get_accumulate.3.rst @@ -70,7 +70,7 @@ the associative function f(a, b) =b; that is, the current value in the target memory is replaced by the value supplied by the origin. A new predefined operation, MPI_NO_OP, is defined. It corresponds to the -assiciative function f(a, b) = a; that is the current value in the +associative function f(a, b) = a; that is the current value in the target memory is returned in the result buffer at the origin and no operation is performed on the target buffer. diff --git a/docs/man-openmpi/man3/MPI_Init.3.rst b/docs/man-openmpi/man3/MPI_Init.3.rst index 6df7d039cc2..b3bd1182c89 100644 --- a/docs/man-openmpi/man3/MPI_Init.3.rst +++ b/docs/man-openmpi/man3/MPI_Init.3.rst @@ -45,7 +45,7 @@ interprets, nor distributes them: int main(int argv, char *argv[]) { MPI_Init(&argc, &argv); - /* ...body of main MPI pogram... */ + /* ...body of main MPI program... */ MPI_Finalize(); return 0; } diff --git a/docs/man-openmpi/man3/MPI_Init_thread.3.rst b/docs/man-openmpi/man3/MPI_Init_thread.3.rst index 8d77ba17d50..e9068b0c20d 100644 --- a/docs/man-openmpi/man3/MPI_Init_thread.3.rst +++ b/docs/man-openmpi/man3/MPI_Init_thread.3.rst @@ -58,7 +58,7 @@ neither modifies, interprets, nor distributes them: int main(int argv, char *argv[]) { int provided; MPI_Init_thread(&argc, &argv, MPI_THREAD_MULTIPLE, &provided); - /* ...body of main MPI pogram... */ + /* ...body of main MPI program... */ MPI_Finalize(); return 0; } @@ -142,7 +142,7 @@ of the values listed below. environment variable. Starting with Open MPI v6.0.0, the Open MPI community - recomends using one of the string name variants so that it + recommends using one of the string name variants so that it can be correctly mapped to the corresponding Open MPI ABI value or the MPI Standard ABI value, as relevant. diff --git a/docs/man-openmpi/man3/MPI_Reduce_scatter_block.3.rst b/docs/man-openmpi/man3/MPI_Reduce_scatter_block.3.rst index 0d4ab22de09..ee1188af231 100644 --- a/docs/man-openmpi/man3/MPI_Reduce_scatter_block.3.rst +++ b/docs/man-openmpi/man3/MPI_Reduce_scatter_block.3.rst @@ -20,7 +20,7 @@ results in blocks. INPUT PARAMETERS ---------------- * ``sendbuf``: Starting address of send buffer (choice). -* ``recvcount``: lement count per block (non-negative integer). +* ``recvcount``: Element count per block (non-negative integer). * ``datatype``: Datatype of elements of input buffer (handle). * ``op``: Operation (handle). * ``comm``: Communicator (handle). diff --git a/docs/man-openmpi/man3/MPI_Session_get_num_psets.3.rst b/docs/man-openmpi/man3/MPI_Session_get_num_psets.3.rst index 032080dd172..839da2ea922 100644 --- a/docs/man-openmpi/man3/MPI_Session_get_num_psets.3.rst +++ b/docs/man-openmpi/man3/MPI_Session_get_num_psets.3.rst @@ -20,7 +20,7 @@ INPUT PARAMETERS OUTPUT PARAMETERS ----------------- -* ``npset_names`` : number of available process sets (non-negtive integer) +* ``npset_names`` : number of available process sets (non-negative integer) * ``ierror`` : Fortran only: Error status (integer). DESCRIPTION diff --git a/docs/man-openmpi/man3/MPI_Session_set_errhandler.3.rst b/docs/man-openmpi/man3/MPI_Session_set_errhandler.3.rst index 7ada2ebb545..bc7d253e8f8 100644 --- a/docs/man-openmpi/man3/MPI_Session_set_errhandler.3.rst +++ b/docs/man-openmpi/man3/MPI_Session_set_errhandler.3.rst @@ -29,6 +29,21 @@ The error handler must be either a predefined error handler or an error handler created by a call to :ref:`MPI_Session_create_errhandler`. +NOTES +----- + +When using MPI_ERRORS_ABORT with a session, note that per MPI-5.0 Sec 9.3 +(pp.447-448), this error handler aborts ONLY the local MPI process, not the +entire job. When using the PRRTE runtime (the default for Open MPI), you must +use the ``--enable-recovery`` option with ``mpirun(1)`` or ``mpiexec(1)`` to +prevent the runtime from automatically aborting other processes in the job +when the local process aborts. + +For example:: + + mpirun --enable-recovery -n 4 ./my_program + + ERRORS ------ diff --git a/docs/man-openmpi/man3/MPI_T_category_get_index.3.rst b/docs/man-openmpi/man3/MPI_T_category_get_index.3.rst new file mode 100644 index 00000000000..e9849463531 --- /dev/null +++ b/docs/man-openmpi/man3/MPI_T_category_get_index.3.rst @@ -0,0 +1,61 @@ +.. _mpi_t_category_get_index: + + +MPI_T_category_get_index +======================== + +.. include_body + +:ref:`MPI_T_category_get_index` |mdash| Query the index of a category from its name + +.. The following file was automatically generated +.. include:: ./bindings/mpi_t_category_get_index.rst + +INPUT PARAMETERS +---------------- +* ``name``: Name of the category to query. + +OUTPUT PARAMETERS +----------------- +* ``cat_index``: Index of the category. + +DESCRIPTION +----------- + +:ref:`MPI_T_category_get_index` can be used to retrieve the index of a +category given its name. The *name* argument is provided by the caller as a +null-terminated string, and the matching index is returned in *cat_index*. +The returned index can then be passed to other MPI tool information interface +routines, such as :ref:`MPI_T_category_get_info`. + +This routine allows a tool to look up a category by name without iterating +over the entire set of categories. Because the number of categories exposed +by the implementation can change over time, this is both more convenient and +lower overhead than enumerating all of the categories to find a particular +one. + + +NOTES +----- + +Category names are implementation-specific. Looking a category up by name is +therefore not portable across MPI implementations, but may be the preferred +approach for a tool that targets Open MPI specifically. + + +ERRORS +------ + +:ref:`MPI_T_category_get_index` will fail if: + +* ``MPI_T_ERR_NOT_INITIALIZED``: The MPI Tools interface is not initialized. + +* ``MPI_T_ERR_INVALID``: ``name`` or ``cat_index`` is ``NULL``. + +* ``MPI_T_ERR_INVALID_NAME``: ``name`` does not match the name of any category + provided by the implementation at the time of the call. + + +.. seealso:: + * :ref:`MPI_T_category_get_info` + * :ref:`MPI_T_category_get_num` diff --git a/docs/man-openmpi/man3/MPI_T_category_get_num_events.3.rst b/docs/man-openmpi/man3/MPI_T_category_get_num_events.3.rst index e3294c15675..4671ef8924b 100644 --- a/docs/man-openmpi/man3/MPI_T_category_get_num_events.3.rst +++ b/docs/man-openmpi/man3/MPI_T_category_get_num_events.3.rst @@ -33,3 +33,5 @@ ERRORS :ref:`MPI_T_category_get_num_events` will fail if: * ``MPI_T_ERR_NOT_INITIALIZED``: The MPI Tools interface not initialized + +* ``MPI_T_ERR_INVALID_INDEX``: The category index is invalid diff --git a/docs/man-openmpi/man3/MPI_T_cvar_get_index.3.rst b/docs/man-openmpi/man3/MPI_T_cvar_get_index.3.rst new file mode 100644 index 00000000000..b695f2ec882 --- /dev/null +++ b/docs/man-openmpi/man3/MPI_T_cvar_get_index.3.rst @@ -0,0 +1,62 @@ +.. _mpi_t_cvar_get_index: + + +MPI_T_cvar_get_index +==================== + +.. include_body + +:ref:`MPI_T_cvar_get_index` |mdash| Query the index of a control variable from its name + +.. The following file was automatically generated +.. include:: ./bindings/mpi_t_cvar_get_index.rst + +INPUT PARAMETERS +---------------- +* ``name``: Name of the control variable to query. + +OUTPUT PARAMETERS +----------------- +* ``cvar_index``: Index of the control variable. + +DESCRIPTION +----------- + +:ref:`MPI_T_cvar_get_index` can be used to retrieve the index of a control +variable given its name. The *name* argument is provided by the caller as a +null-terminated string, and the matching index is returned in *cvar_index*. +The returned index can then be passed to other MPI tool information +interface routines, such as :ref:`MPI_T_cvar_get_info`. Control variables in +Open MPI are the same as MCA parameters. + +This routine allows a tool to look up a control variable by name without +iterating over the entire set of control variables. Because the number of +control variables exposed by the implementation can change over time, this is +both more convenient and lower overhead than enumerating all of the variables +to find a particular one. + + +NOTES +----- + +Control variable names are implementation-specific. Looking a variable up by +name is therefore not portable across MPI implementations, but may be the +preferred approach for a tool that targets Open MPI specifically. + + +ERRORS +------ + +:ref:`MPI_T_cvar_get_index` will fail if: + +* ``MPI_T_ERR_NOT_INITIALIZED``: The MPI Tools interface is not initialized. + +* ``MPI_T_ERR_INVALID``: ``name`` or ``cvar_index`` is ``NULL``. + +* ``MPI_T_ERR_INVALID_NAME``: ``name`` does not match the name of any control + variable provided by the implementation at the time of the call. + + +.. seealso:: + * :ref:`MPI_T_cvar_get_info` + * :ref:`MPI_T_cvar_get_num` diff --git a/docs/man-openmpi/man3/MPI_T_pvar_get_index.3.rst b/docs/man-openmpi/man3/MPI_T_pvar_get_index.3.rst new file mode 100644 index 00000000000..6017e152346 --- /dev/null +++ b/docs/man-openmpi/man3/MPI_T_pvar_get_index.3.rst @@ -0,0 +1,65 @@ +.. _mpi_t_pvar_get_index: + + +MPI_T_pvar_get_index +==================== + +.. include_body + +:ref:`MPI_T_pvar_get_index` |mdash| Query the index of a performance variable from its name + +.. The following file was automatically generated +.. include:: ./bindings/mpi_t_pvar_get_index.rst + +INPUT PARAMETERS +---------------- +* ``name``: Name of the performance variable to query. +* ``var_class``: Class of the performance variable to query. + +OUTPUT PARAMETERS +----------------- +* ``pvar_index``: Index of the performance variable. + +DESCRIPTION +----------- + +:ref:`MPI_T_pvar_get_index` can be used to retrieve the index of a +performance variable given its name and class. The *name* and *var_class* +arguments are provided by the caller |mdash| *name* as a null-terminated +string |mdash| and the matching index is returned in *pvar_index*. A +performance variable is identified by the pair (*name*, *var_class*), so both +must be supplied. The returned index can then be passed to other MPI tool +information interface routines, such as :ref:`MPI_T_pvar_get_info`. + +This routine allows a tool to look up a performance variable by name without +iterating over the entire set of performance variables. Because the number of +performance variables exposed by the implementation can change over time, this +is both more convenient and lower overhead than enumerating all of the +variables to find a particular one. + + +NOTES +----- + +Performance variable names are implementation-specific. Looking a variable up +by name is therefore not portable across MPI implementations, but may be the +preferred approach for a tool that targets Open MPI specifically. + + +ERRORS +------ + +:ref:`MPI_T_pvar_get_index` will fail if: + +* ``MPI_T_ERR_NOT_INITIALIZED``: The MPI Tools interface is not initialized. + +* ``MPI_T_ERR_INVALID``: ``name`` or ``pvar_index`` is ``NULL``. + +* ``MPI_T_ERR_INVALID_NAME``: ``name`` does not match the name of any + performance variable of the specified *var_class* provided by the + implementation at the time of the call. + + +.. seealso:: + * :ref:`MPI_T_pvar_get_info` + * :ref:`MPI_T_pvar_get_num` diff --git a/docs/man-openmpi/man3/MPI_T_source_get_timestamp.3.rst b/docs/man-openmpi/man3/MPI_T_source_get_timestamp.3.rst index d7d67bc32da..96c7d7a0b1a 100644 --- a/docs/man-openmpi/man3/MPI_T_source_get_timestamp.3.rst +++ b/docs/man-openmpi/man3/MPI_T_source_get_timestamp.3.rst @@ -22,7 +22,7 @@ OUTPUT PARAMETERS DESCRIPTION ----------- -:ref:`MPI_T_source_get_timestamp` returns the current timestamp from the specificed source. +:ref:`MPI_T_source_get_timestamp` returns the current timestamp from the specified source. ERRORS diff --git a/docs/man-openmpi/man3/MPI_Type_match_size.3.rst b/docs/man-openmpi/man3/MPI_Type_match_size.3.rst index 5670845b7f4..fb36aab07b2 100644 --- a/docs/man-openmpi/man3/MPI_Type_match_size.3.rst +++ b/docs/man-openmpi/man3/MPI_Type_match_size.3.rst @@ -41,6 +41,25 @@ suitable datatype. In C use the sizeof builtin instead of :ref:`MPI_Sizeof`. It is erroneous to specify a size not supported by the compiler. +NOTES +----- + +In Open MPI, *typeclass* always refers to a Fortran numeric intrinsic +type, and :ref:`MPI_Type_match_size` only ever returns an intrinsic +Fortran predefined datatype (for example, ``MPI_REAL8``, +``MPI_INTEGER4``, or ``MPI_COMPLEX8``) |mdash| regardless of whether it +is called from C or Fortran. It does not return C predefined datatypes +(such as ``MPI_DOUBLE``), nor composite predefined datatypes (such as +``MPI_2REAL`` or ``MPI_2INTEGER``), even when one of those happens to +have the requested *size*. + +Consequently, if Open MPI was built with ``--disable-mpi-fortran``, the +Fortran intrinsic datatypes are unavailable. In that case no datatype +can match any *typeclass* / *size* combination: every such request is +treated as a size not supported by the compiler, and the call fails with +error class ``MPI_ERR_ARG`` (subject to the relevant error handler). + + ERRORS ------ diff --git a/docs/man-openmpi/man3/MPI_Win_fence.3.rst b/docs/man-openmpi/man3/MPI_Win_fence.3.rst index c37aa7433d8..90eb3a4fe06 100644 --- a/docs/man-openmpi/man3/MPI_Win_fence.3.rst +++ b/docs/man-openmpi/man3/MPI_Win_fence.3.rst @@ -60,7 +60,7 @@ MPI_MODE_NOSTORE MPI_MODE_NOPUT Informs that the local window will not be updated by any put or - accummulate calls in the ensuing epoch (until next fence call). + accumulate calls in the ensuing epoch (until next fence call). MPI_MODE_NOSUCCEED No local RMA calls will be issued after this fence. This assertion diff --git a/docs/man-openmpi/man3/MPI_Win_post.3.rst b/docs/man-openmpi/man3/MPI_Win_post.3.rst index fbbf122588a..53212ef02c6 100644 --- a/docs/man-openmpi/man3/MPI_Win_post.3.rst +++ b/docs/man-openmpi/man3/MPI_Win_post.3.rst @@ -46,7 +46,7 @@ MPI_MODE_NOSTORE MPI_MODE_NOPUT Informs that the local window will not be updated by put or - accummulate calls until the ensuing wait synchronization. + accumulate calls until the ensuing wait synchronization. ERRORS diff --git a/docs/man-openmpi/man3/MPI_Win_set_info.3.rst b/docs/man-openmpi/man3/MPI_Win_set_info.3.rst index 69de8bf2894..30aabb87f62 100644 --- a/docs/man-openmpi/man3/MPI_Win_set_info.3.rst +++ b/docs/man-openmpi/man3/MPI_Win_set_info.3.rst @@ -26,7 +26,7 @@ DESCRIPTION :ref:`MPI_WIN_SET_INFO` sets new values for the hints of the window associated with *win.* :ref:`MPI_WIN_SET_INFO` is a collective routine. The info object may be different on each process, but any info entries that an -implementation requires to be the same on all processes must appear with +implementation requires to have identical values on all processes must appear with the same value in each process's *info* object. diff --git a/docs/man-openmpi/man3/MPI_Wtime.3.rst b/docs/man-openmpi/man3/MPI_Wtime.3.rst index 9baeef4c46d..70edf333f3d 100644 --- a/docs/man-openmpi/man3/MPI_Wtime.3.rst +++ b/docs/man-openmpi/man3/MPI_Wtime.3.rst @@ -70,7 +70,7 @@ function will be used to obtain a monotonic clock value with whatever precision is supported on that platform (e.g., nanoseconds). Note, too, that the MCA parameter opal_timer_require_monotonic can -influcence this behavior. It defaults to true, but if set to false, Open +influence this behavior. It defaults to true, but if set to false, Open MPI may use a finer-grained timing mechanism (e.g., the RDTSC/RDTSCP clock ticks on x86_64 platforms), but is not guaranteed to be monotonic in some cases (e.g., if the MPI process is not bound to a single diff --git a/docs/man-openmpi/man3/index.rst b/docs/man-openmpi/man3/index.rst index c746815fbf3..2cbd07ad1d5 100644 --- a/docs/man-openmpi/man3/index.rst +++ b/docs/man-openmpi/man3/index.rst @@ -108,6 +108,7 @@ MPI API manual pages (section 3) MPI_Error_string.3.rst MPI_Exscan.3.rst MPI_Exscan_init.3.rst + MPI_F_sync_reg.3.rst MPI_Fetch_and_op.3.rst MPI_File_c2f.3.rst MPI_File_call_errhandler.3.rst @@ -378,10 +379,12 @@ MPI API manual pages (section 3) MPI_T_category_get_categories.3.rst MPI_T_category_get_cvars.3.rst MPI_T_category_get_events.3.rst + MPI_T_category_get_index.3.rst MPI_T_category_get_info.3.rst MPI_T_category_get_num.3.rst MPI_T_category_get_num_events.3.rst MPI_T_category_get_pvars.3.rst + MPI_T_cvar_get_index.3.rst MPI_T_cvar_get_info.3.rst MPI_T_cvar_get_num.3.rst MPI_T_cvar_handle_alloc.3.rst @@ -407,6 +410,7 @@ MPI API manual pages (section 3) MPI_T_event_set_dropped_handler.3.rst MPI_T_finalize.3.rst MPI_T_init_thread.3.rst + MPI_T_pvar_get_index.3.rst MPI_T_pvar_get_info.3.rst MPI_T_pvar_get_num.3.rst MPI_T_pvar_handle_alloc.3.rst diff --git a/docs/man-openshmem/man3/shmem_int_fetch.3.rst b/docs/man-openshmem/man3/shmem_int_fetch.3.rst index 29f8d0aaf31..ddb254b0bf8 100644 --- a/docs/man-openshmem/man3/shmem_int_fetch.3.rst +++ b/docs/man-openshmem/man3/shmem_int_fetch.3.rst @@ -75,7 +75,7 @@ RETURN VALUES ------------- The contents at the *target* address on the remote PE. The data type of -the return value is the same as the the type of the remote data object. +the return value is the same as the type of the remote data object. .. seealso:: diff --git a/docs/man-openshmem/man3/shmem_wait.3.rst b/docs/man-openshmem/man3/shmem_wait.3.rst index 5b5fd861e65..c30326bda2a 100644 --- a/docs/man-openshmem/man3/shmem_wait.3.rst +++ b/docs/man-openshmem/man3/shmem_wait.3.rst @@ -81,24 +81,6 @@ processor that it has completed some action. The arguments are as follows: -target - The remotely accessible integer data object to be updated on the - remote PE. If you are using C/C++, the type of target should match - that implied in the SYNOPSIS section. If you are using the Fortran - compiler, it must be of type integer with an element size of 4 bytes - for SHMEM_INT4_ADD and 8 bytes for SHMEM_INT8_ADD. - -value - The value to be atomically added to target. If you are using C/C++, - the type of value should match that implied in the SYNOPSIS section. - If you are using Fortran, it must be of type integer with an element - size of target. - -pe - An integer that indicates the PE number upon which target is to be - updated. If you are using Fortran, it must be a default integer - value. - ivar A remotely accessible integer variable that is being updated by another PE. If you are using C/C++, the type of ivar should match @@ -139,7 +121,7 @@ cmp cmp_value cmp_value must be of type integer. If you are using C/C++, the type - of cmp_value should match thatimplied in the SYNOPSIS section. If you + of cmp_value should match that implied in the SYNOPSIS section. If you are using Fortran, cmp_value must be an integer of the same size and kind as ivar. The :ref:`shmem_wait` routines return when ivar is no longer equal to cmp_value. The :ref:`shmem_wait_until` routines return when the diff --git a/docs/mca.rst b/docs/mca.rst index 0c8256a4e55..9f2be8f009a 100644 --- a/docs/mca.rst +++ b/docs/mca.rst @@ -138,7 +138,7 @@ thumb that the developers use are: #. Instead of using a constant for an important value, make it an MCA parameter. #. If a task can be implemented in multiple, user-discernible ways, - implement as many as possible, and use an an MCA parameter to + implement as many as possible, and use an MCA parameter to choose between them at run-time. For example, an easy MCA parameter to describe is the boundary between @@ -264,8 +264,6 @@ shells): Tuning MCA parameter files ^^^^^^^^^^^^^^^^^^^^^^^^^^ -.. error:: TODO This entire section needs to be checked for correctness. - Simple text files can be used to set MCA parameter values for a specific application. @@ -301,8 +299,9 @@ can be added to the command line as follows: shell$ mpirun -np 2 --tune foo.conf,bar.conf a.out The contents of tuned files consist of one or more lines, each of -which contain zero or more `-x` and `--mca` options. Comments are not -allowed. For example, the following tuned file: +which contain zero or more ``-x`` and ``--mca`` options. Empty lines +and lines beginning with the ``#`` character are ignored. For example, +the following tuned file: .. code-block:: @@ -322,13 +321,11 @@ is equivalent to: Although the typical use case for tuned parameter files is to be specified on the command line, they can also be set as MCA parameters -in the environment. The MCA parameter ``mca_base_envvar_file_prefix`` +in the environment. The MCA parameter ``mca_base_envar_file_prefix`` contains a comma-delimited list of tuned parameter files exactly as -they would be passed to the ``--tune`` command line option. The MCA -parameter ``mca_base_envvar_file_path`` specifies the path to search -for tuned files with relative paths. - -.. error:: TODO Check that these MCA var names ^^ are correct. +they would be passed to the ``--tune`` command line option. Tuned +files named with relative paths are resolved against the directories +listed in the ``mca_base_param_file_path`` MCA parameter. Configuration files ^^^^^^^^^^^^^^^^^^^ @@ -361,9 +358,9 @@ By default, two files are searched (in order): #. ``$prefix/etc/openmpi-mca-params.conf``: The system-supplied set of values has a lower precedence. -More specifically, the MCA parameter ``mca_param_files`` specifies a -colon-delimited path of files to search for MCA parameters. Files to -the left have lower precedence; files to the right are higher +More specifically, the MCA parameter ``mca_base_param_files`` specifies +a comma-delimited list of files to search for MCA parameters. Files to +the left have *higher* precedence; files to the right have lower precedence. .. note:: Keep in mind that, just like components, these parameter @@ -379,8 +376,6 @@ precedence. customization, which is especially relevant in heterogeneous environments. -.. error:: TODO This table needs to be checked for correctness. - .. warning:: Setting Open MPI MCA parameters via configuration files entails editing (by default) the ``mca-params.conf`` or ``openmpi-mca-params.conf`` files. When setting PMIx- @@ -392,12 +387,23 @@ precedence. | | ``$prefix/etc/openmpi-mca-params.conf`` | +----------+------------------------------------------+ | PMIx | ``$HOME/.pmix/mca-params.conf`` or | - | | ``$prefix/etc/openpmix-mca-params.conf`` | + | | ``$prefix/etc/pmix-mca-params.conf`` | +----------+------------------------------------------+ - | PRRTE | ``$HOME/.prrte/mca-params.conf`` or | + | PRRTE | ``$HOME/.prte/mca-params.conf`` or | | | ``$prefix/etc/prte-mca-params.conf`` | +----------+------------------------------------------+ +.. note:: By default, PRRTE and PMIx read only their own configuration + files (the PMIx and PRRTE rows above), not Open MPI's + ``openmpi-mca-params.conf``. If you set the ``OMPIHOME`` + environment variable, however, the runtime will additionally + read ``$OMPIHOME/etc/openmpi-mca-params.conf`` and apply any + PRRTE- and PMIx-related MCA parameters it finds there. This + is a convenient way to keep the PRRTE and PMIx settings for a + given Open MPI installation alongside the Open MPI settings in + a single file. Values already set in the environment take + precedence and are not overwritten. + ///////////////////////////////////////////////////////////////////////// .. _label-running-displaying-mca-param-values: @@ -463,7 +469,7 @@ instead of being printed to stdout. For example: .. code-block:: sh shell$ mpirun --mca mpi_show_mca_params enviro \ - --mca mpi_show_mca_param_file /tmp/foo.txt hello_c + --mca mpi_show_mca_params_file /tmp/foo.txt hello_c Hello, World, I am 0 of 1 shell$ cat /tmp/foo.txt # @@ -667,23 +673,31 @@ MCA Parameter Changes Between Open MPI 4.x and newer releases When Open MPI :ref:`switched from using ORTE to PRRTE as its run-time environment, ` some MCA parameters were renamed to be more consistent and/or allow more -flexible behavior. The deprecated Open MPI MCA parameters listed -below are currently replaced by a corresponding new PRRTE parameter, -but may be removed in future releases. - -.. note:: In all cases listed below, the deprecated MCA parameter is - an Open MPI MCA parameter, meaning that its corresponding - environment variable was prefixed with ``OMPI_MCA_`` (e.g., - ``OMPI_MCA_orte_xml_output``). However, the corresponding - new MCA parameter is a PRRTE MCA parameter, meaning that its - corresponding environment variable is prefixed with - ``PRTE_MCA_`` (e.g., ``PRTE_MCA_output``). +flexible behavior. The Open MPI (ORTE-era) MCA parameters in the left column below no +longer exist; use the corresponding replacement shown in the right +column. + +.. note:: The old MCA parameters in the left column were Open MPI + parameters, meaning that their corresponding environment + variables were prefixed with ``OMPI_MCA_`` (e.g., + ``OMPI_MCA_orte_xml_output``). Most of their replacements are + PRRTE parameters, meaning that their corresponding environment + variables are prefixed with ``PRTE_MCA_`` (e.g., + ``PRTE_MCA_output``). The one exception is + ``ompi_stream_buffering``, which remains an Open MPI parameter + (``OMPI_MCA_ompi_stream_buffering``). .. important:: Yes, that's a single ``R`` in the ``PRTE_MCA_`` environment variable prefix. `See this explanation - `_ for the when one - R or two R's are used in the PRRTE name. + `_ for when one R or + two R's are used in the PRRTE name. + +.. note:: ``mapby`` and ``bindto`` are the current names for these two + parameters. The older ``rmaps_default_mapping_policy`` and + ``hwloc_default_binding_policy`` names still work as deprecated + synonyms, but new configurations should use ``mapby`` and + ``bindto``. .. list-table:: @@ -788,7 +802,7 @@ but may be removed in future releases. - ``rmaps_base_no_schedule_local`` Values: boolean - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``[]:nolocal`` @@ -797,7 +811,7 @@ but may be removed in future releases. - ``rmaps_base_oversubscribe`` Values: boolean - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``[]:oversubscribe`` @@ -806,7 +820,7 @@ but may be removed in future releases. - ``rmaps_base_no_oversubscribe`` Values: boolean - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``[]:nooversubscribe`` @@ -814,7 +828,7 @@ but may be removed in future releases. - ``hwloc_base_use_hwthreads_as_cpus`` Values: boolean - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``[]:hwtcpus`` @@ -823,23 +837,23 @@ but may be removed in future releases. - ``hwloc_base_cpu_set`` Value: ```` - - ``rmaps_default_mapping_policy`` + - ``hwloc_default_cpu_list`` - Value: ``pe-list=`` + Value: ```` * - List of processor IDs to bind processes to - ``hwloc_base_cpu_list`` Value: ```` - - ``rmaps_default_mapping_policy`` + - ``hwloc_default_cpu_list`` - Value: ``pe-list=`` + Value: ```` * - Bind processes to cores - ``hwloc_base_bind_to_core`` Values: boolean - - ``hwloc_default_binding_policy`` + - ``bindto`` Value: ``core`` @@ -847,7 +861,7 @@ but may be removed in future releases. - ``hwloc_base_bind_to_socket`` Values: boolean - - ``hwloc_default_binding_policy`` + - ``bindto`` Value: ``package`` @@ -855,7 +869,7 @@ but may be removed in future releases. - ``rmaps_base_bynode`` Values: boolean - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``node`` @@ -863,7 +877,7 @@ but may be removed in future releases. - ``rmaps_base_bycore`` Values: boolean - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``core`` @@ -871,7 +885,7 @@ but may be removed in future releases. - ``rmaps_base_byslot`` Values: boolean - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``slot`` @@ -879,7 +893,7 @@ but may be removed in future releases. - ``rmaps_base_cpus_per_rank`` Value: ```` - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``[]:pe=`` @@ -887,7 +901,7 @@ but may be removed in future releases. - ``rmaps_ppr_n_pernode`` Value: ```` - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``ppr::node`` @@ -895,7 +909,7 @@ but may be removed in future releases. - ``rmaps_ppr_pernode`` Values: boolean - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``ppr:1:node`` @@ -903,7 +917,7 @@ but may be removed in future releases. - ``rmaps_ppr_n_persocket`` Value: integer ```` - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``ppr::package`` @@ -912,7 +926,7 @@ but may be removed in future releases. - ``rmaps_ppr_pattern`` Value: ```` - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``ppr:`` @@ -920,7 +934,7 @@ but may be removed in future releases. - ``orte_rankfile`` Value: ```` - - ``rmaps_default_mapping_policy`` + - ``mapby`` Value: ``rankfile:file=`` @@ -942,7 +956,7 @@ Simple values, where only the name of the MCA parameter changed export OMPI_MCA_orte_ess_base_stream_buffering=2 # New environment variable: (integer value) - export PRTE_MCA_ompi_stream_buffering=2 + export OMPI_MCA_ompi_stream_buffering=2 .. code-block:: ini @@ -1037,7 +1051,7 @@ Converting mapping parameters Mapping parameters were previously prefixed with ``rmaps_base_`` or ``hwloc_base_`` (and also the ``orte_rankfile`` parameter). These have been updated -to the ``rmaps_default_mapping_policy`` and ``hwloc_default_binding_policy`` +to the ``mapby`` and ``bindto`` parameters to be more consistent and indicate that they are the *default* mapping for processes. Some of the old parameters are now values for a new parameter and some are now suffixes, as shown in the examples below. @@ -1051,7 +1065,7 @@ parameter values: export OMPI_MCA_rmaps_base_bycore=1 # New environment variable: (parameter value) - export PRTE_MCA_rmaps_default_mapping_policy=core + export PRTE_MCA_mapby=core .. code-block:: ini @@ -1059,7 +1073,7 @@ parameter values: export OMPI_MCA_hwloc_base_bind_to_socket=1 # New environment variable: (parameter value) - export PRTE_MCA_hwloc_default_binding_policy=package + export PRTE_MCA_bindto=package The examples below show conversions from old parameters that have integer or @@ -1071,7 +1085,7 @@ string values to new parameter values with those same values: export OMPI_MCA_hwloc_base_cpu_set=1,3,8 # New environment variable: (parameter value) - export PRTE_MCA_rmaps_default_mapping_policy=pe-list=1,3,8 + export PRTE_MCA_hwloc_default_cpu_list=1,3,8 .. code-block:: ini @@ -1079,7 +1093,7 @@ string values to new parameter values with those same values: export OMPI_MCA_rmaps_ppr_n_persocket=4 # New environment variable: (parameter value) - export PRTE_MCA_rmaps_default_mapping_policy=ppr:4:package + export PRTE_MCA_mapby=ppr:4:package .. code-block:: ini @@ -1087,7 +1101,7 @@ string values to new parameter values with those same values: export OMPI_MCA_orte_rankfile=rankfile.txt # New environment variable: (parameter value) - export PRTE_MCA_rmaps_default_mapping_policy=rankfile:file=rankfile.txt + export PRTE_MCA_mapby=rankfile:file=rankfile.txt The examples below show conversions from old parameters that map to suffixes for new parameter values: @@ -1098,7 +1112,7 @@ for new parameter values: export OMPI_MCA_hwloc_base_use_hwthreads_as_cpus=1 # New environment variable: (standalone suffix) - export PRTE_MCA_rmaps_default_mapping_policy=:hwtcpus + export PRTE_MCA_mapby=:hwtcpus .. code-block:: ini @@ -1106,7 +1120,7 @@ for new parameter values: export OMPI_MCA_rmaps_base_oversubscribe=1 # New environment variable: (standalone suffix) - export PRTE_MCA_rmaps_default_mapping_policy=:oversubscribe + export PRTE_MCA_mapby=:oversubscribe The examples below show conversions from old parameters that map to suffixes combined with parameters that have values: @@ -1119,8 +1133,29 @@ combined with parameters that have values: # Old environment variable: (boolean value) export OMPI_MCA_rmaps_base_oversubscribe=1 - # New environment variable: (suffix on value) - export PRTE_MCA_rmaps_default_mapping_policy=pe-list=1,3,8:oversubscribe + # New environment variables: (separate parameters) + export PRTE_MCA_hwloc_default_cpu_list=1,3,8 + export PRTE_MCA_mapby=:oversubscribe + +.. note:: ``hwloc_default_cpu_list`` restricts the set of CPUs that + PRRTE may use; it does not bind each process to all of the + listed CPUs. With the default mapping and binding policies, + the example above therefore places one process on CPU 1, one + on CPU 3, and one on CPU 8, each bound to just that one CPU. + + To bind each process to *all* of the listed CPUs, use the + ``pe-list`` mapping directive instead: + + .. code-block:: sh + + shell$ mpirun --map-by pe-list=1,3,8:oversubscribe ... + + Adding the ``ordered`` qualifier (``--map-by + pe-list=1,3,8:ordered:oversubscribe``) yields the + one-process-per-listed-CPU placement described above. Note + that ``pe-list`` is only accepted on the command line; PRRTE + rejects it as a default mapping policy (i.e., as the value + of the ``mapby`` MCA parameter). .. code-block:: ini @@ -1131,7 +1166,7 @@ combined with parameters that have values: export OMPI_MCA_hwloc_base_use_hwthreads_as_cpus=1 # New environment variable: (suffix on value) - export PRTE_MCA_rmaps_default_mapping_policy=ppr:4:package:hwtcpus + export PRTE_MCA_mapby=ppr:4:package:hwtcpus Multiple suffixes may be appended to a mapping value: @@ -1145,4 +1180,4 @@ Multiple suffixes may be appended to a mapping value: export OMPI_MCA_rmaps_base_oversubscribe=1 # New environment variable: (suffix on value) - export PRTE_MCA_rmaps_default_mapping_policy=ppr:4:package:hwtcpus:oversubscribe + export PRTE_MCA_mapby=ppr:4:package:hwtcpus:oversubscribe diff --git a/docs/ompi_docs_common.py b/docs/ompi_docs_common.py new file mode 100644 index 00000000000..4f8b8650113 --- /dev/null +++ b/docs/ompi_docs_common.py @@ -0,0 +1,305 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# + +"""Shared MPI documentation metadata helpers. + +This module factors out logic that is common to the Open MPI +documentation generators: + +* ``docs/generate-mpi-man3-bindings.py`` (the human man-page bindings), and +* ``docs/generate-llm-docs.py`` (the LLM-friendly artifacts). + +It provides: + +* parsing of the top-level ``VERSION`` file (without importing + ``docs/conf.py``, which would trigger Sphinx configuration side effects); +* loading of the embedded ``pympistandard`` library plus + ``docs/mpi-standard-apis.json``; +* parsing of the ``.. mpi-bindings:`` man-page directives that mark pages + documenting multiple MPI procedures; +* rendering of the C (``iso_c``), fixed-form Fortran (``f90``), and + ``use mpi_f08`` (``f08``) binding strings, including the large-count + ("embiggened") variants; and +* build-identity helpers (git commit / ``git describe``) and a + ``SOURCE_DATE_EPOCH``-aware UTC timestamp. + +All code here must remain compatible with Open MPI's supported Python +version floor (see ``python_min_version`` in the top-level ``VERSION`` +file): Python 3.6. +""" + +import os +import re +import subprocess +import sys + +from datetime import datetime, timezone +from pathlib import Path + + +# --------------------------------------------------------------------------- +# VERSION file parsing +# --------------------------------------------------------------------------- + +def read_version_file(path): + """Parse an Open MPI-style VERSION file into a dict. + + Uses the same rules as ``docs/conf.py`` (strip ``#`` comments, keep + ``key=value`` lines), but does not import ``conf.py`` so that no Sphinx + configuration side effects are triggered. + """ + if not os.path.exists(path): + raise FileNotFoundError("Unable to find VERSION file {}".format(path)) + + data = {} + with open(path, encoding='utf-8') as fp: + for line in fp.readlines(): + if '#' in line: + line = line.split("#")[0] + line = line.strip() + if '=' not in line: + continue + key, val = line.split("=", 1) + data[key.strip()] = val.strip() + return data + + +def ompi_version_info(top_srcdir): + """Return a dict of assembled Open MPI version strings. + + ``top_srcdir`` is the top of the Open MPI source tree (the directory + that contains the ``VERSION`` file). The returned dict includes: + + * ``ompi_version``: e.g. ``v6.1.0a1`` + * ``ompi_series``: e.g. ``v6.1.x`` + * ``mpi_standard_version``: e.g. ``4.1`` + """ + data = read_version_file(os.path.join(top_srcdir, "VERSION")) + major = data["major"] + minor = data["minor"] + release = data["release"] + greek = data.get("greek", "") + info = { + "major": major, + "minor": minor, + "release": release, + "greek": greek, + "ompi_version": "v{}.{}.{}{}".format(major, minor, release, greek), + "ompi_series": "v{}.{}.x".format(major, minor), + } + if "mpi_standard_version" in data and "mpi_standard_subversion" in data: + info["mpi_standard_version"] = "{}.{}".format( + data["mpi_standard_version"], data["mpi_standard_subversion"]) + info["mpi_standard_major_version"] = data["mpi_standard_version"] + info["mpi_standard_minor_version"] = data["mpi_standard_subversion"] + info["python_min_version"] = data.get("python_min_version") + return info + + +# --------------------------------------------------------------------------- +# pympistandard loading +# --------------------------------------------------------------------------- + +def load_pympistandard(docs_srcdir): + """Import and initialize the embedded ``pympistandard`` library. + + ``docs_srcdir`` is the ``docs/`` source directory. The ``pympistandard`` + package lives in the Open MPI ``3rd-party`` tree, and the MPI Standard + API JSON ships as ``docs/mpi-standard-apis.json`` (see + ``generate-mpi-man3-bindings.py`` for provenance). + + Returns the imported ``pympistandard`` module, already initialized with + ``use_api_version(1, ...)``. + """ + docs_srcdir = os.path.abspath(docs_srcdir) + top_srcdir = os.path.dirname(docs_srcdir) + + pympistandard_dir = Path(os.path.join( + top_srcdir, "3rd-party", "pympistandard", "src")).resolve() + sys.path.insert(0, str(pympistandard_dir)) + + import pympistandard as std + + mpi_standard_json = os.path.join(docs_srcdir, "mpi-standard-apis.json") + std.use_api_version(1, given_path=mpi_standard_json) + return std + + +# --------------------------------------------------------------------------- +# .. mpi-bindings: directive parsing +# --------------------------------------------------------------------------- + +def read_rst_man_pages(src_dir): + """Scan ``src_dir/man-openmpi/man3`` for ``.. mpi-bindings:`` directives. + + Some ``MPI_*.3.rst`` man pages document several MPI procedures via a + ``.. mpi-bindings: MPI_Foo, MPI_Bar`` comment line (a single-colon + comment, not a registered Sphinx directive). Returns a dict mapping each + man-page API name (lowercased, without ``.3.rst``) to the list of + lowercased procedure names documented on that page. + """ + directives = {} + prog = re.compile(r'^MPI_.*\.3\.rst$') + + man3_dir = Path(os.path.join(src_dir, 'man-openmpi', 'man3')).resolve() + for file in os.listdir(man3_dir): + if not prog.match(file): + continue + + with open(os.path.join(man3_dir, file), encoding='utf-8') as fp: + lines = fp.readlines() + + file_api_name = file.replace('.3.rst', '').lower() + directives[file_api_name] = list() + + prefix = '.. mpi-bindings:' + for line in lines: + line = line.strip() + if not line.startswith(prefix): + continue + + bindings = line[len(prefix):].split(',') + for binding in bindings: + binding = binding.strip() + directives[file_api_name].append(binding.lower()) + + return directives + + +# --------------------------------------------------------------------------- +# Binding string rendering +# --------------------------------------------------------------------------- + +def _valid_binding(binding): + """An express binding is usable iff it is a non-empty, non-'None' string.""" + return binding is not None and len(binding) > 0 and binding != 'None' + + +def c_binding(data): + """Return the C (``iso_c``) binding string for a procedure, or ``None``. + + Applies the workaround for + https://github.com/mpi-forum/pympistandard/issues/25, where + ``MPI_Pcontrol`` renders ``\\ldots`` instead of ``...``. + """ + binding = str(data.express.iso_c) + if not _valid_binding(binding): + return None + return binding.replace(r'\ldots', '...') + + +def c_binding_large(data): + """Return the large-count C binding string, or ``None`` if none exists.""" + if not data.has_embiggenment(): + return None + binding = str(data.express.embiggen.iso_c) + if not _valid_binding(binding): + return None + return binding.replace(r'\ldots', '...') + + +def f90_binding(data): + """Return the fixed-form Fortran (``f90``) binding string, or ``None``. + + The same string is used for both the ``mpif.h`` and ``use mpi`` + interfaces; they differ only in how the interface is accessed + (``INCLUDE 'mpif.h'`` versus ``USE mpi``), not in the signature. + """ + binding = str(data.express.f90) + if not _valid_binding(binding): + return None + return binding + + +def f08_binding(data): + """Return the ``use mpi_f08`` (``f08``) binding string, or ``None``.""" + binding = str(data.express.f08) + if not _valid_binding(binding): + return None + return binding + + +def f08_binding_large(data): + """Return the large-count ``f08`` binding string, or ``None``.""" + if not data.has_embiggenment(): + return None + binding = str(data.express.embiggen.f08) + if not _valid_binding(binding): + return None + return binding + + +# --------------------------------------------------------------------------- +# Build identity / reproducible timestamp +# --------------------------------------------------------------------------- + +def _git(top_srcdir, args): + """Run a git command in ``top_srcdir``; return stripped stdout or None.""" + try: + out = subprocess.check_output( + ['git'] + args, cwd=top_srcdir, stderr=subprocess.DEVNULL) + except (OSError, subprocess.CalledProcessError): + return None + out = out.decode('utf-8', 'replace').strip() + return out if out else None + + +def git_commit(top_srcdir): + """Return the full source commit hash, or ``None`` if unavailable.""" + return _git(top_srcdir, ['rev-parse', 'HEAD']) + + +def git_describe(top_srcdir): + """Return ``git describe --tags --always`` output, or ``None``. + + Mirrors how ``config/opal_get_version.sh`` resolves the repo revision. + """ + return _git(top_srcdir, ['describe', '--tags', '--always']) + + +def source_date_epoch(): + """Return the reproducible build epoch (int seconds) honored project-wide. + + Honors ``SOURCE_DATE_EPOCH`` (see https://reproducible-builds.org/ and + ``config/getdate.sh``); returns ``None`` if it is unset or unparsable so + the caller can fall back gracefully (e.g. to a git commit date). + """ + raw = os.environ.get('SOURCE_DATE_EPOCH') + if raw is None: + return None + try: + return int(raw) + except ValueError: + return None + + +def generated_at_utc(top_srcdir=None): + """Return an ISO-8601 UTC timestamp string for artifact generation. + + Resolution order, mirroring the rest of the build: + + 1. ``SOURCE_DATE_EPOCH`` if set (reproducible builds); + 2. the build commit's committer date, if a git checkout is available; + 3. the current wall-clock time. + """ + epoch = source_date_epoch() + if epoch is None and top_srcdir is not None: + commit_epoch = _git(top_srcdir, ['log', '-1', '--format=%ct']) + if commit_epoch is not None: + try: + epoch = int(commit_epoch) + except ValueError: + epoch = None + if epoch is not None: + dt = datetime.fromtimestamp(epoch, tz=timezone.utc) + else: + dt = datetime.now(tz=timezone.utc) + return dt.strftime('%Y-%m-%dT%H:%M:%SZ') diff --git a/docs/release-notes/changelog/index.rst b/docs/release-notes/changelog/index.rst index 18dbd073f8d..420b55c91c5 100644 --- a/docs/release-notes/changelog/index.rst +++ b/docs/release-notes/changelog/index.rst @@ -8,6 +8,7 @@ since v1.0.0. .. toctree:: :maxdepth: 1 + v6.1.x v6.0.x v5.0.x v4.1.x diff --git a/docs/release-notes/changelog/v1.x.rst b/docs/release-notes/changelog/v1.x.rst index b945177598c..a6180d0a1d3 100644 --- a/docs/release-notes/changelog/v1.x.rst +++ b/docs/release-notes/changelog/v1.x.rst @@ -14,7 +14,7 @@ Open MPI version 1.10.7 - Fix bug in TCP BTL that impacted performance on 10GbE (and faster) networks by not adjusting the TCP send/recv buffer sizes and using system default values -- Add missing MPI_AINT_ADD and MPI_AINT_DIFF function delcarations in +- Add missing MPI_AINT_ADD and MPI_AINT_DIFF function declarations in mpif.h - Fixed time reported by MPI_WTIME; it was previously reported as dependent upon the CPU frequency. @@ -340,7 +340,7 @@ Open MPI version 1.10.0 - Fixed a variety of small bugs in OpenSHMEM. - Fixed MXM configure with additional CPPFLAGS and LDFLAGS. Thanks to David Shrader for the patch. -- Fixed incorrect memalign threshhold in the openib BTL. Thanks to +- Fixed incorrect memalign threshold in the openib BTL. Thanks to Xavier Besseron for pointing out the issue. @@ -503,7 +503,7 @@ Open MPI version 1.8.4 - Fix MPI_SIZEOF; now available in mpif.h for modern Fortran compilers (see README for more details). Also fixed various compiler/linker errors. -- Fixed inadvertant Fortran ABI break between v1.8.1 and v1.8.2 in the +- Fixed inadvertent Fortran ABI break between v1.8.1 and v1.8.2 in the mpi interface module when compiled with gfortran >= v4.9. - Fix various MPI_THREAD_MULTIPLE issues in the TCP BTL. - mpirun no longer requires the ``--hetero-nodes`` switch; it will @@ -694,7 +694,7 @@ Open MPI version 1.7.5 - Fix MPI_GRAPH_CREATE when nnodes is smaller than the size of the old communicator. - usnic BTL now supports underlying UDP transport. -- usnic BTL now checks for common connectivty errors at first send to +- usnic BTL now checks for common connectivity errors at first send to a remote server. - Minor scalability improvements in the usnic BTL. - ompi_info now lists whether the Java MPI bindings are available or not. @@ -782,7 +782,7 @@ Open MPI version 1.7.4 - Prevent integer overflow when creating datatypes. Thanks to original patch from Gilles Gouaillardet. - Port some upstream hwloc fixes to Open MPI's embedded copy for - working around buggy NUMA node cpusets and including mising header + working around buggy NUMA node cpusets and including missing header files. Thanks to Jeff Becker and Paul Hargrove for reporting the issues. - Fix recursive invocation issues in the MXM MTL. @@ -1060,7 +1060,7 @@ Open MPI version 1.7.0 MCA params). - Upgraded to hwloc v1.5.1. - Added performance improvements to the OpenIB (OpenFabrics) BTL. -- Made malloc hooks more friendly to IO interprosers. Thanks to the +- Made malloc hooks more friendly to IO interposers. Thanks to the bug report and suggested fix from Darshan maintainer Phil Carns. - Added support for the DMTCP checkpoint/restart system. - Added support for the Cray uGNI interconnect. @@ -1217,7 +1217,7 @@ Open MPI version 1.6.4 - Improved error message when process affinity fails. - Fixed MPI_MINLOC on man pages for MPI_REDUCE(_LOCAL). Thanks to Jed Brown for noticing the problem and supplying a fix. -- Made malloc hooks more friendly to IO interprosers. Thanks to the +- Made malloc hooks more friendly to IO interposers. Thanks to the bug report and suggested fix from Darshan maintainer Phil Carns. - Restored ability to direct launch under SLURM without PMI support. - Fixed MPI datatype issues on OpenBSD. @@ -1296,7 +1296,7 @@ Open MPI version 1.6.1 https://www.open-mpi.org/faq/?category=openfabrics#ib-low-reg-mem - Fall back to send/receive semantics if registered memory is - unavilable for RDMA. + unavailable for RDMA. - Fix two fragment leaks when registered memory is exhausted. - Hueristically determine how much registered memory is available and warn if it's significantly less than all of RAM. @@ -1317,7 +1317,7 @@ Open MPI version 1.6.1 OpenFabrics devices. - Lots of VampirTrace fixes; upgrade to v5.13.0.4. - Map MPI_2INTEGER to underlying MPI_INTEGERs, not MPI_INTs. -- Ensure that the OMPI version number is toleant of handling spaces. +- Ensure that the OMPI version number is tolerant of handling spaces. Thanks to dragonboy for identifying the issue. - Fixed IN parameter marking on Fortran "mpi" module MPI_COMM_TEST_INTER interface. @@ -1810,7 +1810,7 @@ Open MPI version 1.4.4 to Avinash Malik for reporting the issue. - Fix for correctly handling multi-token args when using debuggers. - Eliminated the unneeded ``u_int*_t`` datatype definitions. -- Change in ORTE DPM to get around gcc 4.[45].x compiler wanrings +- Change in ORTE DPM to get around gcc 4.[45].x compiler warnings about possibly calling free() on a non-heap variable, even though it will never happen because the refcount will never go to zero. - Fixed incorrect text in MPI_File_set_view man page. @@ -1889,7 +1889,7 @@ Open MPI version 1.4.3 - Change to ensure TotalView works properly on Darwin. - Added support for Visual Studio 2010. - Fix to ensure proper placement of VampirTrace header files. -- Needed to add volatile keyword to a varialbe used in debugging +- Needed to add volatile keyword to a variable used in debugging (MPIR_being_debugged). - Fixed a bug in inter-allgather. - Fixed malloc(0) warnings. @@ -2275,7 +2275,7 @@ Open MPI version 1.3.0 - Added ``btl_openib_if_[in|ex]clude`` MCA parameters for including/excluding comma-delimited lists of HCAs and ports. - - Added RDMA CM support, includng ``btl_openib_cpc_[in|ex]clude`` + - Added RDMA CM support, including ``btl_openib_cpc_[in|ex]clude`` MCA parameters - Added NUMA support to only use "near" network adapters - Added "Bucket SRQ" (BSRQ) support to better utilize registered @@ -2283,7 +2283,7 @@ Open MPI version 1.3.0 - Added ConnectX XRC support (and integrated with BSRQ) - Added btl_openib_ib_max_inline_data MCA parameter - Added iWARP support - - Revamped flow control mechansisms to be more efficient + - Revamped flow control mechanisms to be more efficient - ``mpi_leave_pinned=1`` is now the default when possible, automatically improving performance for large messages when application buffers are re-used @@ -2305,7 +2305,7 @@ Open MPI version 1.3.0 predefined datatypes in the fortran header files, there will not be any compatibility issues. - Added Portable Linux Processor Affinity (PLPA) for Linux. -- Addition of a finer symbols export control via the visibiliy feature +- Addition of a finer symbols export control via the visibility feature offered by some compilers. - Added checkpoint/restart process fault tolerance support. Initially support a LAM/MPI-like protocol. @@ -2318,7 +2318,7 @@ Open MPI version 1.3.0 use leave_pinned with ptmalloc2 will now need to link the library into their application explicitly. All other users will use the libc-provided allocator instead of Open MPI's ptmalloc2. This change - may be overriden with the configure option enable-ptmalloc2-internal + may be overridden with the configure option enable-ptmalloc2-internal - The leave_pinned options will now default to using mallopt on Linux in the cases where ptmalloc2 was not linked in. mallopt will also only be available if munmap can be intercepted (the @@ -2355,7 +2355,7 @@ Open MPI version 1.2.9 - Fix the ``--enable-cxx-exceptions`` configure option. See ticket #1607. - Properly handle when the MX BTL cannot open an endpoint. See ticket #1621. - Fix a double free of events on the tcp_events list. See ticket #1631. -- Fix a buffer overun in opal_free_list_grow (called by MPI_Init). +- Fix a buffer overrun in opal_free_list_grow (called by MPI_Init). Thanks to Patrick Farrell for the bugreport and Stephan Kramer for the bugfix. See ticket #1583. - Fix a problem setting OPAL_PREFIX for remote sh-based shells. @@ -2824,7 +2824,7 @@ Open MPI version 1.1.2 - Fix receiving messages to buffers allocated by MPI_ALLOC_MEM. - Fix a number of race conditions with the MPI-2 Onesided interface. -- Fix the "tuned" collective componenete where some cases where +- Fix the "tuned" collective component where some cases where MPI_BCAST could hang. - Update TCP support to support non-uniform TCP environments. - Allow the "poe" RAS component to be built on AIX or Linux. @@ -2841,7 +2841,7 @@ Open MPI version 1.1.1 - Fix for Fortran string handling in various MPI API functions. - Fix for Fortran status handling in MPI_WAITSOME and MPI_TESTSOME. - Various fixes for the XL compilers. -- Automatically disable using mallot() on AIX. +- Automatically disable using mallopt() on AIX. - Memory fixes for 64 bit platforms with registering MCA parameters in the self and MX BTL components. - Fixes for BProc to support oversubscription and changes to the @@ -2919,7 +2919,7 @@ Open MPI version 1.1.0 match. Thanks to Michael Kluskens for pointing out the problems to us. -- Allow short messagees to use RDMA (vs. send/receive semantics) to a +- Allow short messages to use RDMA (vs. send/receive semantics) to a limited number peers in both the mvapi and openib BTL components. This reduces communication latency over IB channels. - Numerous performance improvements throughout the entire code base. @@ -3143,7 +3143,7 @@ Open MPI version 1.0.1 MPI_SCATTERV implementation. - Fix EOF handling on stdin. - Fix missing MPI_F_STATUS_IGNORE and MPI_F_STATUSES_IGNORE - instanatiations. Thanks to Anthony Chan for pointing this out. + instantiations. Thanks to Anthony Chan for pointing this out. - Add a missing value for MPI_WIN_NULL in mpif.h. - Bring over some fixes for the sm btl that somehow didn't make it over from the trunk before v1.0. Thanks to Beth Tibbitts and Bill diff --git a/docs/release-notes/changelog/v2.x.rst b/docs/release-notes/changelog/v2.x.rst index 896055a43b6..13995071739 100644 --- a/docs/release-notes/changelog/v2.x.rst +++ b/docs/release-notes/changelog/v2.x.rst @@ -220,8 +220,8 @@ Open MPI version 2.1.0 .. attention:: Removed legacy support: - The ptmalloc2 hooks have been removed from the Open MPI code base. - This is not really a user-noticable change; it is only mentioned - here because there was much rejoycing in the Open MPI developer + This is not really a user-noticeable change; it is only mentioned + here because there was much rejoicing in the Open MPI developer community. - New MCA parameters: @@ -240,7 +240,7 @@ Open MPI version 2.1.0 libfabric progress model to be used for control and data. - Fix MPI_WTICK regression where the time reported may be inaccurate - on systems with processor frequency scalaing enabled. + on systems with processor frequency scaling enabled. - Fix regression that lowered the memory maximum message bandwidth for large messages on some BTL network transports, such as openib, sm, and vader. @@ -305,7 +305,7 @@ Open MPI version 2.1.0 "test". Thanks to Kevin Buckley for pointing out the issue. - Fix bug when using darrays with lib and extent of darray datatypes. - Updates to make Open MPI binary builds more bit-for-bit - reproducable. Thanks to Alastair McKinstry for the suggestion. + reproducible. Thanks to Alastair McKinstry for the suggestion. - Fix issues regarding persistent request handling. - Ensure that shmemx.h is a standalone OpenSHMEM header file. Thanks to Nick Park (@nspark) for the report. @@ -527,11 +527,11 @@ Open MPI version 2.0.1 all transports. - Fix shared memory performance when using RDMA-capable networks. Thanks to Tetsuya Mishima and Christoph Niethammer for reporting. -- Fix bandwith performance degredation in the yalla (MXM) PML. Thanks +- Fix bandwidth performance degradation in the yalla (MXM) PML. Thanks to Andreas Kempf for reporting the issue. - Fix OpenSHMEM crash when running on non-Mellanox MXM-based networks. Thanks to Debendra Das for reporting the issue. -- Fix a crash occuring after repeated calls to MPI_FILE_SET_VIEW with +- Fix a crash occurring after repeated calls to MPI_FILE_SET_VIEW with predefined datatypes. Thanks to Eric Chamberland and Matthew Knepley for reporting and helping chase down this issue. - Fix stdin propagation to MPI processes. Thanks to Jingchao Zhang @@ -540,7 +540,7 @@ Open MPI version 2.0.1 internal component to v1.1.5. - Fix process startup failures on Intel MIC platforms due to very large entries in ``/proc/mounts``. -- Fix a problem with use of relative path for specifing executables to +- Fix a problem with use of relative path for specifying executables to mpirun / oshrun. Thanks to David Schneider for reporting. - Various improvements when running over portals-based networks. - Fix thread-based race conditions with GNI-based networks. @@ -641,7 +641,7 @@ Open MPI version 2.0.0 - ompi-release#1081: Support MPI_IN_PLACE in MPI_(I)ALLTOALLW and MPI_(I)EXSCAN - ompi-release#1107: Allow future PMIx support for RM spawn limits - ompi-release#1108: Fix sparse group process reference counting - - ompi-release#1109: If specified to be oversubcribed, disable binding + - ompi-release#1109: If specified to be oversubscribed, disable binding - ompi-release#1122: Allow NULL arrays for empty datatypes - ompi-release#1123: Fix signed vs. unsigned compiler warnings - ompi-release#1123: Make max hostname length uniform across code base @@ -748,7 +748,7 @@ Open MPI version 2.0.0 - Allow NULL arrays when creating empty MPI datatypes. - Replace use of alloca with malloc for certain datatype creation functions. Thanks to Bogdan Sataric for reporting this. -- Fix use of MPI_LB and MPI_UB in creation of of certain MPI datatypes. +- Fix use of MPI_LB and MPI_UB in creation of certain MPI datatypes. Thanks to Gus Correa for helping to fix this. - Implement a workaround for a GNU Libtool problem. Thanks to Eric Schnetter for reporting and fixing. diff --git a/docs/release-notes/changelog/v3.0.x.rst b/docs/release-notes/changelog/v3.0.x.rst index 27578071121..f76b4c97b42 100644 --- a/docs/release-notes/changelog/v3.0.x.rst +++ b/docs/release-notes/changelog/v3.0.x.rst @@ -63,7 +63,7 @@ Open MPI version 3.0.5 - Add support for unwinding info to all files that are present in the stack starting from ``MPI_Init``, which is helpful with parallel debuggers. Thanks to James Clark for the report and initial fix. -- Fixed inadvertant use of bitwise operators in the MPI C++ bindings +- Fixed inadvertent use of bitwise operators in the MPI C++ bindings header files. Thanks to Bert Wesarg for the report and the fix. - Added configure option ``--disable-wrappers-runpath`` (alongside the already-existing ``--disable-wrappers-rpath`` option) to prevent Open @@ -79,7 +79,7 @@ Open MPI version 3.0.4 ``--with-devel-headers``. Thanks to @g-raffy for reporting the issue. - Fix possible floating point rounding and division issues in OMPIO which led to crashes and/or data corruption with very large data. - Thanks to Axel Huebl and RenÊ Widera for identifing the issue, + Thanks to Axel Huebl and RenÊ Widera for identifying the issue, supplying and testing the fix (** also appeared: v3.0.4). - Use ``static_cast<>`` in ``mpi.h`` where appropriate. Thanks to @shadow-fx for identifying the issue. @@ -152,7 +152,7 @@ Open MPI version 3.0.2 rather than those documented in the MPI standard. - Fixed ``MPI_SIZEOF`` in the "mpi" Fortran module for the NAG compiler. - Fix RMA function signatures for ``use-mpi-f08`` bindings to have the - asynchonous property on all buffers. + asynchronous property on all buffers. - Fix Fortran ``MPI_COMM_SPAWN_MULTIPLE`` to properly follow the count length argument when parsing the array_of_commands variable. - Revamp Java detection to properly handle new Java versions which do @@ -197,9 +197,9 @@ Open MPI version 3.0.1 propagation tools. By default it is set to false, except for Cray XC systems. - Fix a problem reported on the mailing separately by Kevin McGrattan and Stephen Guzik about consistency issues on NFS file systems when using OMPIO. This fix - also introduces a new mca parameter ``fs_ufs_lock_algorithm`` which allows to + also introduces a new mca parameter ``fs_ufs_lock_algorithm`` which allows users to control the locking algorithm used by ompio for read/write operations. By - default, ompio does not perfom locking on local UNIX file systems, locks the + default, ompio does not perform locking on local UNIX file systems, locks the entire file per operation on NFS file systems, and selective byte-range locking on other distributed file systems. - Add an mca parameter ``pmix_server_usock_connections`` to allow mpirun to diff --git a/docs/release-notes/changelog/v3.1.x.rst b/docs/release-notes/changelog/v3.1.x.rst index 68502fce637..6a6e5371b32 100644 --- a/docs/release-notes/changelog/v3.1.x.rst +++ b/docs/release-notes/changelog/v3.1.x.rst @@ -78,7 +78,7 @@ Open MPI version 3.1.5 - Add support for unwinding info to all files that are present in the stack starting from MPI_Init, which is helpful with parallel debuggers. Thanks to James Clark for the report and initial fix. -- Fixed inadvertant use of bitwise operators in the MPI C++ bindings +- Fixed inadvertent use of bitwise operators in the MPI C++ bindings header files. Thanks to Bert Wesarg for the report and the fix. @@ -93,7 +93,7 @@ Open MPI version 3.1.4 Easterday for the fix. - Fix possible floating point rounding and division issues in OMPIO which led to crashes and/or data corruption with very large data. - Thanks to Axel Huebl and RenÊ Widera for identifing the issue, + Thanks to Axel Huebl and RenÊ Widera for identifying the issue, supplying and testing the fix (** also appeared: v3.0.4). - Use ``static_cast<>`` in ``mpi.h`` where appropriate. Thanks to @shadow-fx for identifying the issue (** also appeared: v3.0.4). @@ -193,7 +193,7 @@ Open MPI version 3.1.1 - Revamp Java detection to properly handle new Java versions which do not provide a javah wrapper. - Fix RMA function signatures for use-mpi-f08 bindings to have the - asynchonous property on all buffers. + asynchronous property on all buffers. - Improved configure logic for finding the UCX library. diff --git a/docs/release-notes/changelog/v4.0.x.rst b/docs/release-notes/changelog/v4.0.x.rst index 70d452c0619..e7db8a26ac0 100644 --- a/docs/release-notes/changelog/v4.0.x.rst +++ b/docs/release-notes/changelog/v4.0.x.rst @@ -153,7 +153,7 @@ Open MPI version 4.0.2 Thanks to Orivej Desh for reporting and providing a fix. - Fix divide by zero segfault in ompio. Thanks to @haraldkl for reporting and providing a fix. -- Fix finalize of flux compnents. +- Fix finalize of flux components. Thanks to Stephen Herbein and Jim Garlick for providing a fix. - Fix osc_rdma_acc_single_intrinsic regression. Thanks to Joseph Schuchart for reporting and providing a fix. @@ -231,7 +231,7 @@ Open MPI version 4.0.1 Thanks to Igor Andriyash and Axel Huebl for reporting. - Fix two memory leaks encountered for certain MPI-RMA usage patterns. Thanks to Joseph Schuchart for reporting and fixing. -- Fix a problem with the ORTE ``rmaps_base_oversubscribe`` MCA paramater. +- Fix a problem with the ORTE ``rmaps_base_oversubscribe`` MCA parameter. Thanks to @iassiour for reporting. - Fix a problem with UCX PML default error handler for MPI communicators. Thanks to Marcin Krotkiewski for reporting. @@ -277,7 +277,7 @@ Open MPI version 4.0.0 - Fix problems with use of newer map-by mpirun options. Thanks to Tony Reina for reporting. - Fix rank-by algorithms to properly rank by object and span -- Allow for running as root of two environment variables are set. +- Allow for running as root if two environment variables are set. Requested by Axel Huebl. - Fix a problem with building the Java bindings when using Java 10. Thanks to Bryce Glover for reporting. diff --git a/docs/release-notes/changelog/v4.1.x.rst b/docs/release-notes/changelog/v4.1.x.rst index f5540505189..f8d35dd0bee 100644 --- a/docs/release-notes/changelog/v4.1.x.rst +++ b/docs/release-notes/changelog/v4.1.x.rst @@ -31,7 +31,7 @@ Open MPI version 4.1.6 - Fix minor issues and add some minor performance optimizations with OFI support. - Support the ``striping_factor`` and ``striping_unit`` MPI_Info names - recomended by the MPI standard for parallel IO. + recommended by the MPI standard for parallel IO. - Fixed some minor issues with UCX support. - Minor optimization for 0-byte MPI_Alltoallw (i.e., make it a no-op). @@ -119,7 +119,7 @@ Open MPI version 4.1.3 based on float precision. - Fix compile failure for ``--enable-heterogeneous``. Also updated the README to clarify that ``--enable-heterogeneous`` is functional, - but still not recomended for most environments. + but still not recommended for most environments. - Minor fixes to OMPIO, including: - Fixing the open behavior of shared memory shared file pointers. @@ -154,7 +154,7 @@ Open MPI version 4.1.2 - Correctly process 0 slots with the ``mpirun --host`` option. - Ensure to unlink and rebind socket when the Open MPI session directory already exists. -- Fix a segv in ``mpirun --disable-dissable-map``. +- Fix a segv in ``mpirun --disable-display-map``. - Fix a potential hang in the memory hook handling. - Slight performance improvement in ``MPI_WAITALL`` when running in ``MPI_THREAD_MULTIPLE``. diff --git a/docs/release-notes/changelog/v6.0.x.rst b/docs/release-notes/changelog/v6.0.x.rst index 6b3038ee6bc..b797608ea56 100644 --- a/docs/release-notes/changelog/v6.0.x.rst +++ b/docs/release-notes/changelog/v6.0.x.rst @@ -29,6 +29,14 @@ Open MPI version v6.0.0 delivered through the Open MPI internal "OMPIO" implementation (which has been the default for quite a while, anyway). +- Removed the Java MPI bindings and the ``--enable-mpi-java`` + configure option. These bindings were experimental, were never part + of the MPI standard, and are no longer supported. + +- Improved OMPIO ``MPI_File_get_info()`` reporting so it returns + supported file hints that OMPIO is actually using, including + component-owned hints for selected OMPIO subcomponents. + - Added support for MPI-4.1 functions to access and update ``MPI_Status`` fields. @@ -68,3 +76,8 @@ Open MPI version v6.0.0 - Extended the ``accelerator`` collective component to support more collective operations on device buffers. + +- ``MPI_T_category_get_events`` and ``MPI_T_category_get_num_events`` + now validate their ``cat_index`` argument and return + ``MPI_T_ERR_INVALID_INDEX`` for an out-of-range index, consistent + with the other ``MPI_T_category_get_*`` query functions. diff --git a/docs/release-notes/changelog/v6.1.x.rst b/docs/release-notes/changelog/v6.1.x.rst new file mode 100644 index 00000000000..d53681a89f5 --- /dev/null +++ b/docs/release-notes/changelog/v6.1.x.rst @@ -0,0 +1,24 @@ +Open MPI v6.1.x series +====================== + +This file contains all the NEWS updates for the Open MPI v6.1.x +series, in reverse chronological order. + +Open MPI version v6.1.0 +-------------------------- +:Date: ...fill me in... + +- Renamed the ``--enable-weak-symbols`` configure option to + ``--enable-weak-aliases``, which more accurately reflects the linker + feature (weak symbol *aliases*) that Open MPI actually tests for and + uses. ``--enable-weak-symbols`` is retained as a deprecated synonym. + +- The documentation now publishes machine-readable, LLM-friendly + artifacts for the public MPI APIs alongside the human-facing HTML and + man pages: a JSONL API catalog, aggregate and per-interface Markdown + corpora, per-symbol Markdown pages, curated examples, an interface + guide, an ``ompi_info`` runtime-introspection guide (how to query an + installed Open MPI for its version, configuration, components, and + run-time MCA parameters), and a manifest, all indexed from + ``llms.txt``. See the "LLM-friendly documentation artifacts" page in + the developer documentation. diff --git a/docs/release-notes/networks.rst b/docs/release-notes/networks.rst index 400bf21f9cd..cf5f42c4665 100644 --- a/docs/release-notes/networks.rst +++ b/docs/release-notes/networks.rst @@ -23,8 +23,7 @@ There are multiple MPI network models available in this release: utilizing MTL ("Matching Transport Layer") plugins: * OpenFabrics Interfaces ("libfabric" tag matching) - * Intel Omni-Path PSM2 (version 11.2.173 or later) - * Intel True Scale PSM (QLogic InfiniPath) + * Cornelis Networks Omni-Path PSM2 (version 11.2.173 or later) * Portals 4 * ``ucx`` uses the `Unified Communication X (UCX) communication @@ -60,7 +59,7 @@ run-time: .. code-block:: sh - shell$ mpirun --mca pml ob1 --mca btl [comma-delimted-BTLs] ... + shell$ mpirun --mca pml ob1 --mca btl [comma-delimited-BTLs] ... # or shell$ mpirun --mca pml cm --mca mtl [MTL] ... # or diff --git a/docs/release-notes/platform.rst b/docs/release-notes/platform.rst index ec6ead13598..f67d3cc3f9c 100644 --- a/docs/release-notes/platform.rst +++ b/docs/release-notes/platform.rst @@ -3,53 +3,133 @@ Platform Notes ============== -.. error:: **TODO We should have a canonical list of:** - - * *required* 3rd-party package versions supported (PRRTE, hwloc, - libevent) - * back-end run-time systems supported (behind PRRTE) - * OS's and compilers supported - * network interconnects supported. - Open MPI uses both `OpenPMIx `_ and -`PRRTE `_ for its run-time system support, -and therefore supports whatever run-time systems they support. +`PRRTE `_ for its run-time system +support, and therefore supports whatever run-time systems they support. .. note:: See the :ref:`PMIx and PRRTE section ` of the Open MPI documentation for more details about those projects and their relationship to Open MPI. -Each version of Open MPI supports a specific set of versions of -OpenPMIx and PRRTE. Those versions therefore determine which run-time systems -that a release of Open MPI supports. +Required support libraries +-------------------------- + +Open MPI |ompi_ver| depends on the following support libraries. Each +may be supplied by a system installation (if it is recent enough) or +used from the copy embedded in the Open MPI distribution: + +* `OpenPMIx `_: version + |pmix_min_version| or later when building without PRRTE (when + building with PRRTE, PRRTE's own OpenPMIx minimum applies). Open + MPI |ompi_ver| embeds OpenPMIx |pmix_embedded_version|. +* `PRRTE `_: version + |prte_min_version| or later. Open MPI |ompi_ver| embeds PRRTE + |prte_embedded_version|. PRRTE is what provides ``mpirun``; it is + not needed if MPI jobs are always launched directly by a + PMIx-enabled resource manager. +* `hwloc `_: version + |hwloc_min_version| or later, and older than v3.0.0 (Open MPI does + not yet support the hwloc v3.x series). Open MPI |ompi_ver| embeds + hwloc |hwloc_embedded_version|. +* `Libevent `_: version |event_min_version| or + later. Open MPI |ompi_ver| embeds Libevent + |event_embedded_version|. + +See the :ref:`Required support libraries section +` for the full details, +including how ``configure`` chooses between an embedded and an +external copy of each library. + +The versions of OpenPMIx and PRRTE that a given Open MPI release +supports determine which back-end run-time systems that release +supports. + +Building Open MPI additionally requires Perl 5 and Python +|python_min_version| or later. These are needed only to *build* Open +MPI itself |mdash| not to build or run MPI applications. + +Operating systems and compilers +------------------------------- + +Open MPI |ompi_ver| requires a C11 (or newer) C compiler. * Systems that have been tested are: - * Linux (various flavors/distros), 64 bit (x86, ppc, aarch64), - with gcc/gfortran (>=7.x+), clang (>=10.x), Intel, - and Portland (be sure to also see :ref:`the Compiler Notes - section `) - * macOS (14.x, 15.x), 64 bit (x86_64) with XCode compilers + * Linux (various flavors/distros), 64 bit (x86, ppc, aarch64), with + gcc/gfortran (>= 7.x), clang (>= 10.x), Intel, and NVIDIA (be sure + to also see :ref:`the Compiler Notes section + `) + * macOS (26.x), 64 bit (arm64/Apple silicon), with Xcode compilers. + This is the macOS platform exercised by Open MPI's GitHub Actions + CI, which builds, runs ``make check``, and runs the example + programs on the ``macos-latest`` runner image. * Other systems have been lightly (but not fully) tested: + * macOS on 64 bit x86_64 (Intel) hardware with Xcode compilers. + Open MPI is expected to work here, but it is not covered by CI. * Cygwin 64 bit with gcc * ARMv6, ARMv7, ARMv9 * Other 64 bit platforms. - * OpenBSD. Requires configure options ``--enable-mca-no-build=patcher`` - and ``--disable-dlopen`` with this release. + * OpenBSD. Requires configure options + ``--enable-mca-no-build=patcher`` and ``--disable-dlopen`` with + this release. * Problems have been reported when building Open MPI on FreeBSD 11.1 - using the clang-4.0 system compiler. A workaround is to build + using the clang-4.0 system compiler. A workaround is to build Open MPI using the GNU compiler. .. note:: 32-bit environments are no longer supported. -* The run-time systems that are currently supported are: +Run-time systems +---------------- + +Because Open MPI delegates launch to PRRTE, the supported back-end +run-time systems are those that PRRTE supports. The currently +supported run-time systems are: + +* **ssh / rsh:** used in non-scheduled environments, and also to launch + within an allocation from a resource manager that does not provide + its own launch mechanism. See :doc:`Launching with SSH + `. +* **Slurm:** Open MPI obtains the node list and slot counts from + Slurm, and uses Slurm's native mechanisms to start processes. + Slurm's ``srun`` "direct launch" mode is also supported. See + :doc:`Launching with Slurm `. +* **PBS Pro / Torque / OpenPBS:** the allocation is obtained from the + resource manager, and processes are started via the TM interface. + See :doc:`Launching with PBS / Torque `. +* **Platform LSF** (tested with v9.1.1 and later): the allocation is + obtained from LSF, and processes are started via LSF's native + mechanisms. Problems have been reported with the most recent LSF + releases; see :doc:`Launching with LSF ` for + the details and a workaround. +* **HPE PALS:** processes are started via the Parallel Application + Launch Service on HPE systems; PALS' ``aprun`` "direct launch" mode + is also supported. See :doc:`Launching with HPE PALS + `. +* **Grid Engine:** the allocation is obtained from Grid Engine, and + processes are started via ``qrsh`` so that Grid Engine can monitor + and control them. This covers the whole Grid Engine family (Oracle + Grid Engine, Son of Grid Engine, Altair/Univa Grid Engine, Open + Cluster Scheduler, and friends); it must be explicitly enabled with + ``configure --with-sge``. See :doc:`Launching with Grid Engine + `. +* **Flux:** the allocation is obtained from Flux, but Flux provides no + native launch mechanism to PRRTE; processes within the allocation + are started via ``ssh``. + +Network interconnects +--------------------- + +The primary network transports that Open MPI supports are: + +* Shared memory, for on-node communication +* TCP / IP +* InfiniBand and RoCE, via `UCX `_ +* Networks supported by `Libfabric (OFI) `_, + including AWS EFA, Cornelis Networks Omni-Path, and HPE Slingshot +* Cisco usNIC - * ssh / rsh - * PBS Pro, Torque - * Platform LSF (tested with v9.1.1 and later) - * Slurm - * HPE/Cray PALS - * Oracle Grid Engine (OGE) 6.1, 6.2 and open source Grid Engine +See the networking pages under :doc:`/tuning-apps/index` for +configuration and tuning details. diff --git a/docs/requirements.txt b/docs/requirements.txt index 9fda5a43a4d..be4538dd67b 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -4,6 +4,10 @@ docutils sphinx-rtd-theme sphobjinv +# Used by validate-llm-docs.py to validate the generated LLM-friendly +# artifacts against the published JSON Schemas (see "make check"). +jsonschema + # These modules are needed for the pympistandard module when you are # running Python 3.6 (they became part of core Python in 3.7). We # specifically use "==" (vs. "<=") because the top-level Open MPI diff --git a/docs/tuning-apps/_include/ompio-mpi-info-hints.rst b/docs/tuning-apps/_include/ompio-mpi-info-hints.rst new file mode 100644 index 00000000000..a0162695e35 --- /dev/null +++ b/docs/tuning-apps/_include/ompio-mpi-info-hints.rst @@ -0,0 +1,152 @@ +MPI allows applications to pass ``MPI_Info`` hints to +:ref:`MPI_File_open`, :ref:`MPI_File_set_view`, and +:ref:`MPI_File_set_info`. Hints are advisory: OMPIO may ignore hints +that are not supported in the selected component stack. The +:ref:`MPI_File_get_info` routine returns a new ``MPI_Info`` object +containing the public hints that OMPIO currently associates with the +file. The caller is responsible for freeing that returned object with +:ref:`MPI_Info_free`. + +OMPIO preserves the spelling of a supported hint that was accepted +from the user's ``MPI_Info`` object. When OMPIO reports an internal +default that did not come from a user-supplied hint, it uses the +canonical public spelling shown below. + +OMPIO common hints +^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + + * - Hint + - Accepted by + - Default / MCA parameter + - Notes + * - ``cb_buffer_size`` + - ``MPI_File_open``, ``MPI_File_set_info``, + ``MPI_File_set_view`` + - ``io_ompio_bytes_per_agg`` + - Size, in bytes, of the temporary collective I/O buffer on each + aggregator. + * - ``cb_nodes`` + - ``MPI_File_open``, ``MPI_File_set_info``, + ``MPI_File_set_view`` + - ``io_ompio_num_aggregators`` when that MCA parameter is set to + a non-negative value. + - Number of collective I/O aggregators. When OMPIO is using its + automatic aggregator selection, this hint is not returned as + ``-1``. + * - ``collective_buffering`` + - ``MPI_File_open``, ``MPI_File_set_info``, + ``MPI_File_set_view`` + - ``true`` + - Controls whether collective buffering is enabled for collective + I/O decisions. + +``sharedfp`` component hints +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. list-table:: + :header-rows: 1 + + * - Component + - Hint + - Accepted by + - Default / MCA parameter + - Notes + * - ``sharedfp/individual`` + - ``OMPIO_SHAREDFP_RELAXED_ORDERING`` + - ``MPI_File_open`` + - No default; no corresponding MCA parameter. + - If present on a write-capable file open, this hint raises the + priority of the ``individual`` shared file pointer component. + OMPIO reports it only when that component is selected for the + file. + +Lustre ``fs`` component hints +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Lustre layout hints affect file layout creation. Lustre applies +these settings only when a file is created in a mode where layout can +be set safely; changing them after a file has been opened does not +change the already-created file layout. + +.. list-table:: + :header-rows: 1 + + * - Canonical hint + - Accepted alias + - Accepted by + - Default / MCA parameter + - Notes + * - ``striping_unit`` + - ``stripe_size`` + - ``MPI_File_open`` + - ``fs_lustre_stripe_size`` when greater than zero. + - Stripe size in bytes. + * - ``striping_factor`` + - ``stripe_width`` + - ``MPI_File_open`` + - ``fs_lustre_stripe_width`` when greater than zero. + - Number of Lustre object storage targets to stripe across. + +If an application supplies an alias, :ref:`MPI_File_get_info` returns +the alias spelling. If both a canonical hint and its alias are +supplied, OMPIO prefers the canonical spelling. If only an MCA +parameter supplies the value, OMPIO reports the canonical spelling. + +GPFS ``fs`` component hints +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The GPFS component accepts the hints listed below when the GPFS +component is built and selected. These hints are consumed when the +file is opened. Later ``MPI_File_set_info`` or ``MPI_File_set_view`` +calls do not reapply them to GPFS, and :ref:`MPI_File_get_info` +continues to report the open-time accepted values. + +.. list-table:: + :header-rows: 1 + + * - Hint + - Accepted by + - Default / MCA parameter + - Notes + * - ``useSIOXLib`` + - ``MPI_File_open`` + - No default; no corresponding MCA parameter. + - Enables optional SIOX-assisted GPFS I/O selection when SIOX + support is compiled in. + * - ``gpfsAccessRange`` + - ``MPI_File_open`` + - No default; no corresponding MCA parameter. + - Passed to GPFS as an access range hint. + * - ``gpfsFreeRange`` + - ``MPI_File_open`` + - No default; no corresponding MCA parameter. + - Passed to GPFS as a free range hint. + * - ``gpfsClearFileCache`` + - ``MPI_File_open`` + - No default; no corresponding MCA parameter. + - Requests GPFS file-cache clearing. + * - ``gpfsCancelHints`` + - ``MPI_File_open`` + - No default; no corresponding MCA parameter. + - Requests cancellation of GPFS hints. + * - ``gpfsSetReplication`` + - ``MPI_File_open`` + - No default; no corresponding MCA parameter. + - Passed to GPFS as a replication hint. + * - ``gpfsByteRange`` + - ``MPI_File_open`` + - No default; no corresponding MCA parameter. + - Passed to GPFS as a byte range hint. + * - ``gpfsRestripeData`` + - ``MPI_File_open`` + - No default; no corresponding MCA parameter. + - Requests GPFS data restriping. + +When SIOX support is compiled in, the GPFS component also accepts: +``sioxAccessRange``, ``sioxFreeRange``, ``sioxClearFileCache``, +``sioxCancelHints``, ``sioxDataShipStart``, ``sioxDataShipStop``, +``sioxSetReplication``, ``sioxByteRange``, and +``sioxRestripeData``. diff --git a/docs/tuning-apps/accelerators/cuda.rst b/docs/tuning-apps/accelerators/cuda.rst index 8a393b3f32f..b3025cfc35c 100644 --- a/docs/tuning-apps/accelerators/cuda.rst +++ b/docs/tuning-apps/accelerators/cuda.rst @@ -1,265 +1,353 @@ CUDA ==== -.. error:: TODO This section needs to be converted from FAQ Q&A style - to regular documentation style. - -How do I build Open MPI with CUDA-aware support? ------------------------------------------------- - CUDA-aware support means that the MPI library can send and receive GPU -buffers directly. CUDA support is being continuously updated so -different levels of support exist in different versions. We recommend -you use the latest version of Open MPI for best support. +buffers directly, without the application first staging them into host +memory. Open MPI automatically detects that a buffer passed to an MPI +routine is a CUDA device pointer and handles it appropriately; this +detection relies on CUDA's Unified Virtual Addressing (UVA), which lets +the library determine whether a given pointer refers to device or host +memory. + +CUDA support is updated continuously, and different levels of support +exist in different versions, so we recommend using the latest release +of Open MPI for the best support. Open MPI offers two flavors of CUDA +support, described below; you may build with either or both. + +Building Open MPI with CUDA-aware support +----------------------------------------- -Open MPI offers two flavors of CUDA support: +Regardless of which flavor of CUDA support you plan to use, configure +Open MPI with the ``--with-cuda=`` option to build in +CUDA support. The configure script searches the given path for +``libcuda.so``; if it cannot be found, also pass +``--with-cuda-libdir``, for example: + +.. code-block:: sh -#. Via `UCX `_. + shell$ ./configure --with-cuda=/usr/local/cuda \ + --with-cuda-libdir=/usr/local/cuda/lib64/stubs - This is the preferred mechanism. Since UCX will be providing the - CUDA support, it is important to ensure that UCX itself is built - with CUDA support. +Support via UCX +^^^^^^^^^^^^^^^ - To see if your ucx was built with CUDA support run the following - command: +Using `UCX `_ is the preferred mechanism. Since +UCX provides the CUDA support in this configuration, it is important +that UCX itself is built with CUDA support. To check whether your UCX +was built with CUDA support, run: - .. code-block:: sh +.. code-block:: sh - # Check if ucx was built with CUDA support - shell$ ucx_info -v + shell$ ucx_info -v - # configured with: --build=powerpc64le-redhat-linux-gnu --host=powerpc64le-redhat-linux-gnu --program-prefix= --disable-dependency-tracking --prefix=/usr --exec-prefix=/usr --bindir=/usr/bin --sbindir=/usr/sbin --sysconfdir=/etc --datadir=/usr/share --includedir=/usr/include --libdir=/usr/lib64 --libexecdir=/usr/libexec --localstatedir=/var --sharedstatedir=/var/lib --mandir=/usr/share/man --infodir=/usr/share/info --disable-optimizations --disable-logging --disable-debug --disable-assertions --enable-mt --disable-params-check --enable-cma --without-cuda --without-gdrcopy --with-verbs --with-cm --with-knem --with-rdmacm --without-rocm --without-xpmem --without-java +and look for ``--with-cuda`` in the reported configure line. If you +need to build UCX yourself to include CUDA support, see the UCX +documentation for `building UCX with Open MPI +`_. +A typical build looks like: - If you need to build ucx yourself to include CUDA support, please - see the UCX documentation for `building ucx with Open MPI: `_ +.. code-block:: sh - It should look something like: + # Configure UCX with CUDA support + shell$ ./configure --prefix=/path/to/ucx-cuda-install \ + --with-cuda=/usr/local/cuda --with-gdrcopy=/usr - .. code-block:: sh + # Configure Open MPI to use that UCX + shell$ ./configure --with-cuda=/usr/local/cuda \ + --with-ucx=/path/to/ucx-cuda-install - # Configure UCX this way - shell$ ./configure --prefix=/path/to/ucx-cuda-install --with-cuda=/usr/local/cuda --with-gdrcopy=/usr +Internal Open MPI CUDA support +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - # Configure Open MPI this way - shell$ ./configure --with-cuda=/usr/local/cuda --with-ucx=/path/to/ucx-cuda-install +Open MPI also provides its own internal CUDA support, used by the +CUDA-ized components such as the ``smcuda`` shared-memory BTL. This is +enabled by the same ``--with-cuda`` option. -#. Via internal Open MPI CUDA support +Running on hosts without CUDA +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Regardless of which flavor of CUDA support (or both) you plan to use, -Open MPI should be configured using the ``--with-cuda=`` -configure option to build CUDA support into Open MPI. The configure -script will automatically search the path given for ``libcuda.so``. If it cannot -be found, please also pass ``--with-cuda-libdir``. For example: -``--with-cuda= --with-cuda-libdir=/usr/local/cuda/lib64/stubs``. +Open MPI supports building with CUDA libraries and then running on +systems that have neither CUDA libraries nor CUDA hardware. For +releases v5.0.2 and newer, no special steps are required. -Open MPI supports building with CUDA libraries and running on systems -without CUDA libraries or hardware. +For the v5.0.0 and v5.0.1 releases only, you must build the +CUDA-dependent components as DSOs to get this behavior, using the +``--enable-mca-dso`` option. This affects the ``smcuda`` shared-memory +and ``uct`` BTLs, as well as the ``rgpusm`` and ``gpusm`` rcache +components: -For releases v5.0.2 and newer no special steps are required to get this behavior. +.. code-block:: sh -In order to realize this behavior for the v5.0.0 and v5.0.1 releases, -when configuring Open MPI, you have to specify the CUDA dependent components to be built as DSOs using the -``--enable-mca-dso= -This affects the ``smcuda`` shared memory and ``uct`` BTLs, as well -as the ``rgpusm`` and ``gpusm`` rcache components. +Building with the NVIDIA compilers +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -An example configure command would look like the following: +With CUDA 6.5 and later, CUDA-aware Open MPI builds with the NVIDIA +compilers without anything special. With CUDA 7.0 and 7.5, some +additional compiler flags are required: - .. code-block:: sh +.. code-block:: sh - # Configure Open MPI this way - shell$ ./configure --with-cuda=/usr/local/cuda \ - --enable-mca-dso=btl-smcuda,rcache-rgpusm,rcache-gpusm,accelerator-cuda + # For NVIDIA compilers version 15.9 and later + shell$ ./configure --with-wrapper-cflags=-ta:tesla -///////////////////////////////////////////////////////////////////////// + # For earlier NVIDIA compiler versions + shell$ ./configure CFLAGS=-D__LP64__ \ + --with-wrapper-cflags="-D__LP64__ -ta:tesla" -How do I verify that Open MPI has been built with CUDA support? ---------------------------------------------------------------- +Verifying that CUDA support was built +------------------------------------- -Verify that Open MPI has been built with cuda using ``ompi_info`` -with one of the following commands. +Use :ref:`ompi_info(1) ` to confirm that a given Open +MPI installation was built with CUDA support: .. code-block:: sh - # Use ompi_info to verify cuda support in Open MPI + # List the MPI extensions that were built shell$ ompi_info | grep "MPI extensions" - MPI extensions: affinity, cuda, pcollreq + MPI extensions: affinity, cuda, ftmpi, rocm + + # Query the CUDA support MCA parameter directly shell$ ompi_info --parsable --all | grep mpi_built_with_cuda_support:value mca:mpi:base:param:mpi_built_with_cuda_support:value:true -///////////////////////////////////////////////////////////////////////// - -How do I run Open MPI with applications that pass CUDA buffers to MPI? ----------------------------------------------------------------------- - -Open MPI will detect and enable CUDA enabled components at runtime with -no additional mpirun parameters. - -///////////////////////////////////////////////////////////////////////// - -How do I build Open MPI with CUDA-aware support using PGI? ----------------------------------------------------------- +Detecting CUDA-aware support at compile and run time +---------------------------------------------------- -With CUDA 6.5, you can build all versions of CUDA-aware Open MPI -without doing anything special. However, with CUDA 7.0 and CUDA 7.5, -you need to pass in some specific compiler flags for things to work -correctly. Add the following to your configure line. +The ``cuda`` MPI extension provides both a compile-time and a run-time +check, and you can use whichever is more convenient. Both require +including the Open MPI-specific header ````: -.. code-block:: sh +* The compile-time check is the ``MPIX_CUDA_AWARE_SUPPORT`` macro. +* The run-time check is the :ref:`MPIX_Query_cuda_support(3) + ` function. - # For PGI 15.9 and later (Also called NVCC): - shell$ ./configure --with-wrapper-cflags=-ta:tesla +The extension is built by default, whether or not Open MPI itself was +built with CUDA-aware support; when support is absent, the run-time +check simply returns ``0``. The following program illustrates both +checks: - # For earlier versions of PGI: - shell$ ./configure CFLAGS=-D__LP64__ --with-wrapper-cflags="-D__LP64__ -ta:tesla" +.. code-block:: c -///////////////////////////////////////////////////////////////////////// + /* + * Program that shows the use of the CUDA-aware macro and run-time check. + */ + #include + #include "mpi.h" -What kind of CUDA support exists in Open MPI? ---------------------------------------------- + #if !defined(OPEN_MPI) || !OPEN_MPI + #error This source code uses an Open MPI-specific extension + #endif -CUDA-aware support is defined as Open MPI automatically detecting that -the argument pointer being passed to an MPI routine is a CUDA device -memory pointer. + /* Needed for MPIX_Query_cuda_support(), below */ + #include "mpi-ext.h" -See :ref:`this FAQ entry ` -for more details on which APIs are CUDA-aware. + int main(int argc, char *argv[]) + { + MPI_Init(&argc, &argv); + printf("Compile time check:\n"); + #if defined(MPIX_CUDA_AWARE_SUPPORT) && MPIX_CUDA_AWARE_SUPPORT + printf("This MPI library has CUDA-aware support.\n"); + #elif defined(MPIX_CUDA_AWARE_SUPPORT) && !MPIX_CUDA_AWARE_SUPPORT + printf("This MPI library does not have CUDA-aware support.\n"); + #else + printf("This MPI library cannot determine if there is CUDA-aware support.\n"); + #endif /* MPIX_CUDA_AWARE_SUPPORT */ -.. error:: CUDA 4.0 is SUPER OLD! End users dont care about the - differences between cuda-aware, cuda-ipc, gpu-direct, and gpu-direct-rdma + printf("Run time check:\n"); + #if defined(MPIX_CUDA_AWARE_SUPPORT) + if (1 == MPIX_Query_cuda_support()) { + printf("This MPI library has CUDA-aware support.\n"); + } else { + printf("This MPI library does not have CUDA-aware support.\n"); + } + #else /* !defined(MPIX_CUDA_AWARE_SUPPORT) */ + printf("This MPI library cannot determine if there is CUDA-aware support.\n"); + #endif /* MPIX_CUDA_AWARE_SUPPORT */ -Open MPI depends on various features of CUDA 4.0, so one needs to have -at least the CUDA 4.0 driver and toolkit. The new features of -interest are the Unified Virtual Addressing (UVA) so that all pointers -within a program have unique addresses. In addition, there is a new -API that allows one to determine if a pointer is a CUDA device pointer -or host memory pointer. This API is used by the library to decide -what needs to be done with each buffer. In addition, CUDA 4.1 also -provides the ability to register host memory with the CUDA driver, -which can improve performance. CUDA 4.1 also added CUDA IPC support -for fast communication between GPUs on the same node. + MPI_Finalize(); + return 0; + } -Note that derived datatypes |mdash| both contiguous and non-contiguous -|mdash| are supported. However, the non-contiguous datatypes -currently have high overhead because of the many calls to the CUDA -function ``cuMemcpy()`` to copy all the pieces of the buffer into the -intermediate buffer. +Running applications that pass CUDA buffers +------------------------------------------- -CUDA-aware support is available in: +Open MPI detects and enables its CUDA-capable components at run time +with no additional ``mpirun`` parameters; an application may simply pass +CUDA device buffers to MPI routines. CUDA-aware support is available +in the following transports: -* The UCX (``ucx``) PML +* The UCX (``ucx``) PML. * The PSM2 (``psm2``) MTL with the CM (``cm``) PML. * The OFI (``ofi``) MTL with the CM (``cm``) PML. -* Both CUDA-ized shared memory (``smcuda``) and TCP (``tcp``) BTLs - with the OB1 (``ob1``) PML. - -///////////////////////////////////////////////////////////////////////// - -PSM2 support for CUDA ---------------------- - -CUDA-aware support is present in PSM2 MTL. When running CUDA-aware -Open MPI on Cornelis Networks Omni-Path, the PSM2 MTL will automatically set -``PSM2_CUDA`` environment variable which enables PSM2 to handle GPU -buffers. If the user wants to use host buffers with a CUDA-aware Open -MPI, it is recommended to set ``PSM2_CUDA`` to ``0`` in the execution -environment. PSM2 also has support for the NVIDIA GPUDirect support -feature. To enable this, users will need to set ``PSM2_GPUDIRECT`` -to ``1`` in the execution environment. - -Note: The PSM2 library and ``hfi1`` driver with CUDA support are requirements -to use GPUDirect support on Cornelis Networks Omni-Path. The minimum -PSM2 build version required is `PSM2 10.2.175 -`_. - -For more information refer to the `Cornelis Networks Customer Center -`_. - -///////////////////////////////////////////////////////////////////////// - -OFI support for CUDA ---------------------- +* The CUDA-ized shared-memory (``smcuda``) and TCP (``tcp``) BTLs with + the OB1 (``ob1``) PML. -CUDA-aware support is present in OFI MTL. When running CUDA-aware -Open MPI over Libfabric, the OFI MTL will check if there are any -providers capable of handling GPU (or other accelerator) memory -through the ``hmem``-related flags. If a CUDA-capable provider is -available, the OFI MTL will directly send GPU buffers through -Libfabric's API after registering the memory. If there are no -CUDA-capable providers available, the buffers will automatically -be copied to host buffers before being transferred through -Libfabric's API. +Both contiguous and non-contiguous derived datatypes are supported. +Non-contiguous datatypes currently carry high overhead, however, +because copying the pieces of the buffer into an intermediate buffer +requires many separate device-to-device copies. -///////////////////////////////////////////////////////////////////////// - -Can I get additional CUDA debug-level information at run-time? --------------------------------------------------------------- - -Yes, by enabling some vebosity flags. - -* The ``opal_cuda_verbose`` parameter has only one level of verbosity: - - .. code-block:: - - shell$ mpirun --mca opal_cuda_verbose 10 ... - - -* The ``mpi_common_cuda_verbose`` parameter provides additional - information about CUDA-aware related activities. This can be set to - a variety of different values. There is really no need to use these - unless you have strange problems: - - .. code-block:: sh - - # A bunch of CUDA debug information - shell$ mpirun --mca mpi_common_cuda_verbose 10 ... - # Even more CUDA debug information - shell$ mpirun --mca mpi_common_cuda_verbose 20 ... - # Yet more CUDA debug information - shell$ mpirun --mca mpi_common_cuda_verbose 100 ... +.. _faq-cuda-mpi-apis-cuda-label: -* The ``smcuda`` BTL has three MCA parameters related to the use of - CUDA IPC. By default, CUDA IPC is used where possible. But the - user can now turn it off if they prefer. +CUDA-aware MPI APIs +------------------- + +The following MPI operations accept CUDA device buffers: + +* ``MPI_Allgather``, ``MPI_Allgatherv`` +* ``MPI_Allreduce`` +* ``MPI_Alltoall``, ``MPI_Alltoallv``, ``MPI_Alltoallw`` +* ``MPI_Bcast`` +* ``MPI_Bsend``, ``MPI_Bsend_init`` +* ``MPI_Exscan`` +* ``MPI_Gather``, ``MPI_Gatherv`` +* ``MPI_Get``, ``MPI_Put`` +* ``MPI_Ibsend`` +* ``MPI_Irecv``, ``MPI_Isend``, ``MPI_Irsend``, ``MPI_Issend`` +* ``MPI_Recv``, ``MPI_Recv_init`` +* ``MPI_Reduce``, ``MPI_Reduce_scatter``, ``MPI_Reduce_scatter_block`` +* ``MPI_Rsend``, ``MPI_Rsend_init`` +* ``MPI_Scan`` +* ``MPI_Scatter``, ``MPI_Scatterv`` +* ``MPI_Send``, ``MPI_Send_init`` +* ``MPI_Sendrecv`` +* ``MPI_Ssend``, ``MPI_Ssend_init`` +* ``MPI_Win_create`` + +The following operations do *not* currently accept CUDA device buffers: + +* ``MPI_Accumulate``, ``MPI_Get_Accumulate`` +* ``MPI_Compare_and_swap``, ``MPI_Fetch_and_op`` +* The non-blocking collectives ``MPI_Iallgather``, ``MPI_Iallgatherv``, + ``MPI_Iallreduce``, ``MPI_Ialltoall``, ``MPI_Ialltoallv``, + ``MPI_Ialltoallw``, ``MPI_Ibcast``, and ``MPI_Iexscan`` +* ``MPI_Rget``, ``MPI_Rput`` + +.. note:: These lists reflect known support and may vary between Open + MPI versions and transports. The set of CUDA-aware operations when + using the UCX PML is essentially the same as above, with the + additional restriction that UCX's one-sided component does not + support CUDA buffers: all one-sided operations (for example, + ``MPI_Put``, ``MPI_Get``, ``MPI_Accumulate``), all window creation + calls (for example, ``MPI_Win_create``), and all non-blocking + reduction collectives are not CUDA-aware when using UCX. + +Transport-specific notes +------------------------ + +CUDA-aware UCX +^^^^^^^^^^^^^^ + +When both UCX and Open MPI are built with CUDA support, selecting the +UCX PML is sufficient to use it. For example, to run ``osu_latency`` +from the `OSU benchmarks `_ +with CUDA buffers: - .. code-block:: sh +.. code-block:: sh - shell$ mpirun --mca btl_smcuda_use_cuda_ipc 0 ... + shell$ mpirun -n 2 --mca pml ucx \ + -x UCX_TLS=rc,sm,cuda_copy,gdr_copy,cuda_ipc ./osu_latency D D - In addition, it is assumed that CUDA IPC is possible when running on - the same GPU, and this is typically true. However, there is the - ability to turn it off. +OFI / libfabric +^^^^^^^^^^^^^^^ + +When running over Libfabric, the OFI MTL checks whether any provider +can handle GPU (or other accelerator) memory through the +``hmem``-related flags. If a CUDA-capable provider is available, the +OFI MTL sends GPU buffers directly through Libfabric's API after +registering the memory; otherwise, the buffers are automatically copied +to host memory before being transferred. + +PSM2 / Omni-Path +^^^^^^^^^^^^^^^^^^ + +CUDA-aware support is present in the PSM2 MTL. When running CUDA-aware +Open MPI on Cornelis Networks Omni-Path, the PSM2 MTL automatically sets +the ``PSM2_CUDA`` environment variable so that PSM2 handles GPU +buffers. If you want to use host buffers with a CUDA-aware Open MPI, it +is recommended to set ``PSM2_CUDA`` to ``0`` in the environment. PSM2 +also supports NVIDIA GPUDirect; to enable it, set ``PSM2_GPUDIRECT`` to +``1``. These variables must be set before ``MPI_Init()`` is called, for +example: - .. code-block:: sh +.. code-block:: sh - shell$ mpirun --mca btl_smcuda_use_cuda_ipc_same_gpu 0 ... + shell$ mpirun -x PSM2_CUDA=1 -x PSM2_GPUDIRECT=1 --mca mtl psm2 ./mpi_hello + +GPUDirect support on Omni-Path requires a PSM2 library and ``hfi1`` +driver with CUDA support; the minimum required PSM2 version is `PSM2 +10.2.175 `_. + +When binding processes to GPUs on Omni-Path, each process should select +a specific GPU (within the same NUMA node as the CPU the process runs +on) before calling ``MPI_Init()`` using ``cudaChooseDevice()``, +``cudaSetDevice()``, and similar; use the ``mpirun`` binding options +(such as ``--bind-to core``) to keep processes from migrating between +NUMA nodes. See :ref:`Selecting a GPU close to the process +`. + +.. note:: The Cornelis Networks Omni-Path / PSM2 details above may be + dated. For current guidance, consult the PSM2 and Omni-Path + documentation from `Cornelis Networks + `_. + +Selecting a CUDA device +----------------------- + +Open MPI requires some CUDA resources for internal use. When possible, +these are allocated lazily, the first time they are needed |mdash| for +example, CUDA IPC memory handles are created when a transfer first +requires them. ``MPI_Init()`` and most communicator operations do not +create any CUDA resources (this is guaranteed at least for +``MPI_Comm_rank`` and ``MPI_Comm_size`` on ``MPI_COMM_WORLD``). + +This is not always the case, however. When using PSM2 or the +``smcuda`` BTL (with the OB1 PML), it is not feasible to delay the +allocation, so those CUDA resources are allocated during +``MPI_Init()``. + +In all cases, the CUDA device must be selected *before* the first MPI +call that requires a CUDA resource. When CUDA resources are initialized +lazily, you may use the communicator operations above to determine rank +information and select a GPU accordingly: - Last, to get some insight into whether CUDA IPC is being used, you - can turn on some verbosity that shows whether CUDA IPC gets enabled - between two GPUs. +.. code-block:: c - .. code-block:: sh + int local_rank = -1; + { + MPI_Comm local_comm; + MPI_Comm_split_type(MPI_COMM_WORLD, MPI_COMM_TYPE_SHARED, rank, + MPI_INFO_NULL, &local_comm); + MPI_Comm_rank(local_comm, &local_rank); + MPI_Comm_free(&local_comm); + } + int num_devices = 0; + cudaGetDeviceCount(&num_devices); + cudaSetDevice(local_rank % num_devices); - shell$ mpirun --mca btl_smcuda_cuda_ipc_verbose 100 ... +Open MPI's internal CUDA resources are released during +``MPI_Finalize()``, so it is an application error to call +``cudaDeviceReset()`` before ``MPI_Finalize()``. -///////////////////////////////////////////////////////////////////////// +For a general treatment of selecting an accelerator device before +``MPI_Init()`` (using the ``OMPI_COMM_WORLD_LOCAL_RANK`` environment +variable), see :doc:`initialize`. .. _faq-cuda-mpi-cuda-numa-issues-label: -NUMA Node Issues ----------------- +Selecting a GPU close to the process +------------------------------------ -When running on a node that has multiple GPUs, you may want to select -the GPU that is closest to the NUMA node on which your process is -running. One way to do this is to make use of the ``hwloc`` library. -The following is a C code snippet that can be used in your application -to select a GPU that is close. It will determine on which CPU it is -running and then look for the closest GPU. There could be multiple -GPUs that are the same distance away. This is dependent on having -``hwloc`` somewhere on your system. +On a node with multiple GPUs, you may want each process to use the GPU +closest to the NUMA node on which it is running. One way to do this is +with the ``hwloc`` library. The following C snippet determines the CPU +the process is running on and then looks for the closest GPU; note that +several GPUs may be equidistant. .. code-block:: c @@ -268,7 +356,6 @@ GPUs that are the same distance away. This is dependent on having * that the MPI program is running on. Note that this works even without * any libpciaccess or libpci support as it keys off the NVIDIA vendor ID. * There may be other ways to implement this but this is one way. - * January 10, 2014 */ #include #include @@ -375,329 +462,90 @@ GPUs that are the same distance away. This is dependent on having return 0; } -///////////////////////////////////////////////////////////////////////// +Run-time tuning and debugging +----------------------------- -How do I develop CUDA-aware Open MPI applications? --------------------------------------------------- +CUDA IPC in the shared-memory BTL +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Developing CUDA-aware applications is a complex topic, and beyond the -scope of this document. CUDA-aware applications often have to take -machine-specific considerations into account, including the number of -GPUs installed on each node and how the GPUs are connected to the CPUs -and to each other. Often, when using a particular transport layer -(such as OPA/PSM2) there will be run-time decisions to make about -which CPU cores will be used with which GPUs. +By default, the ``smcuda`` BTL uses CUDA IPC where possible to move GPU +data quickly between GPUs on the same node and PCI root complex. A few +MCA parameters control this behavior. You can disable CUDA IPC +entirely: -A good place to start is the `NVIDIA CUDA Toolkit Documentation -`_ including the `Programming Guide -`_ and the -`Best Practices Guide -`_. For -examples of how to write CUDA-aware MPI applications, the `NVIDIA -developers blog -`_ -offers examples and the `OSU Micro-Benchmarks -`_ offer an excellent -example of how to write CUDA-aware MPI applications. - -///////////////////////////////////////////////////////////////////////// - -.. _faq-cuda-mpi-apis-cuda-label: - -Which MPI APIs work with CUDA-aware? ------------------------------------- - -* MPI_Allgather -* MPI_Allgatherv -* MPI_Allreduce -* MPI_Alltoall -* MPI_Alltoallv -* MPI_Alltoallw -* MPI_Bcast -* MPI_Bsend -* MPI_Bsend_init -* MPI_Exscan -* MPI_Ibsend -* MPI_Irecv -* MPI_Isend -* MPI_Irsend -* MPI_Issend -* MPI_Gather -* MPI_Gatherv -* MPI_Get -* MPI_Put -* MPI_Rsend -* MPI_Rsend_init -* MPI_Recv -* MPI_Recv_init -* MPI_Reduce -* MPI_Reduce_scatter -* MPI_Reduce_scatter_block -* MPI_Scan -* MPI_Scatter -* MPI_Scatterv -* MPI_Send -* MPI_Send_init -* MPI_Sendrecv -* MPI_Ssend -* MPI_Ssend_init -* MPI_Win_create - -.. FIXME: We need to verify the above list. - -///////////////////////////////////////////////////////////////////////// - -Which MPI APIs do NOT work with CUDA-aware? -------------------------------------------- - -* MPI_Accumulate -* MPI_Compare_and_swap -* MPI_Fetch_and_op -* MPI_Get_Accumulate -* MPI_Iallgather -* MPI_Iallgatherv -* MPI_Iallreduce -* MPI_Ialltoall -* MPI_Ialltoallv -* MPI_Ialltoallw -* MPI_Ibcast -* MPI_Iexscan -* MPI_Rget -* MPI_Rput - -.. FIXME: We need to verify the above list. - -///////////////////////////////////////////////////////////////////////// - -How do I use CUDA-aware UCX for Open MPI? ------------------------------------------ - -Example of running ``osu_latency`` from the `OSU benchmarks -`_ with CUDA buffers -using Open MPI and UCX CUDA support: - -.. code-block:: - - shell$ mpirun -n 2 --mca pml ucx \ - -x UCX_TLS=rc,sm,cuda_copy,gdr_copy,cuda_ipc ./osu_latency D D - -///////////////////////////////////////////////////////////////////////// - -Which MPI APIs work with CUDA-aware UCX? ----------------------------------------- - -* MPI_Send -* MPI_Bsend -* MPI_Ssend -* MPI_Rsend -* MPI_Isend -* MPI_Ibsend -* MPI_Issend -* MPI_Irsend -* MPI_Send_init -* MPI_Bsend_init -* MPI_Ssend_init -* MPI_Rsend_init -* MPI_Recv -* MPI_Irecv -* MPI_Recv_init -* MPI_Sendrecv -* MPI_Bcast -* MPI_Gather -* MPI_Gatherv -* MPI_Allgather -* MPI_Reduce -* MPI_Reduce_scatter -* MPI_Reduce_scatter_block -* MPI_Allreduce -* MPI_Scan -* MPI_Exscan -* MPI_Allgatherv -* MPI_Alltoall -* MPI_Alltoallv -* MPI_Alltoallw -* MPI_Scatter -* MPI_Scatterv -* MPI_Iallgather -* MPI_Iallgatherv -* MPI_Ialltoall -* MPI_Iialltoallv -* MPI_Ialltoallw -* MPI_Ibcast -* MPI_Iexscan - -.. FIXME: We need to verify the above list. These _SHOULD_ be the same - as above. - -///////////////////////////////////////////////////////////////////////// - -Which MPI APIs do NOT work with CUDA-aware UCX? ------------------------------------------------ - -* All one-sided operations such as MPI_Put, MPI_Get, MPI_Accumulate, - MPI_Rget, MPI_Rput, MPI_Get_Accumulate, MPI_Fetch_and_op, - MPI_Compare_and_swap, etc -* All window creation calls such as MPI_Win_create -* All non-blocking reduction collectives like MPI_Ireduce, - MPI_Iallreduce, etc - -.. FIXME: Checking with nVidia. This may be more of an issue of OSC_UCX - not supporting CUDA, though perhaps it's just performance. - -///////////////////////////////////////////////////////////////////////// - -Can I tell at compile time or runtime whether I have CUDA-aware support? ------------------------------------------------------------------------- - -There is both a compile time check and a run-time check available. -You can use whichever is the most convenient for your program. To -access them, you need to include ``mpi-ext.h``. Note that -``mpi-ext.h`` is specific to Open MPI. The following program shows an -example of using the CUDA-aware macro and run-time check. - -.. code-block:: c - - /* - * Program that shows the use of CUDA-aware macro and runtime check. - */ - #include - #include "mpi.h" - - #if !defined(OPEN_MPI) || !OPEN_MPI - #error This source code uses an Open MPI-specific extension - #endif +.. code-block:: sh - /* Needed for MPIX_Query_cuda_support(), below */ - #include "mpi-ext.h" + shell$ mpirun --mca btl_smcuda_use_cuda_ipc 0 ... - int main(int argc, char *argv[]) - { - MPI_Init(&argc, &argv); - - printf("Compile time check:\n"); - #if defined(MPIX_CUDA_AWARE_SUPPORT) && MPIX_CUDA_AWARE_SUPPORT - printf("This MPI library has CUDA-aware support.\n", MPIX_CUDA_AWARE_SUPPORT); - #elif defined(MPIX_CUDA_AWARE_SUPPORT) && !MPIX_CUDA_AWARE_SUPPORT - printf("This MPI library does not have CUDA-aware support.\n"); - #else - printf("This MPI library cannot determine if there is CUDA-aware support.\n"); - #endif /* MPIX_CUDA_AWARE_SUPPORT */ +CUDA IPC is assumed to be possible when two ranks run on the same GPU; +this too can be disabled: - printf("Run time check:\n"); - #if defined(MPIX_CUDA_AWARE_SUPPORT) - if (1 == MPIX_Query_cuda_support()) { - printf("This MPI library has CUDA-aware support.\n"); - } else { - printf("This MPI library does not have CUDA-aware support.\n"); - } - #else /* !defined(MPIX_CUDA_AWARE_SUPPORT) */ - printf("This MPI library cannot determine if there is CUDA-aware support.\n"); - #endif /* MPIX_CUDA_AWARE_SUPPORT */ +.. code-block:: sh - MPI_Finalize(); + shell$ mpirun --mca btl_smcuda_use_cuda_ipc_same_gpu 0 ... - return 0; - } +To see whether CUDA IPC is being enabled between two GPUs, turn on some +verbosity: -///////////////////////////////////////////////////////////////////////// +.. code-block:: sh -How do I limit how much CUDA IPC memory is held in the registration cache? --------------------------------------------------------------------------- + shell$ mpirun --mca btl_smcuda_cuda_ipc_verbose 100 ... -As mentioned earlier, the Open MPI library will make use of CUDA IPC support where -possible to move the GPU data quickly between GPUs that are on the same node and -same PCI root complex. The library holds on to registrations even after the data -transfer is complete as it is expensive to make some of the CUDA IPC registration -calls. If you want to limit how much memory is registered, you can use the -``mpool_rgpusm_rcache_size_limit`` MCA parameter. For example, this sets the limit -to 1000000 bytes: +The ``smcuda`` BTL holds on to CUDA IPC registrations even after a +transfer completes, because the registration calls are expensive. To +limit how much memory is registered, use the +``mpool_rgpusm_rcache_size_limit`` MCA parameter (in bytes); when the +cache reaches this size, the least-recently-used entries are evicted to +make room: -.. code-block:: +.. code-block:: sh shell$ mpirun --mca mpool_rgpusm_rcache_size_limit 1000000 ... -When the cache reaches this size, it will kick out the least recently used until -it can fit the new registration in. - -There also is the ability to have the cache empty itself out when the -limit is reached: +Alternatively, the cache can empty itself entirely when the limit is +reached: -.. code-block:: +.. code-block:: sh shell$ mpirun --mca mpool_rgpusm_rcache_empty_cache 1 ... -///////////////////////////////////////////////////////////////////////// - -What are some guidelines for using CUDA and Open MPI with Omni-Path? --------------------------------------------------------------------- - -When developing CUDA-aware Open MPI applications for OPA-based fabrics, the -PSM2 transport is preferred and a CUDA-aware version of PSM2 is provided with -all versions of the Cornelis Networks Omni-Path OPXS software suite. - -.. error:: TODO Are Intel/OPA references still correct? - -The PSM2 library provides a number of settings that will govern how it -will interact with CUDA, including ``PSM2_CUDA`` and ``PSM2_GPUDIRECT``, -which should be set in the environment before ``MPI_Init()`` is called. For -example: - -.. code-block:: +Verbose output +^^^^^^^^^^^^^^ - shell$ mpirun -x PSM2_CUDA=1 -x PSM2_GPUDIRECT=1 --mca mtl psm2 mpi_hello +Additional CUDA debugging output is available at run time. The +``opal_cuda_verbose`` parameter has a single verbosity level: -In addition, each process of the application should select a specific -GPU card to use before calling ``MPI_Init()``, by using -``cudaChooseDevice()``, ``cudaSetDevice()`` and similar. The chosen -GPU should be within the same NUMA node as the CPU the MPI process is -running on. You will also want to use the ``mpirun`` -``--bind-to-core`` or ``--bind-to-socket`` option to ensure that MPI -processes do not move between NUMA nodes. See the section on -:ref:`NUMA Node Issues `, for -more information. - -For more information see the *Cornelis Networks Performance Scaled Messaging 2 -(PSM2) Programmer's Guide* and the *Cornelis Networks Omni-Path Performance -Tuning Guide*, which can be found in the `Cornelis Networks Customer Center -`_. - -.. error:: TODO Are Intel/OPA references still correct? +.. code-block:: sh -///////////////////////////////////////////////////////////////////////// + shell$ mpirun --mca opal_cuda_verbose 10 ... -When do I need to select a CUDA device? ---------------------------------------- +The ``mpi_common_cuda_verbose`` parameter provides more detailed +information about CUDA-aware activities and accepts a range of values. +There is normally no need to use these unless you are diagnosing a +problem: -Open MPI requires CUDA resources allocated for internal use. When possible, -these resources are allocated lazily when they are first needed, e.g. CUDA -IPC mem handles are created when a communication routine first requires them -during a transfer. MPI_Init and most communicator related operations do not -create any CUDA resources (guaranteed at least for MPI_Comm_rank, -MPI_Comm_size on ``MPI_COMM_WORLD``). +.. code-block:: sh -However, this is not always the case. In certain instances, such as when -using PSM2 or the ``smcuda`` BTL (with the OB1 PML), it is not feasible to -delay the CUDA resources allocation. Consequently, these resources will need -to be allocated during ``MPI_Init()``. + shell$ mpirun --mca mpi_common_cuda_verbose 10 ... # some detail + shell$ mpirun --mca mpi_common_cuda_verbose 20 ... # more detail + shell$ mpirun --mca mpi_common_cuda_verbose 100 ... # most detail -Regardless of the situation, the CUDA device must be selected before the first -MPI call that requires a CUDA resource. When CUDA resources can be initialized -lazily, it is possible to use the aforementioned communicator-related operations -to query rank information and utilize that to select a GPU. +Developing CUDA-aware applications +---------------------------------- +Developing CUDA-aware applications is a broad topic, beyond the scope of +this document. Such applications often must account for +machine-specific details, including the number of GPUs per node and how +the GPUs are connected to the CPUs and to each other. With some +transports there are additional run-time decisions to make about which +CPU cores are used with which GPUs. -.. code-block:: c - - int local_rank = -1; - { - MPI_Comm local_comm; - MPI_Comm_split_type(MPI_COMM_WORLD, MPI_COMM_TYPE_SHARED, rank, MPI_INFO_NULL, &local_comm); - MPI_Comm_rank(local_comm, &local_rank); - MPI_Comm_free(&local_comm); - } - int num_devices = 0; - cudaGetDeviceCount(&num_devices); - cudaSetDevice(local_rank % num_devices); - -MPI internal CUDA resources are released during MPI_Finalize. Thus it is an -application error to call cudaDeviceReset before MPI_Finalize is called. +A good place to start is the `NVIDIA CUDA Toolkit Documentation +`_, including the `Programming Guide +`_ and the `Best +Practices Guide +`_. For +examples of CUDA-aware MPI applications, the `NVIDIA developer blog +`_ +and the `OSU Micro-Benchmarks +`_ are good references. diff --git a/docs/tuning-apps/accelerators/memkind.rst b/docs/tuning-apps/accelerators/memkind.rst index 7567d9f7e82..e414af4eacc 100644 --- a/docs/tuning-apps/accelerators/memkind.rst +++ b/docs/tuning-apps/accelerators/memkind.rst @@ -48,7 +48,7 @@ information to Open MPI at application launch: Asserting usage of memory kind when creating a Communicator =========================================================== -The following code-snipplet demonstrates how to assert that a +The following code snippet demonstrates how to assert that a communicator will only be used for ROCm device buffers: .. code:: c diff --git a/docs/tuning-apps/accelerators/rocm.rst b/docs/tuning-apps/accelerators/rocm.rst index b19ad98bb6d..5c78f78ef78 100644 --- a/docs/tuning-apps/accelerators/rocm.rst +++ b/docs/tuning-apps/accelerators/rocm.rst @@ -255,7 +255,7 @@ An example for configure UCC and Open MPI with ROCm is shown below: --with-ucx=/path/to/ucx-rocm-install \ --with-ucc=/path/to/ucc-rocm-install -To use the UCC component in an application requires setting some +Using the UCC component in an application requires setting some additional parameters: .. code-block:: diff --git a/docs/tuning-apps/benchmarking.rst b/docs/tuning-apps/benchmarking.rst index 0a555a2fdbb..caff1d79340 100644 --- a/docs/tuning-apps/benchmarking.rst +++ b/docs/tuning-apps/benchmarking.rst @@ -8,7 +8,7 @@ This documentation is by no means a definitive guide, but it does try to offer some suggestions for generating accurate, meaningful benchmarks. -#. Decide *exactly* what you are benchmarking and setup your system +#. Decide *exactly* what you are benchmarking and set up your system accordingly. For example, if you are trying to benchmark maximum performance, then many of the suggestions listed below are extremely relevant (be the only user on the systems and network in @@ -82,7 +82,7 @@ benchmarks. * Perform some "warmup" events first. Many MPI implementations (including Open MPI) |mdash| and other subsystems upon which the - MPI uses |mdash| may use "lazy" semantics to setup and maintain + MPI uses |mdash| may use "lazy" semantics to set up and maintain streams of communications. Hence, the first event (or first few events) may well take significantly longer than subsequent events. @@ -108,4 +108,3 @@ benchmarks. system configuration that you are benchmarking. Note, for example, all hardware and software characteristics (to include hardware, firmware, and software versions as appropriate). - diff --git a/docs/tuning-apps/collectives/components.rst b/docs/tuning-apps/collectives/components.rst index 921f7e12036..b828d9861ad 100644 --- a/docs/tuning-apps/collectives/components.rst +++ b/docs/tuning-apps/collectives/components.rst @@ -2,7 +2,7 @@ Available Collective Components =============================== Open MPI's ``coll`` framework provides a number of components -implementing collective communication, each of which target a +implementing collective communication, each of which targets a different environment or scenario. Some of these components may not be available depending on how Open MPI was compiled and what hardware is available on the system. A run-time decision based on each @@ -27,7 +27,7 @@ The following provides a list of components and their primary target scenario: between algorithms for each operation and message size. See :doc:`tuned` for more details. - ``ucc``: component using the `UCC library `_ - for collective operations. + for collective operations. See :doc:`ucc` for more details. - ``xhc``: shared memory collective component, employing hierarchical & topology-aware algorithms, with XPMEM for data transfers. See :doc:`xhc` for more details. @@ -36,13 +36,13 @@ The following provides a list of components and their primary target scenario: - ``accelerator``: component providing host-proxy algorithms for some collective operations using device buffers. - ``ftagree``: component providing fault-tolerant collective operations. - - ``inter``: component providing collective operaitons for inter-communicators. + - ``inter``: component providing collective operations for inter-communicators. - ``basic``: component providing basic algorithms, used as a fall-back component. - ``sync``: component used in scenarios where some nodes can be overrun with messages. This component can be used to insert synchronization points every *n-th* execution of a collective operations. - - ``portals4``: component targetting portals4 networks. + - ``portals4``: component targeting portals4 networks. Different component can and will be used for different collective operations, since no component is providing implementations for all diff --git a/docs/tuning-apps/collectives/index.rst b/docs/tuning-apps/collectives/index.rst index 2e49cae2d15..be30b5a574c 100644 --- a/docs/tuning-apps/collectives/index.rst +++ b/docs/tuning-apps/collectives/index.rst @@ -2,7 +2,7 @@ Collective operations ===================== Open MPI provides a number of components implementing collective -operations and significant flexbility to tune collective operations. +operations and significant flexibility to tune collective operations. This section documents the available components and gives for some components additional information on how to utilize them. @@ -11,5 +11,6 @@ components additional information on how to utilize them. components tuned + ucc acoll xhc diff --git a/docs/tuning-apps/collectives/ucc.rst b/docs/tuning-apps/collectives/ucc.rst new file mode 100644 index 00000000000..e212aa5ff74 --- /dev/null +++ b/docs/tuning-apps/collectives/ucc.rst @@ -0,0 +1,173 @@ +The ``ucc`` Component +===================== + +The ``ucc`` collective component uses the `Unified Collective +Communication (UCC) library `_ to +offload selected MPI collective operations to UCC. This component is +useful on systems where UCC has been configured for the target transport +or accelerator environment. + +Building with UCC +----------------- + +Open MPI must be configured with UCC support: + +.. code-block:: sh + + shell$ ./configure --with-ucc=/path/to/ucc-install + +If UCC support is explicitly requested and the UCC headers and library +cannot be found, ``configure`` aborts. The ``ucc`` component is disabled +when Open MPI is configured with progress thread support, because the UCC +driver does not currently support progress threads. + +Enabling the Component +---------------------- + +The component is not enabled by default. Enable it at run time and give +it a high enough priority to be selected: + +.. code-block:: sh + + shell$ mpirun --mca coll_ucc_enable 1 \ + --mca coll_ucc_priority 100 \ + -np 64 ./my_mpi_app + +The ``ucc`` component is considered only for intracommunicators whose +size is at least ``coll_ucc_np``. The default value of ``coll_ucc_np`` +is ``2``. + +UCC Layers and Protocols +------------------------ + +For each MPI communicator selected for UCC, Open MPI creates a UCC +``team``: the UCC group object used to initialize and execute collective +operations. Inside UCC, collective implementations are selected through +two kinds of layers: + +* Collective layers (CLs), such as ``basic`` and ``hier``, decide how a + collective is decomposed. +* Team layers (TLs), such as ``ucp``, ``self``, ``cuda``, ``nccl``, + ``rccl``, ``sharp``, and ``mlx5``, provide the underlying transport or + accelerator implementation. + +For example, the ``ucp`` TL uses UCX/UCP transports such as InfiniBand, +RoCE, and shared memory; ``sharp`` uses SHARP in-network collective +offload; and ``nccl`` or ``rccl`` can be used for GPU collectives on +CUDA or ROCm memory. + +The ``basic`` CL is the general-purpose layer. The ``hier`` CL can use +system hierarchy when it is available; for example, it may split work +across ``NODE`` and ``NET`` subgroups, plus the ``FULL`` group, and then +pipeline phases through different TLs. A typical hierarchical protocol +could use an intra-node reduction, an inter-node operation such as +SHARP, and an intra-node broadcast. + +The exact CLs, TLs, and algorithms available depend on how UCC was +built. Use UCC's own tools to inspect the installed library: + +.. code-block:: sh + + shell$ ucc_info -s # Show available CLs and TLs + shell$ ucc_info -A # Show supported collective algorithms + shell$ ucc_info -caf # Show UCC configuration variables + +Open MPI's ``coll_ucc_cls`` MCA parameter is passed to UCC as its +``CLS`` setting. It can be used to restrict team creation to specific +UCC collective layers, for example: + +.. code-block:: sh + + shell$ mpirun --mca coll_ucc_enable 1 \ + --mca coll_ucc_cls hier \ + ./my_mpi_app + +For lower-level TL tuning, use UCC environment variables such as +``UCC_TL__TUNE`` or a UCC configuration file. UCC scores TLs +based on factors including the collective type, message size, memory +type, and team size. + +Selecting Collective Operations +------------------------------- + +Use ``coll_ucc_cts`` to choose which collective operations the component +should provide. By default, the component enables all supported blocking +and nonblocking operations. + +.. code-block:: sh + + shell$ mpirun --mca coll_ucc_enable 1 \ + --mca coll_ucc_cts allreduce,iallreduce,bcast,ibcast \ + ./my_mpi_app + +Prefix the value with ``^`` to start from all supported operations and +disable specific operations from that set: + +.. code-block:: sh + + shell$ mpirun --mca coll_ucc_enable 1 \ + --mca coll_ucc_cts ^alltoall,ialltoall \ + ./my_mpi_app + +The supported operation names are: + +* ``barrier``, ``bcast``, ``allreduce``, ``alltoall``, ``alltoallv``, + ``allgather``, ``allgatherv``, ``reduce``, ``gather``, ``gatherv``, + ``reduce_scatter_block``, ``reduce_scatter``, ``scatterv``, and + ``scatter`` +* ``ibarrier``, ``ibcast``, ``iallreduce``, ``ialltoall``, + ``ialltoallv``, ``iallgather``, ``iallgatherv``, ``ireduce``, + ``igather``, ``igatherv``, ``ireduce_scatter_block``, + ``ireduce_scatter``, ``iscatterv``, and ``iscatter`` + +The aliases ``colls_b``, ``colls_i`` (or ``colls_nb``), and ``colls_p`` +select all blocking, nonblocking, and persistent collective operations, +respectively. Individual persistent collective operations can be +selected by adding the ``_init`` suffix to the blocking operation name, +for example ``allreduce_init``. + +Other MCA Parameters +-------------------- + +.. list-table:: + :header-rows: 1 + :widths: 30 15 55 + + * - Parameter + - Default + - Description + * - ``coll_ucc_enable`` + - ``0`` + - Enable or disable the component. + * - ``coll_ucc_priority`` + - ``10`` + - Component selection priority. + * - ``coll_ucc_verbose`` + - ``0`` + - Verbosity level for component logging. + * - ``coll_ucc_np`` + - ``2`` + - Minimum communicator size for enabling the component. + * - ``coll_ucc_cls`` + - UCC default + - Comma-separated list of UCC collective layers to use for team + creation, passed to UCC as ``CLS``. + * - ``coll_ucc_cts`` + - All supported blocking and nonblocking operations + - Comma-separated list of UCC collective types to enable. + +Verifying Selection +------------------- + +Use ``coll_base_verbose`` to check which collective component Open MPI +selects for each operation: + +.. code-block:: sh + + shell$ mpirun --mca coll_ucc_enable 1 \ + --mca coll_ucc_priority 100 \ + --mca coll_base_verbose 20 \ + ./my_mpi_app + +See :doc:`components` for more details about interpreting collective +component selection output. diff --git a/docs/tuning-apps/collectives/xhc.rst b/docs/tuning-apps/collectives/xhc.rst index 8ac345649c5..a74d81698f0 100644 --- a/docs/tuning-apps/collectives/xhc.rst +++ b/docs/tuning-apps/collectives/xhc.rst @@ -167,7 +167,7 @@ Main * - coll_xhc__hierarchy - bcast/barrier: ``numa,socket`` (all)reduce: ``l3,numa,socket`` - - Topological features to consider for XHC's hierarchy, specifially for + - Topological features to consider for XHC's hierarchy, specifically for this primitive. Mutually exclusive with the respective non-specific parameter. diff --git a/docs/tuning-apps/fault-tolerance/checkpoint-restart.rst b/docs/tuning-apps/fault-tolerance/checkpoint-restart.rst index d904c7b1efa..248be721a80 100644 --- a/docs/tuning-apps/fault-tolerance/checkpoint-restart.rst +++ b/docs/tuning-apps/fault-tolerance/checkpoint-restart.rst @@ -7,7 +7,7 @@ Old versions of Open MPI (starting from v1.3 series) had support for the transparent, coordinated checkpointing and restarting of MPI processes (similar to LAM/MPI). -Open MPI supported both the the `BLCR `_ +Open MPI supported both the `BLCR `_ checkpoint/restart system and a "self" checkpointer that allows applications to perform their own checkpoint/restart functionality while taking advantage of the Open MPI checkpoint/restart infrastructure. diff --git a/docs/tuning-apps/large-clusters/index.rst b/docs/tuning-apps/large-clusters/index.rst index ff58b2b7e13..8ed80bb842b 100644 --- a/docs/tuning-apps/large-clusters/index.rst +++ b/docs/tuning-apps/large-clusters/index.rst @@ -5,6 +5,10 @@ Setting up a large cluster to run MPI applications can be challenging and full of many decisions. The following sections include guidance for installing and tuning Open MPI on large clusters. +Also see :doc:`Deciding what to install +` for +build-time considerations that can affect large-cluster deployments. + .. toctree:: :maxdepth: 1 diff --git a/docs/tuning-apps/large-clusters/libraries.rst b/docs/tuning-apps/large-clusters/libraries.rst index 8673c45de14..8e53e9d04d0 100644 --- a/docs/tuning-apps/large-clusters/libraries.rst +++ b/docs/tuning-apps/large-clusters/libraries.rst @@ -24,7 +24,7 @@ place the Open MPI libraries on networked file systems: cluster! Doing so will lead to significant network traffic and delayed start times, especially on clusters with a large number of nodes. Instead, be sure to :ref:`configure your build - ` with + ` with ``--disable-dlopen``. This will include the DSO's in the main libraries, resulting in much faster startup times. diff --git a/docs/tuning-apps/large-clusters/reduce-startup-time.rst b/docs/tuning-apps/large-clusters/reduce-startup-time.rst index 01751330bf6..e72697777ef 100644 --- a/docs/tuning-apps/large-clusters/reduce-startup-time.rst +++ b/docs/tuning-apps/large-clusters/reduce-startup-time.rst @@ -1,8 +1,6 @@ Reducing startup time for jobs ============================== -.. error:: TODO This whole section needs to be checked. - There are several ways to reduce the startup time on large clusters. Some of them are described on this page. We continue to work on making startup even faster, especially on the large clusters coming diff --git a/docs/tuning-apps/large-clusters/reduce-wireup-time.rst b/docs/tuning-apps/large-clusters/reduce-wireup-time.rst index 349f0500a77..1634b362334 100644 --- a/docs/tuning-apps/large-clusters/reduce-wireup-time.rst +++ b/docs/tuning-apps/large-clusters/reduce-wireup-time.rst @@ -2,32 +2,67 @@ Reducing wireup time ==================== Open MPI's run-time uses an *out-of-band* (OOB) communication -subsystem to pass messages during the launch, initialization, and -termination stages for the job. These messages allow ``mpirun`` to tell -its daemons what processes to launch, and allow the daemons in turn to -forward stdio to ``mpirun``, update ``mpirun`` on process status, etc. - -The OOB uses TCP sockets for its communication, with each daemon -opening a socket back to ``mpirun`` upon startup. In a large cluster, -this can mean thousands of connections being formed on the node where -``mpirun`` resides, and requires that ``mpirun`` actually process all -these connection requests. ``mpirun`` defaults to processing -connection requests sequentially |mdash| so on large clusters, a -backlog can be created that can cause remote daemons to timeout -waiting for a response. - -Fortunately, Open MPI provides an alternative mechanism for processing -connection requests that helps alleviate this problem. Setting the MCA -parameter ``oob_tcp_listen_mode`` to ``listen_thread`` causes -``mpirun`` to startup a separate thread dedicated to responding to -connection requests. Thus, remote daemons receive a quick response to -their connection request, allowing ``mpirun`` to deal with the message -as soon as possible. - -.. error:: TODO This seems very out of date. We should have content - about PMIx instant on. - -This parameter can be included in the default MCA parameter file, -placed in the user's environment, or added to the ``mpirun`` command -line. See :ref:`this FAQ entry ` -for more details on how to set MCA parameters. +subsystem to pass control messages during the launch, initialization, +and termination stages of a job. These messages allow ``mpirun`` to +tell its daemons which processes to launch, and allow the daemons in +turn to forward stdio to ``mpirun``, update ``mpirun`` on process +status, and so on. + +.. note:: Since Open MPI 5.0, the run-time environment |mdash| + including the OOB subsystem and the ``mpirun`` launcher |mdash| is + provided by :ref:`PRRTE `, not + by Open MPI itself. + +The OOB uses TCP sockets, but the daemons do *not* all connect back to +``mpirun``. Instead, PRRTE arranges them in a *radix tree*: each daemon +connects only to its parent in that tree, and traffic bound for +``mpirun`` is relayed up the tree hop by hop. The fan-out of the tree +is set by the ``rml_base_radix`` PRTE MCA parameter, which defaults to +64 |mdash| so no matter how large the job is, only the first 64 daemons +connect directly to ``mpirun``, and each daemon below them accepts at +most 64 connections from its own children. + +Similarly, when the ``ssh`` launcher is used, the daemons are also +*launched* through a tree: each daemon ``ssh``\ s the next level of +daemons into existence, rather than ``mpirun`` launching every daemon +itself. (This can be disabled by setting the ``plm_ssh_no_tree_spawn`` +PRTE MCA parameter to 1, but there is rarely a reason to do so on a +large cluster.) + +Together, these keep the connection and launch load on the node where +``mpirun`` resides bounded rather than growing with the job size. +``mpirun`` additionally services the connections it does receive on a +dedicated listener thread, so remote daemons get a prompt response. +This behavior is built in and requires no tuning. + +PMIx "Instant On" +----------------- + +A larger contributor to startup cost at scale is the exchange of +per-process communication endpoint information (the "modex") that MPI +processes historically performed during ``MPI_Init`` in order to wire +up their point-to-point connections. As the job size grows, this global +exchange grows with it. + +Open MPI, PRRTE, and PMIx support the PMIx *Instant On* capability, +which moves this work out of ``MPI_Init``. Rather than having the +processes exchange endpoint information among themselves at run time, +the launcher (PRRTE) and the fabric/network software collect the +necessary network addressing information as part of the launch and +pre-position it in each process's PMIx data store before the process +starts. Each process can then look up any peer's endpoint information +locally, so no global wireup exchange is required |mdash| the processes +start already "wired up." + +Instant On is provided cooperatively by PMIx, PRRTE, and the +network/fabric components. Whether |mdash| and how much of |mdash| it +is available therefore depends on the interconnect and its software +stack, rather than on a single user-settable MCA parameter. See the +:ref:`PMIx and PRRTE section ` for +more information about these projects. + +For applications with sparse communication patterns, you can also +reduce or eliminate the up-front modex with the +``pmix_base_async_modex`` MCA parameter, which defers endpoint lookups +until first message. See :doc:`reduce-startup-time` for details on that +and other launch-time options. diff --git a/docs/tuning-apps/large-clusters/static-cluster-config.rst b/docs/tuning-apps/large-clusters/static-cluster-config.rst index b40cdc7bffe..d063dcbe3fb 100644 --- a/docs/tuning-apps/large-clusters/static-cluster-config.rst +++ b/docs/tuning-apps/large-clusters/static-cluster-config.rst @@ -1,43 +1,57 @@ Static cluster configurations ============================= -.. error:: This entire section needs to be checked. - Clusters rarely change from day-to-day, and large clusters rarely change at all. If you know your cluster's configuration, there are several steps you can take to both reduce Open MPI's memory footprint and reduce the launch time of large-scale applications. These steps use a combination of build-time configuration options to eliminate -components |mdash| thus eliminating their libraries and avoiding -unnecessary component open/close operations |mdash| as well as -run-time MCA parameters to specify what modules to use by default for -most users. +components |mdash| making Open MPI's libraries smaller and avoiding the +work of registering and querying components that will never be used +|mdash| as well as run-time MCA parameters to select the components to +use by default for most users. + +.. note:: Since Open MPI v5.0, components are, by default, compiled + directly into Open MPI's core libraries (for example, + ``libopen-pal`` and ``libmpi``) rather than being built as separate + dynamically-loaded plugins (DSOs). Even when a component is compiled + in this way, it is still registered and queried at run time so that + each framework can decide which of its components to use. The + techniques on this page |mdash| not building components you do not + need, and choosing framework defaults up front |mdash| therefore + still reduce both memory footprint and startup work. See + :ref:`Components ("plugins"): static or DSO? + ` for more about the static and + DSO build models and how to select between them. -One way to save memory is to avoid building components that will -actually never be selected by the system. Unless MCA parameters -specify which components to open, built components are always opened -and tested as to whether or not they should be selected for use. If -you know that a component can build on your system, but due to your -cluster's configuration will never actually be selected, then it is -best to simply configure OMPI to not build that component by using the -``--enable-mca-no-build`` configure option. +One way to save memory is to avoid building components that will never +be selected on your system. Every component that is built into Open +MPI is registered and tested at run time to decide whether it should be +selected, unless MCA parameters restrict which components are +considered. If you know that a component can build on your system but, +due to your cluster's configuration, will never actually be selected, +then it is best to configure Open MPI to not build that component by +using the ``--enable-mca-no-build`` configure option. -For example, if you know that your system will only utilize the -``ob1`` component of the PML framework, then you can ``no_build`` all -the others. This not only reduces memory in the libraries, but also -reduces memory footprint that is consumed by Open MPI opening all the -built components to see which of them can be selected to run. +For example, if you know that your system will only use the ``ob1`` +component of the PML framework, then you can ``no_build`` all the +others. This both makes the resulting libraries smaller and avoids the +startup cost of registering and querying components that would never be +used. -In some cases, however, a user may optionally choose to use a -component other than the default. For example, you may want to build -all of the PRRTE ``routed`` framework components, even though the vast -majority of users will simply use the default ``debruijn`` +In some cases, however, you may want to keep a framework's other +components built |mdash| so that users retain the option to select them +|mdash| even though most users will use a single default. For example, +you may want to keep all of the ``pml`` framework's components built (so +that users can still select ``ucx`` or ``cm`` when appropriate), even +though the vast majority of users will use the default ``ob1`` component. This means you have to allow the system to build the other components, even though they may rarely be used. You can still save launch time and memory, though, by setting the -``routed=debruijn`` MCA parameter in the default MCA parameter file. -This causes OMPI to not open the other components during startup, but -allows users to override this on their command line or in their -environment so no functionality is lost |mdash| you just save some +``pml = ob1`` MCA parameter in the default MCA parameter file. This +tells Open MPI to consider only the ``ob1`` component at startup, rather +than registering and querying every ``pml`` component, but still allows +users to override the setting on their command line or in their +environment |mdash| so no functionality is lost, and you save some memory and time. diff --git a/docs/tuning-apps/mpi-io.rst b/docs/tuning-apps/mpi-io.rst index cf6327f9efd..ed01c961855 100644 --- a/docs/tuning-apps/mpi-io.rst +++ b/docs/tuning-apps/mpi-io.rst @@ -46,6 +46,13 @@ parameters available for the OMPIO ``io``, ``fcoll``, ``fs``, shell$ ompi_info --param fbtl all --level 9 shell$ ompi_info --param sharedfp all --level 9 +.. _label-ompio-mpi-info-hints: + +OMPIO MPI_Info hints +-------------------- + +.. include:: /tuning-apps/_include/ompio-mpi-info-hints.rst + OMPIO sub-framework components ------------------------------ @@ -158,15 +165,15 @@ file on a parallel file system. Note, that many file systems only allow changing these setting upon file creation, i.e. modifying these values for an already existing file might not be possible. -#. ``fs_lustre_stripe_size``: Sets the number of storage servers for a +#. ``fs_lustre_stripe_size``: Sets the stripe size, in bytes, for a new file on a Lustre file system. If not set, system default will be used. Note that this parameter can also be set through the - ``stripe_size`` MPI Info value. + ``striping_unit`` or ``stripe_size`` MPI Info value. -#. ``fs_lustre_stripe_width``: Sets the size of an individual block - for a new file on a Lustre file system. If not set, system default - will be used. Note that this parameter can also be set through the - ``stripe_width`` MPI Info value. +#. ``fs_lustre_stripe_width``: Sets the number of storage servers for + a new file on a Lustre file system. If not set, system default will + be used. Note that this parameter can also be set through the + ``striping_factor`` or ``stripe_width`` MPI Info value. Using GPU device buffers in MPI File I/O operations ---------------------------------------------------- @@ -207,7 +214,7 @@ individual files are merged into the actual output file, using the time stamps as the main criteria. The component has certain limitations and restrictions, such as its -relience on the synchronization clocks on the individual cluster nodes +reliance on the synchronization clocks on the individual cluster nodes to determine the order between entries in the final file, which might lead to some deviations compared to the actual calling sequence. diff --git a/docs/tuning-apps/networking/ib-and-roce.rst b/docs/tuning-apps/networking/ib-and-roce.rst index 7b722faf9a4..ff8c61d6506 100644 --- a/docs/tuning-apps/networking/ib-and-roce.rst +++ b/docs/tuning-apps/networking/ib-and-roce.rst @@ -1,188 +1,166 @@ -InifiniBand / RoCE support -========================== - -.. error:: TODO This section needs to be converted from FAQ Q&A style - to regular documentation style. - -How are InfiniBand / RoCE devices supported in Open MPI? --------------------------------------------------------- +InfiniBand / RoCE support +========================= Open MPI's support for InfiniBand and RoCE devices has changed over -time. - -In the Open MPI |ompi_series| series, InfiniBand and RoCE devices are -supported via the UCX (``ucx``) PML. +time. In the Open MPI |ompi_series| series, InfiniBand and RoCE +devices are supported via the UCX (``ucx``) PML. .. note:: Prior versions of Open MPI also included the ``openib`` BTL for InfiniBand and RoCE devices. Open MPI |ompi_series| no longer includes the ``openib`` BTL. -///////////////////////////////////////////////////////////////////////// - -What is UCX? ------------- - -`UCX `_ is an open-source optimized -communication library which supports multiple networks, including -RoCE, InfiniBand, uGNI, TCP, shared memory, and others. UCX -mixes-and-matches transports and protocols which are available on the -system to provide optimal performance. It also has built-in support -for GPU transports (with CUDA and ROCm providers) which lets -RDMA-capable transports access the GPU memory directly. +UCX +--- -///////////////////////////////////////////////////////////////////////// +`UCX `_ is an open-source, optimized +communication library that supports multiple networks, including RoCE, +InfiniBand, uGNI, TCP, shared memory, and others. UCX +mixes-and-matches the transports and protocols available on the system +to provide optimal performance. It also has built-in support for GPU +transports (with CUDA and ROCm providers), which lets RDMA-capable +transports access GPU memory directly. -How do I use UCX with Open MPI? -------------------------------- +Using UCX with Open MPI +----------------------- If Open MPI includes UCX support, then UCX is enabled and selected by default for InfiniBand and RoCE network devices; typically, no additional parameters are required. In this case, the network port -with the highest bandwidth on the system will be used for inter-node -communication, and shared memory will be used for intra-node -communication. To select a specific network device to use (for -example, ``mlx5_0`` device port 1): +with the highest bandwidth on the system is used for inter-node +communication, and shared memory is used for intra-node communication. + +To select a specific network device to use (for example, the +``mlx5_0`` device, port 1): .. code-block:: shell$ mpirun -x UCX_NET_DEVICES=mlx5_0:1 ... -It's also possible to force using UCX for MPI point-to-point and +It is also possible to force the use of UCX for MPI point-to-point and one-sided operations: .. code-block:: shell$ mpirun --mca pml ucx --mca osc ucx ... -For OpenSHMEM, in addition to the above, it's possible to force using -UCX for remote memory access and atomic memory operations: +For OpenSHMEM, in addition to the above, you can force the use of UCX +for remote memory access and atomic memory operations: .. code-block:: shell$ mpirun --mca pml ucx --mca osc ucx --mca scoll ucx --mca atomic ucx ... -///////////////////////////////////////////////////////////////////////// - -What is RDMA over Converged Ethernet (RoCE)? --------------------------------------------- +RDMA over Converged Ethernet (RoCE) +----------------------------------- -RoCE (which stands for *RDMA over Converged Ethernet*) provides -InfiniBand native RDMA transport on top of lossless Ethernet data -links. - -Since we're talking about Ethernet, there's no Subnet Manager, no -Subnet Administrator, no InfiniBand SL, nor any other InfiniBand -Subnet Administration parameters. +RoCE (RDMA over Converged Ethernet) provides the InfiniBand native RDMA +transport on top of lossless Ethernet data links. Because the +underlying link is Ethernet, there is no Subnet Manager, no Subnet +Administrator, no InfiniBand Service Level, and none of the other +InfiniBand Subnet Administration parameters. Connection management in RoCE is based on the OFED RDMACM (RDMA Connection Manager) service: -* The OS IP stack is used to resolve remote (IP,hostname) tuples to - a DMAC. -* The outgoing Ethernet interface and VLAN are determined according - to this resolution. +* The OS IP stack is used to resolve remote (IP, hostname) tuples to a + DMAC. +* The outgoing Ethernet interface and VLAN are determined according to + this resolution. * The appropriate RoCE device is selected accordingly. -* Network parameters (such as MTU, SL, timeout) are set locally by +* Network parameters (such as MTU, SL, and timeout) are set locally by the RDMACM in accordance with kernel policy. -///////////////////////////////////////////////////////////////////////// +Running over RoCE +----------------- -How do I know what MCA parameters are available for tuning MPI performance? ---------------------------------------------------------------------------- - -The ``ompi_info`` command can display all the parameters available for -any Open MPI component. For example: +To use RoCE with the UCX PML, specify the relevant Ethernet port with +the ``UCX_NET_DEVICES`` environment variable. For example: .. code-block:: - shell$ ompi_info --param pml ucx --level 9 - -.. important:: Unlike most other Open MPI components, the UCX PML - mainly uses environment variables for run-time tuning - |mdash| not Open MPI MCA parameters. Consult `the UCX - documentation - `_ for details - about what environment variables are available. - -///////////////////////////////////////////////////////////////////////// - -How do I tell Open MPI which IB Service Level to use? ------------------------------------------------------ + shell$ mpirun --mca pml ucx -x UCX_NET_DEVICES=mlx5_0:1 ... -In order to tell the UCX PML which SL to use, the IB SL must be -specified using the ``UCX_IB_SL`` environment variable. For example: +UCX selects IPv4 RoCEv2 by default. If different behavior is needed, +you can set a specific GID index: .. code-block:: - shell$ mpirun --mca pml ucx -x UCX_IB_SL=N ... + shell$ mpirun --mca pml ucx -x UCX_NET_DEVICES=mlx5_0:1 -x UCX_IB_GID_INDEX=1 ... -The value of IB SL ``N`` should be between 0 and 15, where 0 is the -default value. +Selecting the InfiniBand Service Level +-------------------------------------- -///////////////////////////////////////////////////////////////////////// +To tell the UCX PML which InfiniBand Service Level (SL) to use, specify +it with the ``UCX_IB_SL`` environment variable. For example: -How do I run Open MPI over RoCE? --------------------------------- +.. code-block:: -In order to use RoCE with the UCX PML, the relevant Ethernet port must -be specified using the ``UCX_NET_DEVICES`` environment variable. For -example: + shell$ mpirun --mca pml ucx -x UCX_IB_SL=N ... -.. code-block:: +The value ``N`` should be between 0 and 15, where 0 is the default. - shell$ mpirun --mca pml ucx -x UCX_NET_DEVICES=mlx5_0:1 ... +Tuning MPI performance +---------------------- -UCX selects IPv4 RoCEv2 by default. If different behavior is needed, -you can set a specific GID index: +The ``ompi_info`` command can display all the parameters available for +any Open MPI component. For example: .. code-block:: - shell$ mpirun --mca pml ucx -x UCX_NET_DEVICES=mlx5_0:1 -x UCX_IB_GID_INDEX=1 ... + shell$ ompi_info --param pml ucx --level 9 -///////////////////////////////////////////////////////////////////////// +.. important:: Unlike most other Open MPI components, the UCX PML mainly + uses environment variables for run-time tuning |mdash| + not Open MPI MCA parameters. Consult `the UCX + documentation `_ for + details about which environment variables are available. .. _faq-ib-troubleshoot-label: -I'm experiencing a problem with Open MPI on my InfiniBand / RoCE network; how do I troubleshoot and get help? -------------------------------------------------------------------------------------------------------------- +Troubleshooting and getting help +-------------------------------- -In order for us to help you, it is *most* helpful if you can run a few -steps before sending an e-mail to both perform some basic -troubleshooting and provide us with enough information about your -environment to help you. Please include answers to the following -questions in your e-mail: +If you are experiencing a problem with Open MPI on an InfiniBand or +RoCE network, it is *most* helpful if you run a few steps before +sending an e-mail, both to perform some basic troubleshooting and to +provide enough information about your environment for others to help +you. Please include answers to the following questions in your e-mail: -#. Which UCX and OpenFabrics version are you running? Please specify - where you got the software from (e.g., from the OpenFabrics and/or - UCX community web sites, already included in your Linux +#. Which UCX and OpenFabrics versions are you running? Please specify + where you obtained the software (for example, from the OpenFabrics + and/or UCX community web sites, already included in your Linux distribution, downloaded from NVIDIA's web site, etc.). -#. What distro and version of Linux are you running? What is your - kernel version? +#. Which distribution and version of Linux are you running? What is + your kernel version? #. What is the output of the ``ibv_devinfo`` command on a known "good" node and a known "bad" node? - .. note:: There must be at least one port listed as "PORT_ACTIVE" + .. note:: There must be at least one port listed as ``PORT_ACTIVE`` for Open MPI to work. If there is not at least one - PORT_ACTIVE port, something is wrong with your InfiniBand - / RoCE environment and Open MPI will not be able to run. + ``PORT_ACTIVE`` port, something is wrong with your + InfiniBand / RoCE environment, and Open MPI will not be + able to run. #. What is the output of the ``ifconfig`` command on a known "good" node and a known "bad" node? - .. note:: Note that some Linux distributions do not put - ``ifconfig`` in the default path for normal users; look - for it at ``/sbin/ifconfig`` or ``/usr/sbin/ifconfig``. + .. note:: Some Linux distributions do not put ``ifconfig`` in the + default path for normal users; look for it at + ``/sbin/ifconfig`` or ``/usr/sbin/ifconfig``. #. If running under Bourne shells, what is the output of the ``ulimit - -l`` command? - - If running under C shells, what is the output of the ``limit | grep - memorylocked`` command? - - .. note:: If the value is not ``unlimited``, ................. - - .. error:: TODO Would be good to point to some UCX/vendor docs here - about setting memory limits (rather than reproducing this - information ourselves). + -l`` command? If running under C shells, what is the output of the + ``limit | grep memorylocked`` command? + + This reports the maximum amount of memory that a user process is + allowed to lock (pin) into physical RAM. InfiniBand and RoCE + transfers use RDMA, which requires the communication buffers to be + locked so that the network hardware can access them directly. If + the reported value is not ``unlimited`` (or is not large enough for + your application's buffers), memory registration can fail and MPI + jobs may abort or perform poorly. Raising this limit is a + system-configuration task; see the `UCX documentation + `__ and your OpenFabrics or + vendor documentation for how to allow locked memory on your system. diff --git a/docs/tuning-apps/networking/iwarp.rst b/docs/tuning-apps/networking/iwarp.rst index a24b3c73fbb..79293fa58e9 100644 --- a/docs/tuning-apps/networking/iwarp.rst +++ b/docs/tuning-apps/networking/iwarp.rst @@ -6,7 +6,7 @@ Open MPI's support for iWARP devices has changed over time. In the Open MPI |ompi_series| series, iWARP devices are supported via the OFI (``ofi``) MTL via the CM (``cm``) PML. -.. note:: Prior versions of Open MPI supported iWARP devies via the +.. note:: Prior versions of Open MPI supported iWARP devices via the ``openib`` BTL. Open MPI |ompi_series| no longer includes the ``openib`` BTL. diff --git a/docs/tuning-apps/networking/ofi.rst b/docs/tuning-apps/networking/ofi.rst index 3f7563985d1..e3819fa6cc3 100644 --- a/docs/tuning-apps/networking/ofi.rst +++ b/docs/tuning-apps/networking/ofi.rst @@ -1,15 +1,9 @@ OpenFabrics Interfaces (OFI) / Libfabric support ================================================ -.. error:: TODO This section needs to be converted from FAQ Q&A style - to regular documentation style. - -What is OFI / Libfabric? ------------------------- - "OFI" stands for the `OpenFabrics Interfaces `_, which are implemented in the ``libfabric`` -library. These two terms are typically used interchangeably. +library; the two terms are typically used interchangeably. Open MPI supports many different underlying networks via Libfabric, including (but not limited to): @@ -19,117 +13,111 @@ including (but not limited to): * Cornelis Networks Omni-Path * HPE Slingshot 11 -In general, the OFI-based components in Open MPI will auto-select -themselves as appropriate at run time. - -That being said, additional questions are available in this FAQ -section to provide more information about specific OFI-based network -types and support. - -///////////////////////////////////////////////////////////////////////// - -What are the Libfabric (OFI) components in Open MPI? ----------------------------------------------------- - -Open MPI has three main components for Libfabric (a.k.a., OFI) -communications: - -#. ``ofi`` MTL: Available since Open MPI v1.10, this component is used - with the ``cm`` PML and is used for two-sided MPI communication - (e.g., ``MPI_SEND`` and ``MPI_RECV``). - - The ``ofi`` MTL requires that the Libfabric provider support - reliable datagrams with ordered tagged messaging (specifically: - ``FI_EP_RDM`` endpoints, ``FI_TAGGED`` capabilities, and - ``FI_ORDER_SAS`` ordering). - -#. ``ofi`` BTL: Available since Open MPI v4.0.0, this component is primarily - intended for one-sided MPI communications (e.g., ``MPI_PUT``). It - can also support BTL send/recv operations. - ``ofi`` BTL requires that the Libfabric provider support reliable - datagrams, RMA and atomic operations, and remote atomic completion - notifications (specifically: ``FI_EP_RDM`` endpoints, ``FI_RMA`` - and ``FI_ATOMIC`` capabilities, and ``FI_DELIVERY_COMPLETE`` op - flags). - -#. ``usnic`` BTL: This BTL is used exclusively with Cisco usNIC-based - networks. It will auto-select itself over the other OFI-based - components when run with Cisco usNIC-based networks. - -See each Lifabric provider man page (e.g., fi_sockets(7)) to understand which -provider will work for each of the above-listed Open MPI components. Some -providers may require to be used with one of the Libfabric utility providers; -for example, the verbs provider needs to be paired with utility provider -``ofi_rxm`` to provide reliable datagram endpoint support (``verbs;ofi_rxm``). - -Both components have MCA parameters to specify the Libfabric provider(s) that -will be included/excluded in the selection process. For example: +In general, the OFI-based components in Open MPI auto-select themselves +as appropriate at run time. The remaining sections describe the +OFI-based components and the tuning options for specific OFI-based +network types. + +Libfabric (OFI) components in Open MPI +-------------------------------------- + +Open MPI has three main components for Libfabric (OFI) communication: + +#. The ``ofi`` MTL, available since Open MPI v1.10, is used with the + ``cm`` PML for two-sided MPI communication (for example, + ``MPI_Send`` and ``MPI_Recv``). It requires that the Libfabric + provider support reliable datagrams with ordered tagged messaging + |mdash| specifically, ``FI_EP_RDM`` endpoints, ``FI_TAGGED`` + capabilities, and ``FI_ORDER_SAS`` ordering. + +#. The ``ofi`` BTL, available since Open MPI v4.0.0, is primarily + intended for one-sided MPI communication (for example, + ``MPI_Put``), but can also support BTL send/receive operations. It + requires that the Libfabric provider support reliable datagrams, RMA + and atomic operations, and remote atomic completion notifications + |mdash| specifically, ``FI_EP_RDM`` endpoints, ``FI_RMA`` and + ``FI_ATOMIC`` capabilities, and the ``FI_DELIVERY_COMPLETE`` op + flag. + +#. The ``usnic`` BTL is used exclusively with Cisco usNIC-based + networks. It auto-selects itself over the other OFI-based + components when run on such networks. + +Consult each Libfabric provider's man page (for example, +``fi_sockets(7)``) to understand which provider will work for each of +the components above. Some providers must be paired with a Libfabric +utility provider; for example, the verbs provider needs the ``ofi_rxm`` +utility provider to supply reliable datagram endpoint support +(``verbs;ofi_rxm``). + +Each component has MCA parameters that specify the Libfabric +provider(s) to include in or exclude from the selection process. For +example: .. code-block:: - shell$ mpirun --mca pml cm --mca mtl ofi --mca mtl_ofi_provider_include psm2 mpi_hello + shell$ mpirun --mca pml cm --mca mtl ofi \ + --mca mtl_ofi_provider_include psm2 ./mpi_hello -In addition, each component has specific parameters for each one; see -``ompi_info --param -level 9`` for a full -list. For example: +Each component also has its own component-specific parameters; use +``ompi_info`` to list them. For example: .. code-block:: shell$ ompi_info --param mtl ofi --level 9 -.. important:: When using the HPE CXI provider and ``mpirun`` as the job launcher, - it is recommended that the PRTE ``ras_base_launch_orted_on_hn`` MCA parameter be set to 1. - This can be done by adding ``--prtemca ras_base_launch_orted_on_hn 1`` to the job launch - command line. This ensures that MPI processes launched on the first node of - an allocation are able to use the CXI provider. - -For more information refer to the `Libfabric web site -`_. +.. important:: When using the HPE CXI provider with ``mpirun`` as the + job launcher, it is recommended to set the PRRTE + ``ras_base_launch_orted_on_hn`` MCA parameter to 1 by + adding ``--prtemca ras_base_launch_orted_on_hn 1`` to + the job launch command line. This ensures that MPI + processes launched on the first node of an allocation + are able to use the CXI provider. -///////////////////////////////////////////////////////////////////////// +For more information, refer to the `Libfabric web site +`_. -Omni-Path: How can the multi-rail settings be adjusted if multiple HFI (Host Fabric Interface) cards are installed on the system? ---------------------------------------------------------------------------------------------------------------------------------- +Omni-Path: multi-rail with multiple HFI cards +--------------------------------------------- -Multi-Rail feature allows a process to use multiple HFIs to transfer a message -to improve message bandwidth. The PSM2 library handles the support for multi-rail -which is off by default. The multi-rail settings can be modified using the -following environment variables: +The multi-rail feature lets a single process use multiple Host Fabric +Interface (HFI) cards to transfer a message, improving message +bandwidth. The PSM2 library provides multi-rail support, which is off +by default. The behavior is controlled with the following environment +variables: -* ``PSM2_MULTIRAIL=[0,1,2] ]``: 0=Disabled, 1=Enable across all HFIs in the - system, 2=Enable multi-rail within a NUMA node. -* ``PSM2_MULTIRAIL_MAP=unit:port,unit:port...`` +* ``PSM2_MULTIRAIL=[0,1,2]``: 0 disables multi-rail, 1 enables it + across all HFIs in the system, and 2 enables multi-rail within a NUMA + node. +* ``PSM2_MULTIRAIL_MAP=unit:port,unit:port,...`` -The variables above may be included in the ``mpirun`` command line or in -the environment. For example: +These variables may be set on the ``mpirun`` command line or in the +environment. For example: .. code-block:: - shell$ mpirun -mca mtl [psm2|ofi] -x PSM2_MULTIRAIL=1 -n 2 -H host1,host2 ./a.out - -.. note:: When using the OFI MTL, please ensure that the PSM2 OFI - provider is used for communication with OPA devices. - -///////////////////////////////////////////////////////////////////////// + shell$ mpirun --mca mtl [psm2|ofi] -x PSM2_MULTIRAIL=1 \ + -n 2 -H host1,host2 ./a.out -Omni-Path: What is Multi-HFI support in PSM2 and how does it differ from multi-rail? ------------------------------------------------------------------------------------- +.. note:: When using the OFI MTL, ensure that the PSM2 OFI provider is + used for communication with OPA devices. -Multi-HFI support is intended to describe the use of multiple HFIs in -a system among MPI ranks local to a node in order to load-balance the -hardware resources. It differs from the Multi-Rail feature, which is -intended to allow a single process to use all HFIs in the system. For -an MPI job with multiple processes on a single node, the default PSM2 -behavior depends on the affinity settings of the MPI process. The PSM2 -library defaults to using the HFI (Host Fabric Interface) that is in -the same NUMA node as that of the MPI process. +Omni-Path: multi-HFI support in PSM2 +------------------------------------ -Users can restrict access to a single HFI using the environment variable: +Multi-HFI support describes the use of multiple HFIs in a system among +the MPI ranks local to a node, in order to load-balance the hardware +resources. It differs from the multi-rail feature, which allows a +single process to use all HFIs in the system. For an MPI job with +multiple processes on a single node, the default PSM2 behavior depends +on the affinity settings of the MPI process: the PSM2 library defaults +to using the HFI that is in the same NUMA node as the MPI process. -* ``HFI_UNIT=N``: valid values of N are 0,1,2 and 3 +Users can restrict a process to a single HFI with an environment +variable: -More details can be found on the PSM2 Programmer's Guide and the Omni-Path -Fabric Performance Tuning Guide. +* ``HFI_UNIT=N``: valid values of ``N`` are 0, 1, 2, and 3. -Please see the `Cornelis Networks Customer Center `_ -for more details. +More details can be found in the PSM2 Programmer's Guide and the +Omni-Path Fabric Performance Tuning Guide; see the `Cornelis Networks +Customer Center `_. diff --git a/docs/tuning-apps/networking/tcp.rst b/docs/tuning-apps/networking/tcp.rst index 7b87546596c..60ddb65e214 100644 --- a/docs/tuning-apps/networking/tcp.rst +++ b/docs/tuning-apps/networking/tcp.rst @@ -1,126 +1,99 @@ TCP === -.. error:: TODO This section needs to be converted from FAQ Q&A style - to regular documentation style. +Using the TCP BTL for MPI messages +---------------------------------- -How do I specify to use the IP network for MPI messages? --------------------------------------------------------- +Open MPI generally uses the ``tcp`` BTL automatically when: -Open MPI will generally automatically use the ``tcp`` BTL when: +#. The ``tcp`` BTL is available at run time (which it should be on most + POSIX-like systems), and +#. A higher-performance network is not available. -#. The ``tcp`` BTL is available at run time (which it should be on - most POSIX-like systems), and -#. A higher-performance network is not available +When the ``tcp`` BTL is used, it is typically also used (automatically) +with the ``self`` and ``sm`` BTLs for process-loopback and node-loopback +communication, respectively. -When the ``tcp`` BTL is used, it is typically also (automatically) -used with the ``self`` and ``sm`` BTLs for process-loopback and -node-loopback communication, respectively. - -If you want to guarantee that the ``tcp``, ``sm``, and ``self`` BTLs -are used, you can explicitly specify them on the ``mpirun`` command -line: +To guarantee that the ``tcp``, ``sm``, and ``self`` BTLs are used, you +can specify them explicitly on the ``mpirun`` command line: .. code-block:: sh shell$ mpirun --mca pml ob1 --mca btl tcp,sm,self ... .. warning:: Failure to specify the ``sm`` BTL will likely result in - lower performance when Open MPI uses the TCP network - stack to send to peers on the same host. - -.. warning:: Failure to specify the ``self`` BTL may result in Open - MPI being unable to complete send-to-self scenarios - (meaning that your program will run fine until a process - tries to send to itself). - -///////////////////////////////////////////////////////////////////////// - -But wait |mdash| I'm using a high-speed network. Do I have to disable the TCP BTL? ------------------------------------------------------------------------------------ - -No. Following the so-called "Law of Least Astonishment", Open MPI -assumes that if you have both an IP network and at least one -high-speed network (such InfiniBand), you will likely only want to use -the high-speed network(s) for MPI message passing. Hence, the ``tcp`` -BTL component will sense this and automatically deactivate itself. - -That being said, Open MPI may still use TCP for setup and teardown -information |mdash| so you'll see traffic across your IP network during + lower performance when Open MPI uses the TCP network stack + to send to peers on the same host. + +.. warning:: Failure to specify the ``self`` BTL may result in Open MPI + being unable to complete send-to-self scenarios (meaning + that your program will run fine until a process tries to + send to itself). + +Coexisting with a high-speed network +------------------------------------ + +If you have both an IP network and at least one high-speed network +(such as InfiniBand), you do not need to disable the TCP BTL. +Following the so-called "Law of Least Astonishment," Open MPI assumes +that you will likely want to use only the high-speed network(s) for MPI +message passing, so the ``tcp`` BTL component senses this and +automatically deactivates itself. + +That said, Open MPI may still use TCP for setup and teardown +information, so you will see traffic across your IP network during startup and shutdown of your MPI job. This is normal and does not -affect the MPI message passing channels. - -///////////////////////////////////////////////////////////////////////// +affect the MPI message-passing channels. -How do I know what MCA parameters are available for tuning MPI performance? ---------------------------------------------------------------------------- +Listing tunable parameters +-------------------------- -The ``ompi_info`` command can display all the parameters -available for the ``tcp`` BTL component (i.e., the component that uses -TCP for MPI communications): +The ``ompi_info`` command can display all the parameters available for +the ``tcp`` BTL component (that is, the component that uses TCP for MPI +communication): .. code-block:: sh shell$ ompi_info --param btl tcp --level 9 -///////////////////////////////////////////////////////////////////////// - -Does Open MPI use the IP loopback interface? --------------------------------------------- - -Usually not. +The IP loopback interface +------------------------- -In general message passing usage, there are two scenarios where using -the operating system IP loopback interface could be used: +Open MPI usually does *not* use the operating system IP loopback +interface. In general message-passing usage, there are two scenarios +in which the IP loopback interface could be used: -#. Sending a message from one process to itself +#. Sending a message from one process to itself, and #. Sending a message from one process to another process on the same - machine - -The TCP BTL does not handle "send-to-self" scenarios in Open MPI. -Instead, the ``self`` BTL is used for all send-to-self MPI communications. -This allows all Open MPI BTL components to avoid special case code -for send-to-self scenarios, and also avoids using less efficient -loopback network stacks (such as the IP loopback device). However, -in cases where there is a need to use the IP loopback device, the ``tcp`` -BTL can be configured to use it by setting the ``btl_tcp_if_include`` -MCA parameter to ``lo`` or ``127.0.0.0/32`` (or whatever is the local -naming scheme on your system). For example: + machine. + +The ``tcp`` BTL does not handle send-to-self scenarios; instead, the +``self`` BTL is used for all send-to-self MPI communication. This lets +all Open MPI BTL components avoid special-case code for send-to-self +and also avoids using less efficient loopback network stacks (such as +the IP loopback device). The ``self`` component uses its own +mechanisms and does not use operating system network interfaces such as +the IP loopback interface. + +When sending to other processes on the same machine, Open MPI defaults +to using a shared-memory BTL (``sm``). If the shared-memory BTL has +been deactivated, then |mdash| depending on what other BTL components +are available |mdash| it is possible that the ``tcp`` BTL will be +chosen for on-node message passing, in which case the IP loopback +device will likely be used. This is not the default, however: either +shared memory must fail to start up properly, or the user must +specifically request not to use the shared-memory BTL. If you do need +to use the IP loopback device, the ``tcp`` BTL can be configured to use +it by setting the ``btl_tcp_if_include`` MCA parameter to ``lo`` or +``127.0.0.0/32`` (or whatever the local naming scheme is on your +system). For example: .. code-block:: sh shell$ mpirun --mca btl_tcp_if_include lo ... - shell$ mpirun --mca btl_tcp_if_exclude 127.0.0.0/32 ... - - -Specifically: the ``self`` component uses its own mechanisms for -send-to-self scenarios; it does not use operating system network -interfaces such as the IP loopback interface. - -When sending to other processes on the same machine, Open MPI will -default to using a shared memory BTL (``sm``). If the user has -deactivated these BTLs, depending on what other BTL components are -available, it is possible that the TCP BTL will be chosen for message -passing to processes on the same node, in which case the IP loopback -device will likely be used. But this is not the default; either -shared memory has to fail to startup properly or the user must -specifically request not to use the shared memory BTL. - -///////////////////////////////////////////////////////////////////////// - -I have multiple IP networks on some/all of my cluster nodes. Which ones will Open MPI use? -------------------------------------------------------------------------------------------- - -In general, Open MPI will greedily use all IP networks that -it finds per its :ref:`reachability computations `. - -To change this behavior, you can either specifically include certain -networks or specifically exclude certain networks. :ref:`See this FAQ -entry ` for more details. - -///////////////////////////////////////////////////////////////////////// -I'm getting TCP-related errors. What do they mean? +Interpreting TCP-related errors +------------------------------- TCP-related errors are usually reported by Open MPI in a message similar to these: @@ -130,8 +103,8 @@ similar to these: btl_tcp_endpoint.c:572:mca_btl_tcp_endpoint_complete_connect: connect() failed with errno=113 mca_btl_tcp_frag_send: writev failed with errno=104 -If an `errno` number is displayed with no explanation string, you can -see what that specific error number means on your operating system. +If an ``errno`` number is displayed with no explanation string, you can +look up what that specific error number means on your operating system. On Linux, you can use the ``perror`` command: .. code-block:: sh @@ -144,178 +117,169 @@ On Linux, you can use the ``perror`` command: shell$ perror 104 OS error code 104: Connection reset by peer -Two types of errors are commonly reported to the Open MPI user's +Two types of errors are commonly reported to the Open MPI users' mailing list: -#. **No route to host:** These types of errors *usually* mean that - there are multiple IP interfaces available and they do not obey - Open MPI's assumptions about routability. See :ref:`the TCP - routability assumptions FAQ entry ` and - :ref:`the TCP selection FAQ entry ` for more - information. +#. **No route to host:** These errors *usually* mean that there are + multiple IP interfaces available and they do not obey Open MPI's + assumptions about routability. See :ref:`how Open MPI determines + routability ` and :ref:`selecting which IP + interfaces to use ` for more information. -#. **Connection reset by peer:** These types of errors *usually* occur - after ``MPI_INIT`` has completed, and typically indicate that an - MPI process has died unexpectedly (e.g., due to a catastrophic error - such as a segmentation fault). The specific error message - indicates that a peer MPI process tried to write to the now-dead - MPI process and failed. - -///////////////////////////////////////////////////////////////////////// +#. **Connection reset by peer:** These errors *usually* occur after + ``MPI_Init`` has completed, and typically indicate that an MPI + process has died unexpectedly (for example, due to a catastrophic + error such as a segmentation fault). The message indicates that a + peer MPI process tried to write to the now-dead MPI process and + failed. .. _faq-tcp-selection: -How do I tell Open MPI which IP interfaces / networks to use? -------------------------------------------------------------- +Selecting which IP interfaces to use +------------------------------------ In some HPC environments, it is not uncommon to have multiple IP -interfaces on each node |mdash| for example, one IP network may be -"slow" and used for control information such as a batch scheduler, a -networked filesystem, and/or interactive logins. Another IP network -(or networks) may be "fast" and be intended for parallel applications -to use during their runs. As another example, some operating systems -may also have virtual interfaces for communicating with virtual -machines. - -Unless otherwise specified, Open MPI will greedily use all "up" IP -networks that it can find and try to connect to all peers *upon -demand* (i.e., Open MPI does not open sockets to all of its MPI peers -during ``MPI_INIT`` |mdash| see :ref:`this FAQ entry -` for more details). Hence, if you want MPI jobs to -not use specific IP networks |mdash| or not use any IP networks at all -|mdash| then you need to tell Open MPI. +interfaces on each node |mdash| for example, one "slow" IP network used +for control information (such as a batch scheduler, a networked +filesystem, and/or interactive logins) and another "fast" IP network +(or networks) intended for parallel applications to use during their +runs. Some operating systems may also have virtual interfaces for +communicating with virtual machines. + +Unless otherwise specified, Open MPI greedily uses all "up" IP networks +that it can find, per its :ref:`reachability computations +`, and tries to connect to all peers *on demand* +(Open MPI does not open sockets to all of its MPI peers during +``MPI_Init`` |mdash| see :ref:`sockets opened during MPI_Init +`). If you want MPI jobs to not use specific IP +networks |mdash| or not use any IP networks at all |mdash| then you +must tell Open MPI. .. warning:: Aggressively using all "up" interfaces can cause problems in some cases. For example, if you have a machine with a - local-only interface (e.g., the loopback device, or a + local-only interface (such as the loopback device, or a virtual-machine bridge device that can only be used *on - that machine*, and cannot be used to communicate with MPI - processes on other machines), you will likely need to - tell Open MPI to ignore these networks. + that machine* and cannot be used to communicate with MPI + processes on other machines), you will likely need to tell + Open MPI to ignore these networks. Open MPI usually ignores loopback devices by default, but - *other local-only devices must be manually ignored.* - Users have reported cases where RHEL6 automatically - installed a ``virbr0`` device for Xen virtualization. - This interface was automatically given an IP address in - the 192.168.1.0/24 subnet and marked as "up". Since Open - MPI saw this 192.168.1.0/24 "up" interface in all MPI - processes on all nodes, it assumed that that network was - usable for MPI communications. This is obviously - incorrect, and it led to MPI applications hanging when - they tried to send or receive MPI messages. - -#. To disable Open MPI from using TCP for MPI communications, the - ``tcp`` MCA parameter should be set accordingly. You can either - *exclude* the TCP component or *include* all other components. - Specifically: + *other local-only devices must be manually ignored.* Users + have reported cases where RHEL6 automatically installed a + ``virbr0`` device for Xen virtualization. This interface + was automatically given an IP address in the 192.168.1.0/24 + subnet and marked as "up". Since Open MPI saw this + 192.168.1.0/24 "up" interface in all MPI processes on all + nodes, it assumed that network was usable for MPI + communication. This is obviously incorrect, and it led to + MPI applications hanging when they tried to send or receive + MPI messages. + +There are several ways to control which interfaces Open MPI uses: + +#. To prevent Open MPI from using TCP for MPI communication at all, set + the ``btl`` MCA parameter accordingly. You can either *exclude* the + TCP component or *include* only other components: .. code-block:: sh - # This says to exclude the TCP BTL component - # (implicitly including all others) - shell$ mpirun --mca btl ^tcp... + # Exclude the TCP BTL component (implicitly including all others) + shell$ mpirun --mca btl ^tcp ... - # This says to include only the listed BTL components + # Include only the listed BTL components # (tcp is not listed, and therefore will not be used) - shell$ mpirun --mca btl self,vader,openib ... + shell$ mpirun --mca btl self,sm ... -#. If you want to use TCP for MPI communications, but want to restrict - it from certain networks, use the ``btl_tcp_if_include`` or - ``btl_tcp_if_exclude`` MCA parameters (only one of the two should - be set). The values of these parameters can be a comma-delimited - list of network interfaces. For example: +#. To use TCP for MPI communication but restrict it to (or from) + certain networks, use the ``btl_tcp_if_include`` or + ``btl_tcp_if_exclude`` MCA parameter (only one of the two should be + set). The value can be a comma-delimited list of network + interfaces. For example: .. code-block:: sh - # This says to not use the eth0 and lo interfaces. - # (and implicitly use all the rest). Per the description - # above, IP loopback and all local-only devices *must* - # be included if the exclude list is specified. + # Do not use the lo and eth0 interfaces (and implicitly use the + # rest). Per the description above, IP loopback and all + # local-only devices *must* be included in an exclude list. shell$ mpirun --mca btl_tcp_if_exclude lo,eth0 ... - # This says to only use the eth1 and eth2 interfaces - # (and implicitly ignore the rest) + # Use only the eth1 and eth2 interfaces (and implicitly ignore + # the rest) shell$ mpirun --mca btl_tcp_if_include eth1,eth2 ... -#. You can also specify subnets in the include or exclude lists in - CIDR notation. For example: +#. You can also specify subnets in the include or exclude lists in CIDR + notation. For example: .. code-block:: sh # Only use the 192.168.1.0/24 and 10.10.0.0/16 subnets for MPI - # communications: + # communication shell$ mpirun --mca btl_tcp_if_include 192.168.1.0/24,10.10.0.0/16 ... - .. note:: You must specify the CIDR notation for a given network precisely. For example, if you have two IP networks - 10.10.0.0/24 and 10.10.1.0/24, Open MPI will not - recognize either of them if you specify "10.10.0.0/16". + 10.10.0.0/24 and 10.10.1.0/24, Open MPI will not recognize + either of them if you specify "10.10.0.0/16". .. warning:: If you use the ``btl_tcp_if_include`` and - ``btl_tcp_if_exclude`` MCA parameters to shape the - behavior of the TCP BTL for MPI communications, you may - also need/want to investigate the corresponding PRRTE - parameters that control use of network interfaces by the - runtime (e.g., communications setup and coordination - during :ref:`MPI_Init` and :ref:`MPI_Finalize`) using the - :ref:`prte_info(1) ` - and :ref:`pmix_info(1) ` commands. - -Note that the Open MPI runtime uses TCP for control messages, such as -for data exchange between ``mpirun(1)`` and the MPI processes, -rendezvous information during :ref:`MPI_Init`, etc. even if the -``tcp`` BTL component is disabled. - -///////////////////////////////////////////////////////////////////////// + ``btl_tcp_if_exclude`` MCA parameters to shape the behavior + of the TCP BTL for MPI communication, you may also + need/want to investigate the corresponding PRRTE parameters + that control use of network interfaces by the runtime (for + example, communication setup and coordination during + :ref:`MPI_Init` and :ref:`MPI_Finalize`), using the + :ref:`prte_info(1) ` and + :ref:`pmix_info(1) ` commands. + +Note that the Open MPI runtime uses TCP for control messages |mdash| +such as data exchange between ``mpirun(1)`` and the MPI processes, +rendezvous information during :ref:`MPI_Init`, and so on |mdash| even if +the ``tcp`` BTL component is disabled. .. _faq-tcp-sockets: -Does Open MPI open a bunch of sockets during ``MPI_INIT``? ----------------------------------------------------------- +Sockets opened during MPI_Init +------------------------------ Although Open MPI is likely to open multiple TCP sockets during -``MPI_INIT``, the ``tcp`` BTL component *does not open one socket per -MPI peer process during MPI_INIT.* Open MPI opens sockets as they -are required |mdash| so the first time a process sends a message to a -peer and there is no TCP connection between the two, Open MPI will -automatically open a new socket. - -Hence, you should not have scalability issues with running large -numbers of processes (e.g., running out of per-process file -descriptors) if your parallel application is sparse in its +``MPI_Init``, the ``tcp`` BTL component *does not open one socket per +MPI peer process during* ``MPI_Init``. Open MPI opens sockets as they +are required, so the first time a process sends a message to a peer and +there is no TCP connection between the two, Open MPI automatically opens +a new socket. + +As a result, you should not have scalability issues (such as running +out of per-process file descriptors) when running large numbers of +processes, provided your parallel application is sparse in its communication with peers. -///////////////////////////////////////////////////////////////////////// - -Are there any Linux kernel TCP parameters that I should set? ------------------------------------------------------------- +Recommended Linux kernel TCP parameters +--------------------------------------- -Everyone has different opinions on this, and it also depends -on your exact hardware and environment. Below are general guidelines -that some users have found helpful. +Everyone has different opinions on this, and the best settings also +depend on your exact hardware and environment. The following are +general guidelines that some users have found helpful. #. ``net.ipv4.tcp_syn_retries``: Some Linux systems have very large initial connection timeouts |mdash| they retry sending SYN packets many times before determining that a connection cannot be made. If - MPI is going to fail to make socket connections, it would be better - for them to fail somewhat quickly (minutes vs. hours). You might - want to reduce this value to a smaller value; YMMV. + MPI is going to fail to make socket connections, it is better for it + to fail somewhat quickly (minutes vs. hours), so you might want to + reduce this value; your mileage may vary. #. ``net.ipv4.tcp_keepalive_time``: Some MPI applications send an initial burst of MPI messages (over TCP) and then send nothing for - long periods of time (e.g., embarrassingly parallel applications). - Linux may decide that these dormant TCP sockets are dead because it - has seen no traffic on them for long periods of time. You might - therefore need to lengthen the TCP inactivity timeout. Many Linux - systems default to 7,200 seconds; increase it if necessary. + long periods of time (for example, embarrassingly parallel + applications). Linux may decide that these dormant TCP sockets are + dead because it has seen no traffic on them for a long time. You + might therefore need to lengthen the TCP inactivity timeout. Many + Linux systems default to 7,200 seconds; increase it if necessary. #. Increase TCP buffering for 10G or 40G Ethernet. Many Linux - distributions come with good buffering presets for 1G Ethernet. In - a datacenter/HPC cluster with 10G or 40G Ethernet NICs, this amount - of kernel buffering is typically insufficient. Here's a set of - parameters that some have used for good 10G/40G TCP bandwidth: + distributions come with good buffering presets for 1G Ethernet, but + in a datacenter/HPC cluster with 10G or 40G Ethernet NICs, this + amount of kernel buffering is typically insufficient. Here is a set + of parameters that some have used for good 10G/40G TCP bandwidth: * ``net.core.rmem_max``: 16777216 * ``net.core.wmem_max``: 16777216 @@ -327,62 +291,60 @@ that some users have found helpful. * ``net.ipv4.tcp_mem``: '16777216 16777216 16777216' * ``net.ipv4.route.flush``: 1 - Each of the above items is a Linux kernel parameter that can be set - in multiple different ways. +Each of the above is a Linux kernel parameter that can be set in +several ways: - #. You can change the running kernel via the ``/proc`` filesystem: +#. You can change the running kernel via the ``/proc`` filesystem: - .. code-block:: sh - - shell# cat /proc/sys/net/ipv4/tcp_syn_retries - 5 - shell# echo 6 > /proc/sys/net/ipv4/tcp_syn_retries + .. code-block:: sh - #. You can also use the ``sysctl`` command: + shell# cat /proc/sys/net/ipv4/tcp_syn_retries + 5 + shell# echo 6 > /proc/sys/net/ipv4/tcp_syn_retries - .. code-block:: sh +#. You can use the ``sysctl`` command: - shell# sysctl net.ipv4.tcp_syn_retries - net.ipv4.tcp_syn_retries = 5 - shell# sysctl -w net.ipv4.tcp_syn_retries=6 - net.ipv4.tcp_syn_retries = 6 + .. code-block:: sh - #. Or you can set them by adding entries in ``/etc/sysctl.conf``, - which are persistent across reboots: + shell# sysctl net.ipv4.tcp_syn_retries + net.ipv4.tcp_syn_retries = 5 + shell# sysctl -w net.ipv4.tcp_syn_retries=6 + net.ipv4.tcp_syn_retries = 6 - .. code-block:: sh +#. You can set them persistently (across reboots) by adding entries in + ``/etc/sysctl.conf``: - shell$ grep tcp_syn_retries /etc/sysctl.conf - net.ipv4.tcp_syn_retries = 6 + .. code-block:: sh - #. Your Linux distro may also support putting individual files in - ``/etc/sysctl.d`` (even if that directory does not yet exist), - which is actually better practice than putting them in - ``/etc/sysctl.conf``. For example: + shell$ grep tcp_syn_retries /etc/sysctl.conf + net.ipv4.tcp_syn_retries = 6 - .. code-block:: sh +#. Your Linux distribution may also support putting individual files in + ``/etc/sysctl.d`` (even if that directory does not yet exist), which + is actually better practice than putting them in + ``/etc/sysctl.conf``. For example: - shell$ cat /etc/sysctl.d/my-tcp-settings - net.ipv4.tcp_syn_retries = 6 + .. code-block:: sh -///////////////////////////////////////////////////////////////////////// + shell$ cat /etc/sysctl.d/my-tcp-settings + net.ipv4.tcp_syn_retries = 6 .. _faq-tcp-routability: -How does Open MPI know which IP addresses are routable to each other? ---------------------------------------------------------------------- +How Open MPI determines routability +----------------------------------- Open MPI assumes that all interfaces are routable as long as they have -the same address family, IPv4 or IPv6. We use graph theory and give +the same address family (IPv4 or IPv6). It uses graph theory, giving each possible connection a weight depending on the quality of the -connection. This allows the library to select the best connections -between nodes. This method also supports striping but prevents more +connection, which allows the library to select the best connections +between nodes. This method also supports striping, but prevents more than one connection to any interface. -The quality of the connection is defined as follows, with a higher -number meaning better connection. Note that when giving a weight to a -connection consisting of a private address and a public address, it -will give it the weight of ``PRIVATE_DIFFERENT_NETWORK``. +The quality of a connection is defined as follows, with a higher number +meaning a better connection. Note that a connection consisting of a +private address and a public address is given the weight +``PRIVATE_DIFFERENT_NETWORK``. .. code-block:: @@ -392,9 +354,8 @@ will give it the weight of ``PRIVATE_DIFFERENT_NETWORK``. PUBLIC_DIFFERENT_NETWORK = 3 PUBLIC_SAME_NETWORK = 4 -An example will best illustrate how two processes on two different -nodes would connect up. Here we have two nodes with a variety of -interfaces: +An example best illustrates how two processes on two different nodes +would connect. Here we have two nodes with a variety of interfaces: .. code-block:: @@ -413,11 +374,11 @@ interfaces: | 192.168.2.2/24 | | | ---------------- ------------------ -From these two nodes, the software builds up a bipartite graph that -shows all the possible connections with all the possible weights. The -*lo0* interfaces are excluded as the ``btl_tcp_if_exclude`` MCA parameter -is set to *lo* by default. Here is what all the possible connections -with their weights look like. +From these two nodes, the software builds a bipartite graph that shows +all the possible connections with all the possible weights. The +``lo0`` interfaces are excluded because the ``btl_tcp_if_exclude`` MCA +parameter is set to ``lo`` by default. Here is what all the possible +connections with their weights look like: .. code-block:: @@ -432,13 +393,12 @@ with their weights look like. ------- 1 -------- eth1 The library then examines all the connections and picks the optimal -ones. This leaves us with two connections being established between -the two nodes. +ones. This leaves two connections established between the two nodes. If you are curious about the actual ``connect()`` calls being made by -the processes, then you can run with ``--mca btl_base_verbose 30``. -This can be useful if you notice your job hanging and believe it may -be the library trying to make connections to unreachable hosts. +the processes, run with ``--mca btl_base_verbose 30``. This can be +useful if you notice your job hanging and believe it may be the library +trying to make connections to unreachable hosts: .. code-block:: sh @@ -451,117 +411,104 @@ be the library trying to make connections to unreachable hosts. [NodeB:16842] btl: tcp: attempting to connect() to address 192.168.1.1 on port 44500 [...snip...] -In case you want more details about the theory behind the connection -code, you can find the background story in `this IEEE paper +If you want more details about the theory behind the connection code, +you can find the background story in `this IEEE paper `_. -///////////////////////////////////////////////////////////////////////// - -Does Open MPI ever close TCP sockets? -------------------------------------- +When Open MPI closes TCP sockets +-------------------------------- -In general, no. However, there are some exceptions. - -Although TCP sockets are opened "lazily" (meaning that MPI -connections / TCP sockets are only opened upon demand |mdash| as opposed to +In general, Open MPI does not close TCP sockets; however, there are +some exceptions. Although TCP sockets are opened lazily (MPI +connections / TCP sockets are only opened on demand, as opposed to opening all possible sockets between MPI peer processes during -``MPI_INIT``), they are never closed unless the MPI world or MPI sessions -are explicitly finalized or disconnected. For example, if the MPI world is -finalized, all TCP sockets will be closed. If a spawned MPI process is -disconnected, all TCP sockets to any parent MPI process will be closed. -Similarly, if the MPI session is disconnected, all TCP sockets to any child -MPI processes will be closed. - -///////////////////////////////////////////////////////////////////////// +``MPI_Init``), they are never closed unless the MPI world or MPI +sessions are explicitly finalized or disconnected. For example, if the +MPI world is finalized, all TCP sockets are closed. If a spawned MPI +process is disconnected, all TCP sockets to any parent MPI process are +closed. Similarly, if an MPI session is disconnected, all TCP sockets +to any child MPI processes are closed. -Does Open MPI support IP interfaces that have more than one IP address? ------------------------------------------------------------------------ +Interfaces with multiple IP addresses +-------------------------------------- -In general, no. - -For example, if the output from your ``ifconfig`` has a single IP device -with multiple IP addresses like this: +In general, Open MPI does not support an IP interface that has more than +one IP address. For example, if the output from your ``ifconfig`` has a +single IP device with multiple IP addresses like this: .. code-block:: 0: eth0: mtu 1500 qdisc mq state UP qlen 1000 link/ether 00:18:ae:f4:d2:29 brd ff:ff:ff:ff:ff:ff inet 192.168.0.3/24 brd 192.168.0.255 scope global eth0:1 - inet 10.10.0.3/24 brf 10.10.0.255 scope global eth0 + inet 10.10.0.3/24 brd 10.10.0.255 scope global eth0 inet6 fe80::218:aef2:29b4:2c4/64 scope link valid_lft forever preferred_lft forever -(note the two ``inet`` lines in there) - -Then Open MPI will be unable to use this device. - -///////////////////////////////////////////////////////////////////////// - -Does Open MPI support virtual IP interfaces? --------------------------------------------- - -No. - -For example, if the output of your ``ifconfig`` has both ``eth0`` and -``eth0:0``, Open MPI will get confused if you use the TCP BTL, and -may hang or otherwise act unpredictably. +(note the two ``inet`` lines), then Open MPI will be unable to use this +device. -Note that using ``btl_tcp_if_include`` or ``btl_tcp_if_exclude`` to avoid -using the virtual interface will *not* solve the issue. +Virtual IP interfaces +--------------------- -///////////////////////////////////////////////////////////////////////// +Open MPI does not support virtual IP interfaces. For example, if the +output of your ``ifconfig`` has both ``eth0`` and ``eth0:0``, Open MPI +will get confused if you use the TCP BTL, and may hang or otherwise act +unpredictably. Note that using ``btl_tcp_if_include`` or +``btl_tcp_if_exclude`` to avoid using the virtual interface will *not* +solve the issue. -Can I use multiple TCP connections to improve network performance? ------------------------------------------------------------------- +Using multiple TCP connections +------------------------------ Open MPI can use multiple TCP connections between any pair of MPI processes, striping large messages across the connections. The -``btl_tcp_links`` parameter can be used to set how many TCP -connections should be established between MPI processes. - -Note that -this may not improve application performance for common use cases of -nearest-neighbor exchanges when there many MPI processes on each host. In -these cases, there are already many TCP connections between any two -hosts (because of the many processes all communicating), so the extra TCP -connections are likely just consuming extra resources and adding work -to the MPI implementation. - -However, for highly multi-threaded applications, where there are only -one or two MPI processes per host, the ``btl_tcp_links`` option may -improve TCP throughput considerably. - -///////////////////////////////////////////////////////////////////////// - -Can I limit the time spent on establishing TCP connections? ------------------------------------------------------------ - -Yes. You can set the ``tcp_recv_timeout`` and ``tcp_handshake_timeout`` -MCA parameters to limit the time spent on establishing TCP connections. -The difference between the two is subtle, but important: the -``tcp_recv_timeout`` is the timeout for one receive operation -while the ``tcp_handshake_timeout`` is the timeout for the entire handshake -(i.e., the exchange of the correct magic string and process GUID). These two -parameters can be used to avoid deadlocks in adversarial situations where -external processes (e.g., not MPI processes part of any job) are trying to -connect to the Open MPI process, and are holding the connection socket open -without sending the proper handshake information. - -The default values are 250,000 usec for ``tcp_recv_timeout`` and 1,000,000 -usec for ``tcp_handshake_timeout``. - -For example: +``btl_tcp_links`` parameter sets how many TCP connections are +established between MPI processes. + +Note that this may not improve application performance for common use +cases of nearest-neighbor exchanges when there are many MPI processes +on each host. In those cases, there are already many TCP connections +between any two hosts (because of the many processes all communicating), +so the extra TCP connections are likely just consuming extra resources +and adding work to the MPI implementation. + +However, for highly multi-threaded applications where there are only one +or two MPI processes per host, the ``btl_tcp_links`` option may improve +TCP throughput considerably. + +Limiting the time spent establishing TCP connections +----------------------------------------------------- + +You can set the ``btl_tcp_recv_timeout`` and +``btl_tcp_handshake_timeout`` MCA parameters to limit the time spent +establishing TCP connections. The difference between the two is subtle +but important: ``btl_tcp_recv_timeout`` is the timeout for one receive +operation, while ``btl_tcp_handshake_timeout`` is the timeout for the +entire handshake (that is, the exchange of the correct magic string and +process GUID). These parameters can be used to avoid deadlocks in +adversarial situations where external processes (for example, processes +that are not part of any MPI job) try to connect to the Open MPI process +and hold the connection socket open without sending the proper handshake +information. + +The default values are 250,000 usec for ``btl_tcp_recv_timeout`` and +1,000,000 usec for ``btl_tcp_handshake_timeout``. For example: .. code-block:: sh - shell$ mpirun --mca tcp_recv_timeout 1000000 ... - shell$ mpirun --mca tcp_handshake_timeout 1000000 ... + shell$ mpirun --mca btl_tcp_recv_timeout 1000000 ... + shell$ mpirun --mca btl_tcp_handshake_timeout 1000000 ... -///////////////////////////////////////////////////////////////////////// +Nagle's algorithm and TCP_NODELAY +--------------------------------- -Can I use TCP_NODELAY to improve latency? ------------------------------------------ +By default, the TCP BTL disables Nagle's algorithm |mdash| that is, it +sets the ``TCP_NODELAY`` socket option |mdash| which favors low latency +for applications driven by waves of small messages. Using Nagle's +algorithm can increase short-message latency. -If your application is driven by waves of small messages, you may be able -to improve latency by enabling TCP_NODELAY. You can set the ``btl_tcp_use_nagle`` -MCA parameter to 1 to enable TCP_NODELAY. +This behavior is controlled by the ``btl_tcp_use_nagle`` MCA parameter, +which defaults to 0 (Nagle's algorithm disabled, ``TCP_NODELAY`` set). +Set it to 1 to use Nagle's algorithm (clearing ``TCP_NODELAY``); this is +rarely desirable for latency-sensitive workloads. diff --git a/docs/validate-llm-docs.py b/docs/validate-llm-docs.py new file mode 100755 index 00000000000..216409aa8a4 --- /dev/null +++ b/docs/validate-llm-docs.py @@ -0,0 +1,322 @@ +#!/usr/bin/env python3 +# +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +# + +"""Validate the generated LLM-friendly documentation artifacts. + +Run after generate-llm-docs.py (e.g. via "make check" in docs/). Checks: + +* every JSONL catalog record validates against openmpi-mpi-api.schema.json; +* the manifest validates against openmpi-docs-manifest.schema.json; +* the cross-field invariant that each record's `languages` equals the distinct + `bindings[].language` set (JSON Schema cannot express this); +* every record links back to the human docs (urls.html present); +* generated Markdown contains no unresolved RST (`.. include::` directives, + `:ref:` roles, RST `\\`text \\`_` hyperlinks, or man-page `|...|` + substitutions); +* the manifest inventories every artifact except itself, with matching sha256 + and byte size, and lists nothing that is missing on disk; +* the committed sample records (specs/llms-friendly-docs/sample-records.jsonl) + validate against the schema and match the generated catalog by semantic + content, ignoring the per-build version stamp (docset) so the samples need + not be regenerated on every version bump and stay valid across branches; +* every MPI Forum procedure that Open MPI actually implements has a man page, + so an implemented MPI API is never silently undocumented. + +Exits non-zero if any check fails. Targets the Python 3.6 floor. +""" + +import argparse +import hashlib +import json +import os +import re +import sys + +try: + import jsonschema + # The schemas are JSON Schema draft 2020-12, which needs jsonschema >= 4 + # (Python 3.7+). On the Python 3.6 floor only jsonschema 3.2.0 installs, + # and it lacks Draft202012Validator -- degrade gracefully there rather + # than crashing on the missing attribute. + HAVE_JSONSCHEMA = hasattr(jsonschema, 'Draft202012Validator') +except ImportError: + HAVE_JSONSCHEMA = False + +# Unresolved-RST patterns that must NOT appear in generated Markdown. +UNRESOLVED = [ + (":ref: role", re.compile(r':ref:`')), + (".. include:: directive", re.compile(r'^\s*\.\.\s+include::', re.M)), + ("RST directive", re.compile(r'^\s*\.\.\s+[a-zA-Z0-9_-]+::', re.M)), + ("RST hyperlink", re.compile(r'`[^`]+<[^`>]+>`_')), + ("man-page substitution", + re.compile(r'\|(?:mdash|rarrow|deprecated_favor|ompi_ver|ompi_series|' + r'mpi_standard_version|mpi_standard_major_version|' + r'mpi_standard_minor_version)\|')), +] + + +def fail(errors, msg): + errors.append(msg) + + +def read_text(path): + with open(path, encoding='utf-8') as fp: + return fp.read() + + +def read_bytes(path): + with open(path, 'rb') as fp: + return fp.read() + + +def read_json(path): + return json.loads(read_text(path)) + + +def main(): + ap = argparse.ArgumentParser(description="Validate LLM-friendly docs") + ap.add_argument('--srcdir', required=True, help='docs source dir') + ap.add_argument('--llms-dir', required=True, + help='published/staged llms/ directory to validate') + ap.add_argument('--samples', + help='path to sample-records.jsonl (optional)') + args = ap.parse_args() + + srcdir = os.path.abspath(args.srcdir) + llms = os.path.abspath(args.llms_dir) + schema_dir = os.path.join(srcdir, 'llms-src') + errors = [] + + cat_validator = man_validator = None + if HAVE_JSONSCHEMA: + catalog_schema = read_json( + os.path.join(schema_dir, 'openmpi-mpi-api.schema.json')) + manifest_schema = read_json( + os.path.join(schema_dir, 'openmpi-docs-manifest.schema.json')) + jsonschema.Draft202012Validator.check_schema(catalog_schema) + jsonschema.Draft202012Validator.check_schema(manifest_schema) + cat_validator = jsonschema.Draft202012Validator(catalog_schema) + man_validator = jsonschema.Draft202012Validator(manifest_schema) + else: + print(" WARNING: jsonschema (>= 4, with Draft202012Validator) not " + "available; skipping JSON Schema validation (cross-field, " + "manifest, and Markdown checks still run). Install a recent " + "jsonschema for full validation.", file=sys.stderr) + + # --- catalog records --- + catalog_path = os.path.join(llms, 'openmpi-mpi-api.jsonl') + catalog = {} + n_records = 0 + for i, line in enumerate(read_text(catalog_path).split('\n'), 1): + line = line.rstrip('\n') + if not line: + continue + n_records += 1 + rec = json.loads(line) + catalog[rec['name']] = line + if cat_validator: + for e in cat_validator.iter_errors(rec): + fail(errors, "catalog record {} ({}): {}".format( + i, rec.get('name'), e.message)) + # cross-field invariant + decl = sorted(rec['languages']) + present = sorted({b['language'] for b in rec['bindings']}) + if decl != present: + fail(errors, "catalog record {}: languages {} != bindings langs {}" + .format(rec['name'], decl, present)) + # link-back + if not rec.get('urls', {}).get('html'): + fail(errors, "catalog record {}: missing urls.html".format( + rec['name'])) + # docset must not carry build identity (that lives only in the manifest) + for bad in ('git_commit', 'git_describe', 'generated_at'): + if bad in rec.get('docset', {}): + fail(errors, "catalog record {}: docset contains build-identity " + "field {}".format(rec['name'], bad)) + + # --- manifest --- + manifest_path = os.path.join(llms, 'openmpi-docs-manifest.json') + manifest = read_json(manifest_path) + if man_validator: + for e in man_validator.iter_errors(manifest): + fail(errors, "manifest: {}".format(e.message)) + + # --- manifest <-> on-disk consistency --- + # The published root is the parent of llms/ (llms.txt lives there too). + pub_root = os.path.dirname(llms) + manifest_rel = 'llms/openmpi-docs-manifest.json' + listed = set() + for art in manifest['artifacts']: + rel = art['path'] + listed.add(rel) + if rel == manifest_rel: + fail(errors, "manifest lists itself ({})".format(rel)) + full = os.path.join(pub_root, rel) + if not os.path.exists(full): + fail(errors, "manifest lists missing artifact: {}".format(rel)) + continue + data = read_bytes(full) + if hashlib.sha256(data).hexdigest() != art['sha256']: + fail(errors, "manifest sha256 mismatch: {}".format(rel)) + if len(data) != art['bytes']: + fail(errors, "manifest byte size mismatch: {}".format(rel)) + + # Every on-disk LLM artifact (everything under llms/, plus the top-level + # llms.txt) except the manifest must be listed. Scope to the LLM tree so + # this works whether llms/ sits alone in a staging dir or inside the full + # Sphinx HTML output. + on_disk = set() + for root, _dirs, files in os.walk(llms): + for fn in files: + on_disk.add(os.path.relpath(os.path.join(root, fn), pub_root)) + if os.path.exists(os.path.join(pub_root, 'llms.txt')): + on_disk.add('llms.txt') + for rel in sorted(on_disk): + if rel == manifest_rel: + continue + if rel not in listed: + fail(errors, "artifact on disk not in manifest: {}".format(rel)) + + # --- versioned llms.txt must be timestamp-free (so it is reproducible and + # listable in the manifest) --- + llms_txt = os.path.join(pub_root, 'llms.txt') + if os.path.exists(llms_txt): + txt = read_text(llms_txt) + if re.search(r'\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}', txt) \ + or 'generated_at' in txt: + fail(errors, "llms.txt contains a generation timestamp " + "(it must be timestamp-free)") + + # --- no unresolved RST in generated Markdown --- + for root, _dirs, files in os.walk(llms): + for fn in files: + if not fn.endswith('.md'): + continue + # Curated sources are hand-written Markdown, not RST-derived. + if fn in ('openmpi-mpi-interface-guide.md', + 'openmpi-mpi-examples.md', + 'openmpi-runtime-introspection.md'): + continue + text = read_text(os.path.join(root, fn)) + for label, pat in UNRESOLVED: + if pat.search(text): + fail(errors, "{}: unresolved {}".format(fn, label)) + + # --- sample records --- + samples = args.samples or os.path.join( + srcdir, '..', 'specs', 'llms-friendly-docs', 'sample-records.jsonl') + if os.path.exists(samples): + for i, line in enumerate(read_text(samples).split('\n'), 1): + line = line.rstrip('\n') + if not line: + continue + rec = json.loads(line) + if cat_validator: + for e in cat_validator.iter_errors(rec): + fail(errors, "sample {} ({}): {}".format( + i, rec.get('name'), e.message)) + if rec['name'] not in catalog: + fail(errors, "sample {} not in generated catalog".format( + rec['name'])) + else: + # Compare the sample against the generated record by semantic + # content, ignoring the docset field. docset carries the + # per-build Open MPI version and series (e.g. ompi_version + # "v6.1.0a1", ompi_series "v6.1.x"): a single global stamp, + # identical across every record, that changes on every version + # bump, branch, and release. Comparing it byte-for-byte would + # force the committed samples to be regenerated every time the + # version changes, and -- worse -- would tie a sample file to a + # single branch's VERSION, so cherry-picking this check to a + # release branch (whose VERSION differs from main) would always + # fail here even though the API content is identical. The + # version is not API content; its shape is still validated + # against the schema above, schema_version is also a top-level + # record field that remains compared here, and any formatting or + # key-ordering drift in the catalog file is caught separately by + # the manifest sha256/byte-size check. So drop docset from both + # sides and compare the rest. + gen = {k: v for k, v in json.loads(catalog[rec['name']]).items() + if k != 'docset'} + smp = {k: v for k, v in rec.items() if k != 'docset'} + if gen != smp: + fail(errors, "sample {} differs from generated catalog " + "record (regenerate the samples)".format(rec['name'])) + + # --- coverage: an implemented MPI API must have a man page --- + # A procedure in the MPI Forum metadata that Open MPI actually implements + # must have a man page; otherwise it is silently absent from the rendered + # HTML docs, the installed Unix man pages, and these artifacts. Whether a + # procedure is "implemented" is read from the public C header + # (ompi/include/mpi.h.in) and, for Fortran-only routines such as + # MPI_F_sync_reg, from the use-mpi-f08 sources. Procedures the MPI Standard + # defines but Open MPI does not implement are intentionally not flagged. + # Skipped (with a warning) when pympistandard or the ompi/ source tree is + # unavailable, e.g. a docs-only checkout. + top_srcdir = os.path.dirname(srcdir) + mpih = os.path.join(top_srcdir, 'ompi', 'include', 'mpi.h.in') + std = None + try: + import ompi_docs_common as common + std = common.load_pympistandard(srcdir) + except Exception as exc: + print(" WARNING: pympistandard unavailable ({}); skipping " + "implemented-API man-page coverage check".format(exc), + file=sys.stderr) + if std is not None and not os.path.exists(mpih): + print(" WARNING: {} not found; skipping implemented-API man-page " + "coverage check".format(mpih), file=sys.stderr) + elif std is not None: + directives = common.read_rst_man_pages(srcdir) + documented = set(directives) + for procs in directives.values(): + documented.update(procs) + undocumented = [p for p in sorted(std.PROCEDURES) + if p not in documented] + if undocumented: + mpih_l = read_text(mpih).lower() + f08_parts = [] + f08_dir = os.path.join(top_srcdir, 'ompi', 'mpi', 'fortran', + 'use-mpi-f08') + for root, _dirs, files in os.walk(f08_dir): + for fn in files: + if fn.endswith(('.F90', '.f90', '.h', '.in')): + f08_parts.append( + read_text(os.path.join(root, fn)).lower()) + f08_l = '\n'.join(f08_parts) + for proc in undocumented: + in_c = re.search(r'\b' + re.escape(proc) + r'\s*\(', mpih_l) + in_f08 = re.search(r'\b' + re.escape(proc) + r'_f08\b', f08_l) + if not (in_c or in_f08): + continue + p = std.PROCEDURES[proc] + binding = (common.c_binding(p) or common.f08_binding(p) + or common.f90_binding(p) or '') + m = re.search(r'(MPI[A-Za-z0-9_]*)\s*\(', binding) + pname = m.group(1) if m else proc + fail(errors, "implemented MPI API has no man page: {0} " + "(add docs/man-openmpi/man3/{0}.3.rst)".format(pname)) + + if errors: + print("LLM docs validation FAILED ({} error(s)):".format(len(errors)), + file=sys.stderr) + for e in errors[:50]: + print(" - {}".format(e), file=sys.stderr) + if len(errors) > 50: + print(" ... and {} more".format(len(errors) - 50), file=sys.stderr) + sys.exit(1) + + print(" CHECK LLM docs: OK ({} records, manifest + samples validated)" + .format(n_records)) + + +if __name__ == "__main__": + main() diff --git a/docs/version-numbering.rst b/docs/version-numbering.rst index b96da23de91..00fbdd426bc 100644 --- a/docs/version-numbering.rst +++ b/docs/version-numbering.rst @@ -67,7 +67,7 @@ format. Each of the three numbers has a specific meaning: change in the code base and/or end-user functionality, and also indicate a break from backward compatibility. Specifically: Open MPI releases with different major version numbers are not - backward compatibale with each other. + backward compatible with each other. .. important:: This rule does not extend to versions prior to v1.10.0. Specifically: v1.10.x is not guaranteed to be backward @@ -163,7 +163,6 @@ Here's how we apply those rules specifically to Open MPI: * ``libmpi_usempi_ignore_tkr`` * ``libmpi_usempif08`` * ``libmpi_cxx`` - * ``libmpi_java`` * ``liboshmem`` API and ABI Compatibility diff --git a/examples/Connectivity.java b/examples/Connectivity.java deleted file mode 100644 index 551a56ad188..00000000000 --- a/examples/Connectivity.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Test the connectivity between all processes - */ - -import mpi.*; -import java.nio.IntBuffer; - -class Connectivity { - public static void main(String args[]) throws MPIException { - MPI.Init(args); - - /* - * MPI.COMM_WORLD is the communicator provided when MPI is - * initialized. It contains all the processes that are created - * upon program execution. - */ - int myRank = MPI.COMM_WORLD.getRank(); - int numProcesses = MPI.COMM_WORLD.getSize(); - boolean verbose = false; - String processorName = MPI.getProcessorName(); - - for (String arg : args) { - if (arg.equals("-v") || arg.equals("--verbose")) { - verbose = true; - break; - } - } - - for (int i = 0; i < numProcesses; i++) { - /* Find current process */ - if (myRank == i) { - /* send to and receive from all higher ranked processes */ - for (int j = i + 1; j < numProcesses; j++) { - if (verbose) - System.out.printf("Checking connection between rank %d on %s and rank %d\n", i, processorName, - j); - - /* - * rank is the Buffer passed into sendRecv to send to rank j. - * rank is populated with myRank, which is the data to send off - * peer is the Buffer received from rank j to current rank - */ - IntBuffer rank = MPI.newIntBuffer(1); - IntBuffer peer = MPI.newIntBuffer(1); - rank.put(0, myRank); - - /* - * To avoid deadlocks, use combined sendRecv operation. - * This performs a send and recv as a combined atomic operation - * and allow MPI to efficiently handle the requests internally. - */ - MPI.COMM_WORLD.sendRecv(rank, 1, MPI.INT, j, myRank, peer, 1, MPI.INT, j, j); - } - } else if (myRank > i) { - IntBuffer rank = MPI.newIntBuffer(1); - IntBuffer peer = MPI.newIntBuffer(1); - rank.put(0, myRank); - - /* receive from and reply to rank i */ - MPI.COMM_WORLD.sendRecv(rank, 1, MPI.INT, i, myRank, peer, 1, MPI.INT, i, i); - } - } - - /* Wait for all processes to reach barrier before proceeding */ - MPI.COMM_WORLD.barrier(); - - /* - * Once all ranks have reached the barrier, - * have only one process print out the confirmation message. - * In this case, we are having the "master" process print the message. - */ - if (myRank == 0) { - System.out.printf("Connectivity test on %d processes PASSED.\n", numProcesses); - } - - MPI.Finalize(); - } -} \ No newline at end of file diff --git a/examples/Hello.java b/examples/Hello.java deleted file mode 100644 index cd7c5268fa2..00000000000 --- a/examples/Hello.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - https://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. -*/ -/* - * Author of revised version: Franklyn Pinedo - * - * Adapted from Source Code in C of Tutorial/User's Guide for MPI by - * Peter Pacheco. - */ -/* - * Copyright (c) 2011 Cisco Systems, Inc. All rights reserved. - * - */ - -import mpi.*; - -class Hello { - static public void main(String[] args) throws MPIException { - - - MPI.Init(args); - - int myrank = MPI.COMM_WORLD.getRank(); - int size = MPI.COMM_WORLD.getSize() ; - System.out.println("Hello world from rank " + myrank + " of " + size); - - MPI.Finalize(); - } -} diff --git a/examples/Makefile b/examples/Makefile index 735b911923d..3616a617400 100644 --- a/examples/Makefile +++ b/examples/Makefile @@ -15,6 +15,7 @@ # Copyright (c) 2013 Mellanox Technologies, Inc. All rights reserved. # Copyright (c) 2017-2018 Research Organization for Information Science # and Technology (RIST). All rights reserved. +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -26,7 +27,6 @@ MPICC = mpicc MPIFC = mpifort -MPIJAVAC = mpijavac SHMEMCC = shmemcc SHMEMCXX = shmemc++ SHMEMFC = shmemfort @@ -51,16 +51,13 @@ EXAMPLES = \ hello_oshmem \ hello_oshmemcxx \ hello_oshmemfh \ - Hello.class \ ring_c \ ring_mpifh \ ring_usempi \ ring_usempif08 \ ring_oshmem \ ring_oshmemfh \ - Ring.class \ connectivity_c \ - Connectivity.class \ oshmem_shmalloc \ oshmem_circular_shift \ oshmem_max_reduction \ @@ -74,7 +71,7 @@ EXAMPLES = \ # others if we have the appropriate Open MPI / OpenSHMEM language # bindings. -all: hello_c ring_c connectivity_c spc_example hello_sessions_c +all: hello_c ring_c connectivity_c hello_sessions_c @ if which ompi_info >/dev/null 2>&1 ; then \ $(MAKE) mpi; \ fi @@ -94,8 +91,8 @@ mpi: @ if ompi_info --parsable | grep -q bindings:use_mpi_f08:yes >/dev/null; then \ $(MAKE) hello_usempif08 ring_usempif08; \ fi - @ if ompi_info --parsable | grep -q bindings:java:yes >/dev/null; then \ - $(MAKE) Hello.class Ring.class; \ + @ if ompi_info --parsable | grep -q enable-spc >/dev/null; then \ + $(MAKE) spc_example; \ fi # OpenSHMEM examples @@ -121,7 +118,7 @@ oshmem: clean: rm -f $(EXAMPLES) *~ *.o -# Don't rely on default rules for the Fortran and Java examples +# Don't rely on default rules for the Fortran examples hello_c: hello_c.c $(MPICC) $(CFLAGS) $(LDFLAGS) $? $(LDLIBS) -o $@ @@ -149,11 +146,6 @@ hello_usempif08: hello_usempif08.f90 ring_usempif08: ring_usempif08.f90 $(MPIFC) $(FCFLAGS) $(LDFLAGS) $? $(LDLIBS) -o $@ -Hello.class: Hello.java - $(MPIJAVAC) Hello.java -Ring.class: Ring.java - $(MPIJAVAC) Ring.java - hello_oshmem: hello_oshmem_c.c $(SHMEMCC) $(CFLAGS) $(LDFLAGS) $? $(LDLIBS) -o $@ hello_oshmemcxx: hello_oshmem_cxx.cc diff --git a/examples/Makefile.include b/examples/Makefile.include index 8da106cb507..1fe536d669b 100644 --- a/examples/Makefile.include +++ b/examples/Makefile.include @@ -16,6 +16,7 @@ # Copyright (c) 2013 Mellanox Technologies, Inc. All rights reserved. # Copyright (c) 2017 Research Organization for Information Science # and Technology (RIST). All rights reserved. +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -54,7 +55,5 @@ EXTRA_DIST += \ examples/oshmem_max_reduction.c \ examples/oshmem_strided_puts.c \ examples/oshmem_symmetric_data.c \ - examples/Hello.java \ - examples/Ring.java \ examples/spc_example.c \ examples/hello_sessions_c.c diff --git a/examples/README.md b/examples/README.md index 6a1ab9528cb..ec6e2e23e2e 100644 --- a/examples/README.md +++ b/examples/README.md @@ -21,7 +21,6 @@ The MPI version of the canonical "hello world" program: * Fortran mpif.h: `hello_mpifh.f` * Fortran use mpi: `hello_usempi.f90` * Fortran use mpi_f08: `hello_usempif08.f90` -* Java: `Hello.java` * C shmem.h: `hello_oshmem_c.c` * Fortran shmem.fh: `hello_oshmemfh.f90` @@ -34,7 +33,6 @@ Send a trivial message around in a ring: * Fortran mpif.h: `ring_mpifh.f` * Fortran use mpi: `ring_usempi.f90` * Fortran use mpi_f08: `ring_usempif08.f90` -* Java: `Ring.java` * C shmem.h: `ring_oshmem_c.c` * Fortran shmem.fh: `ring_oshmemfh.f90` diff --git a/examples/Ring.java b/examples/Ring.java deleted file mode 100644 index 6aa3770e21b..00000000000 --- a/examples/Ring.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright (c) 2011 Cisco Systems, Inc. All rights reserved. - * - * Simple ring test program - */ - -import mpi.* ; - -class Ring { - static public void main(String[] args) throws MPIException { - - - MPI.Init(args) ; - - int source; // Rank of sender - int dest; // Rank of receiver - int tag=50; // Tag for messages - int next; - int prev; - int message[] = new int [1]; - - int myrank = MPI.COMM_WORLD.getRank() ; - int size = MPI.COMM_WORLD.getSize() ; - - /* Calculate the rank of the next process in the ring. Use the - modulus operator so that the last process "wraps around" to - rank zero. */ - - next = (myrank + 1) % size; - prev = (myrank + size - 1) % size; - - /* If we are the "manager" process (i.e., MPI_COMM_WORLD rank 0), - put the number of times to go around the ring in the - message. */ - - if (0 == myrank) { - message[0] = 10; - - System.out.println("Process 0 sending " + message[0] + " to rank " + next + " (" + size + " processes in ring)"); - MPI.COMM_WORLD.send(message, 1, MPI.INT, next, tag); - } - - /* Pass the message around the ring. The exit mechanism works as - follows: the message (a positive integer) is passed around the - ring. Each time it passes rank 0, it is decremented. When - each processes receives a message containing a 0 value, it - passes the message on to the next process and then quits. By - passing the 0 message first, every process gets the 0 message - and can quit normally. */ - - while (true) { - MPI.COMM_WORLD.recv(message, 1, MPI.INT, prev, tag); - - if (0 == myrank) { - --message[0]; - System.out.println("Process 0 decremented value: " + message[0]); - } - - MPI.COMM_WORLD.send(message, 1, MPI.INT, next, tag); - if (0 == message[0]) { - System.out.println("Process " + myrank + " exiting"); - break; - } - } - - /* The last process does one extra send to process 0, which needs - to be received before the program can exit */ - - if (0 == myrank) { - MPI.COMM_WORLD.recv(message, 1, MPI.INT, prev, tag); - } - - MPI.Finalize(); - } -} diff --git a/ompi/Makefile.am b/ompi/Makefile.am index f855492ef15..79f992e7b35 100644 --- a/ompi/Makefile.am +++ b/ompi/Makefile.am @@ -21,7 +21,7 @@ # Copyright (c) 2018 FUJITSU LIMITED. All rights reserved. # Copyright (c) 2021 Amazon.com, Inc. or its affiliates. All Rights # reserved. -# Copyright (c) 2025 Jeffrey M. Squyres. All rights reserved. +# Copyright (c) 2025-2026 Jeffrey M. Squyres. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -94,10 +94,11 @@ SUBDIRS = \ mpi/fortran/mpiext-use-mpi-f08 \ $(MCA_ompi_FRAMEWORK_COMPONENT_DSO_SUBDIRS) -if OMPI_WANT_JAVA_BINDINGS +# Unit tests for the OMPI layer. Placed at the end of SUBDIRS so that +# libmpi (built by the "." entry above) exists before the test programs +# are linked during "make check". SUBDIRS += \ - mpi/java -endif + test # The ordering of the DIST_SUBDIRS isn't as important, but note that # its contents *are* different than SUBDIRS. In particular, the @@ -121,7 +122,7 @@ DIST_SUBDIRS = \ mpi/fortran/use-mpi-f08/mod \ mpi/fortran/use-mpi-f08/bindings \ mpi/fortran/mpiext-use-mpi-f08 \ - mpi/java \ + test \ $(OMPI_MPIEXT_ALL_SUBDIRS) \ $(MCA_ompi_FRAMEWORKS_SUBDIRS) \ $(MCA_ompi_FRAMEWORK_COMPONENT_ALL_SUBDIRS) diff --git a/ompi/attribute/attribute.c b/ompi/attribute/attribute.c index d224f4b8793..3654b43a23c 100644 --- a/ompi/attribute/attribute.c +++ b/ompi/attribute/attribute.c @@ -136,8 +136,9 @@ * -> *ret will equal 7. * * 5. Fortran MPI-1 reads the attribute value. The C int value is - * cast to a fortran INTEGER (i.e., MPI_Fint) -- potentially being - * truncated if sizeof(int) > sizeof(INTEGER). + * cast to a fortran INTEGER (i.e., MPI_Fint). configure requires + * sizeof(INTEGER) >= sizeof(int) (see OMPI_SETUP_MPI_FORTRAN), so + * this conversion is always exact. * * Example: INTEGER ret * CALL MPI_ATTR_GET(..., ret, ierr) diff --git a/ompi/attribute/attribute_predefined.c b/ompi/attribute/attribute_predefined.c index 3bc1849dc52..781bdbb70dd 100644 --- a/ompi/attribute/attribute_predefined.c +++ b/ompi/attribute/attribute_predefined.c @@ -143,6 +143,9 @@ int ompi_attr_create_predefined_keyvals(void) OMPI_SUCCESS != (rc = create_win(MPI_WIN_CREATE_FLAVOR)) || OMPI_SUCCESS != (rc = create_win(MPI_WIN_MODEL)) || OMPI_SUCCESS != (rc = create_comm(MPI_FT, false)) || /* not #if conditional on OPAL_ENABLE_FT_MPI for ABI */ + OMPI_SUCCESS != (rc = create_win(MPI_WIN_NOTIFICATION_NUM_SB)) || + OMPI_SUCCESS != (rc = create_win(MPI_WIN_NOTIFICATION_NUM_UB)) || + OMPI_SUCCESS != (rc = create_win(MPI_WIN_NOTIFICATION_VALUE_UB)) || 0) { ret = rc; } @@ -227,7 +230,10 @@ int ompi_attr_free_predefined(void) OMPI_SUCCESS != (rc = free_win(MPI_WIN_SIZE)) || OMPI_SUCCESS != (rc = free_win(MPI_WIN_DISP_UNIT)) || OMPI_SUCCESS != (rc = free_win(MPI_WIN_CREATE_FLAVOR)) || - OMPI_SUCCESS != (rc = free_win(MPI_WIN_MODEL))) { + OMPI_SUCCESS != (rc = free_win(MPI_WIN_MODEL)) || + OMPI_SUCCESS != (rc = free_win(MPI_WIN_NOTIFICATION_NUM_SB)) || + OMPI_SUCCESS != (rc = free_win(MPI_WIN_NOTIFICATION_NUM_UB)) || + OMPI_SUCCESS != (rc = free_win(MPI_WIN_NOTIFICATION_VALUE_UB))) { ret = rc; } diff --git a/ompi/communicator/comm.c b/ompi/communicator/comm.c index ce51aa7336a..78d491683d7 100644 --- a/ompi/communicator/comm.c +++ b/ompi/communicator/comm.c @@ -28,6 +28,7 @@ * reserved. * Copyright (c) 2023-2025 Advanced Micro Devices, Inc. All rights reserved. * Copyright (c) 2025 BULL S.A.S. All rights reserved. + * Copyright (c) 2026 NVIDIA Corporation. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -40,6 +41,8 @@ #include #include "ompi/constants.h" +#include "opal/mca/accelerator/accelerator.h" +#include "opal/mca/base/mca_base_var.h" #include "opal/mca/hwloc/base/base.h" #include "opal/mca/pmix/pmix-internal.h" #include "opal/util/string_copy.h" @@ -64,6 +67,7 @@ struct ompi_comm_split_type_hw_guided_t { const char *info_value; int split_type; + bool use_for_unguided; }; typedef struct ompi_comm_split_type_hw_guided_t ompi_comm_split_type_hw_guided_t; @@ -73,18 +77,19 @@ typedef struct ompi_comm_split_type_hw_guided_t ompi_comm_split_type_hw_guided_t * the order in this array must be from largest topology class to smallest. */ static const ompi_comm_split_type_hw_guided_t ompi_comm_split_type_hw_guided_support[] = { - {.info_value = "cluster", .split_type = OMPI_COMM_TYPE_CLUSTER}, - {.info_value = "cu", .split_type = OMPI_COMM_TYPE_CU}, - {.info_value = "host", .split_type = OMPI_COMM_TYPE_HOST}, - {.info_value = "mpi_shared_memory", .split_type = MPI_COMM_TYPE_SHARED}, - {.info_value = "board", .split_type = OMPI_COMM_TYPE_BOARD}, - {.info_value = "numanode", .split_type = OMPI_COMM_TYPE_NUMA}, - {.info_value = "socket", .split_type = OMPI_COMM_TYPE_SOCKET}, - {.info_value = "l3cache", .split_type = OMPI_COMM_TYPE_L3CACHE}, - {.info_value = "l2cache", .split_type = OMPI_COMM_TYPE_L2CACHE}, - {.info_value = "l1cache", .split_type = OMPI_COMM_TYPE_L1CACHE}, - {.info_value = "core", .split_type = OMPI_COMM_TYPE_CORE}, - {.info_value = "hwthread", .split_type = OMPI_COMM_TYPE_HWTHREAD}, + {.info_value = "cluster", .split_type = OMPI_COMM_TYPE_CLUSTER, .use_for_unguided = false}, + {.info_value = "nvlink", .split_type = OMPI_COMM_TYPE_NVLINK, .use_for_unguided = false}, + {.info_value = "cu", .split_type = OMPI_COMM_TYPE_CU, .use_for_unguided = true}, + {.info_value = "host", .split_type = OMPI_COMM_TYPE_HOST, .use_for_unguided = true}, + {.info_value = "mpi_shared_memory", .split_type = MPI_COMM_TYPE_SHARED, .use_for_unguided = true}, + {.info_value = "board", .split_type = OMPI_COMM_TYPE_BOARD, .use_for_unguided = true}, + {.info_value = "numanode", .split_type = OMPI_COMM_TYPE_NUMA, .use_for_unguided = true}, + {.info_value = "socket", .split_type = OMPI_COMM_TYPE_SOCKET, .use_for_unguided = true}, + {.info_value = "l3cache", .split_type = OMPI_COMM_TYPE_L3CACHE, .use_for_unguided = true}, + {.info_value = "l2cache", .split_type = OMPI_COMM_TYPE_L2CACHE, .use_for_unguided = true}, + {.info_value = "l1cache", .split_type = OMPI_COMM_TYPE_L1CACHE, .use_for_unguided = true}, + {.info_value = "core", .split_type = OMPI_COMM_TYPE_CORE, .use_for_unguided = true}, + {.info_value = "hwthread", .split_type = OMPI_COMM_TYPE_HWTHREAD, .use_for_unguided = true}, {.info_value = NULL}, }; @@ -278,10 +283,14 @@ int ompi_comm_set_nb (ompi_communicator_t **ncomm, ompi_communicator_t *oldcomm, /* NTH: use internal idup function that takes a local group argument */ ompi_comm_idup_internal (old_localcomm, newcomm->c_local_group, NULL, NULL, &newcomm->c_local_comm, req); + if (NULL != newcomm->c_local_comm + && !OMPI_COMM_IS_INTRINSIC(newcomm->c_local_comm)) { + OBJ_RETAIN(newcomm->c_local_comm); + } } else { - /* take ownership of the old communicator (it must be an intracommunicator) */ assert (OMPI_COMM_IS_INTRA(oldcomm)); newcomm->c_local_comm = oldcomm; + OBJ_RETAIN(newcomm->c_local_comm); } } else { newcomm->c_remote_group = newcomm->c_local_group; @@ -832,6 +841,7 @@ static int ompi_comm_split_type_get_part (ompi_group_t *group, const int split_t case OMPI_COMM_TYPE_CLUSTER: include = OPAL_PROC_ON_LOCAL_CLUSTER(locality); break; + case OMPI_COMM_TYPE_NVLINK: case MPI_COMM_TYPE_HW_GUIDED: case MPI_COMM_TYPE_HW_UNGUIDED: case MPI_COMM_TYPE_RESOURCE_GUIDED: @@ -913,6 +923,270 @@ static int ompi_comm_split_verify (ompi_communicator_t *comm, int split_type, in return OMPI_SUCCESS; } +static int ompi_comm_split_type_nvlink_domain_compare(const void *a, const void *b) +{ + return memcmp(a, b, OPAL_ACCELERATOR_NVLINK_CLUSTER_UUID_LEN); +} + +static int ompi_comm_split_type_nvlink_hex_nibble(char digit) +{ + if ('0' <= digit && digit <= '9') { + return digit - '0'; + } + if ('a' <= digit && digit <= 'f') { + return digit - 'a' + 10; + } + if ('A' <= digit && digit <= 'F') { + return digit - 'A' + 10; + } + return -1; +} + +static int ompi_comm_split_type_parse_nvlink_domain( + const char *value, opal_accelerator_cuda_nvlink_domain_t *domain) +{ + char uuid_string[2 * OPAL_ACCELERATOR_NVLINK_CLUSTER_UUID_LEN + 1] = {0}; + unsigned int clique_id; + int cuda_device; + int end = 0; + + if (NULL == value || + 3 != sscanf(value, "cuda_device=%d,cluster_uuid=%32[0123456789abcdefABCDEF],clique_id=%u%n", + &cuda_device, uuid_string, &clique_id, &end) || + '\0' != value[end] || + 2 * OPAL_ACCELERATOR_NVLINK_CLUSTER_UUID_LEN != strlen(uuid_string)) { + return OMPI_ERR_BAD_PARAM; + } + + domain->cuda_device = cuda_device; + domain->clique_id = (uint32_t) clique_id; + for (int i = 0; i < OPAL_ACCELERATOR_NVLINK_CLUSTER_UUID_LEN; ++i) { + int high = ompi_comm_split_type_nvlink_hex_nibble(uuid_string[2 * i]); + int low = ompi_comm_split_type_nvlink_hex_nibble(uuid_string[2 * i + 1]); + + if (0 > high || 0 > low) { + return OMPI_ERR_BAD_PARAM; + } + domain->cluster_uuid[i] = (uint8_t) ((high << 4) | low); + } + + return OMPI_SUCCESS; +} + +static int ompi_comm_split_type_get_nvlink_domain( + opal_accelerator_cuda_nvlink_domain_t *domain) +{ + char **value = NULL; + int var_id, rc; + + domain->cuda_device = MCA_ACCELERATOR_NO_DEVICE_ID; + memset(domain->cluster_uuid, 0, sizeof(domain->cluster_uuid)); + domain->clique_id = 0; + + /* The CUDA accelerator owns the cache and exposes it through this + * read-only MCA variable. If CUDA did not register or fill it, the default + * no-device domain above keeps the split color MPI_UNDEFINED. */ + var_id = mca_base_var_find("opal", "accelerator", NULL, "nvlink_domain"); + if (0 > var_id) { + return OMPI_ERR_NOT_FOUND; + } + + rc = mca_base_var_get_value(var_id, &value, NULL, NULL); + if (OMPI_SUCCESS != rc || NULL == value) { + return rc; + } + + return ompi_comm_split_type_parse_nvlink_domain(*value, domain); +} + +#if OPAL_ENABLE_DEBUG +static void ompi_comm_split_type_nvlink_uuid_to_hex( + const uint8_t uuid[OPAL_ACCELERATOR_NVLINK_CLUSTER_UUID_LEN], + char hex_uuid[2 * OPAL_ACCELERATOR_NVLINK_CLUSTER_UUID_LEN + 1]) +{ + static const char hex[] = "0123456789abcdef"; + + for (int i = 0; i < OPAL_ACCELERATOR_NVLINK_CLUSTER_UUID_LEN; ++i) { + hex_uuid[2 * i] = hex[uuid[i] >> 4]; + hex_uuid[2 * i + 1] = hex[uuid[i] & 0x0f]; + } + hex_uuid[2 * OPAL_ACCELERATOR_NVLINK_CLUSTER_UUID_LEN] = '\0'; +} +#endif + +#define OMPI_COMM_SPLIT_TYPE_NVLINK_UUID_WORDS 4 +#define OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS OMPI_COMM_SPLIT_TYPE_NVLINK_UUID_WORDS +#define OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_SIZE \ + (OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS * sizeof(int)) + +static int ompi_comm_split_type_nvlink(ompi_communicator_t *comm, int local_split_type, + opal_info_t *info, ompi_communicator_t **newcomm) +{ + opal_accelerator_cuda_nvlink_domain_t my_domain = { + .cuda_device = MCA_ACCELERATOR_NO_DEVICE_ID, + }; + int my_token[OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS] = {0}; + int *domains = NULL; + int local_size = ompi_comm_size(comm), remote_size = 0, max_domains; + int inter, send_first = 0, local_offset = 0, remote_offset = 0; + int color = MPI_UNDEFINED, rc = OMPI_SUCCESS; + bool have_domain = false; + + /* The allgather payload is only the 16-byte color identifier, carried as + * MPI_INTs through the split-time allgather/broadcast exchange. The + * communicator uses the MCA-backed NVLink domain cache exactly as stored. + * Its default is an all-zero UUID with cuda_device set to + * MCA_ACCELERATOR_NO_DEVICE_ID; ranks in that state still enter the + * collective exchange with a zero token, but keep MPI_UNDEFINED as their + * split color because the default does not imply an NVLink domain. Only the + * CUDA accelerator may replace NVML's active single-node all-zero + * clusterUuid with a hostname-derived token. + */ + inter = OMPI_COMM_IS_INTER(comm); + if (inter) { + remote_size = ompi_comm_remote_size(comm); + send_first = ompi_comm_determine_first_auto(comm); + if (send_first) { + remote_offset = local_size * OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS; + } else { + local_offset = remote_size * OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS; + } + } + + max_domains = local_size + remote_size; + domains = malloc(max_domains * OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_SIZE); + /* Do not add a collective allocation check here: all ranks must enter the + * same split-time allgather sequence. For now assume the local allocations + * succeed. */ + + if (MPI_UNDEFINED != local_split_type) { + (void) ompi_comm_split_type_get_nvlink_domain(&my_domain); + if (MCA_ACCELERATOR_NO_DEVICE_ID != my_domain.cuda_device) { + memcpy(my_token, my_domain.cluster_uuid, + OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_SIZE); + have_domain = true; + } + } + + rc = comm->c_coll->coll_allgather(my_token, + OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS, + MPI_INT, domains + remote_offset, + OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS, + MPI_INT, comm, + comm->c_coll->coll_allgather_module); + if (OMPI_SUCCESS != rc) { + goto failure; + } + + if (inter) { + int local_root = 0 == ompi_comm_rank(comm) ? MPI_ROOT : MPI_PROC_NULL; + int *bcast_domains; + int bcast_count, bcast_root; + + /* Intercommunicator allgather gives each group the peer group's tokens. + * Use the deterministic send_first value to establish one global order + * for the domains array on both groups: all ranks from the "first" side + * followed by all ranks from the "second" side. The allgather writes + * the peer block directly into its final global-order position. The two + * broadcasts then send each peer block back to its owning side so every + * process ends with an identical domains[] layout. The conditional + * expressions below pick the block, count, and root for each direction: + * first the second-side block, then the first-side block. This common + * order is what lets the color assignment below avoid sorting. */ + bcast_domains = domains + (send_first ? remote_offset : local_offset); + bcast_count = (send_first ? remote_size : local_size) + * OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS; + bcast_root = send_first ? local_root : 0; + rc = comm->c_coll->coll_bcast(bcast_domains, bcast_count, MPI_INT, + bcast_root, comm, + comm->c_coll->coll_bcast_module); + if (OMPI_SUCCESS != rc) { + goto failure; + } + + bcast_domains = domains + (send_first ? local_offset : remote_offset); + bcast_count = (send_first ? local_size : remote_size) + * OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS; + bcast_root = send_first ? 0 : local_root; + rc = comm->c_coll->coll_bcast(bcast_domains, bcast_count, MPI_INT, + bcast_root, comm, + comm->c_coll->coll_bcast_module); + if (OMPI_SUCCESS != rc) { + goto failure; + } + } + +#if OPAL_ENABLE_DEBUG + for (int i = 0; i < max_domains; ++i) { + int *domain = domains + i * OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS; + char hex_uuid[2 * OPAL_ACCELERATOR_NVLINK_CLUSTER_UUID_LEN + 1]; + + ompi_comm_split_type_nvlink_uuid_to_hex((const uint8_t *) domain, + hex_uuid); + OPAL_OUTPUT_VERBOSE((10, ompi_comm_output, "rank %d: nvlink domain[%d] uuid=%s", + ompi_comm_rank(comm), i, hex_uuid)); + } +#endif + + if (have_domain) { + int unique_count = 0; + + /* Build the color map in global domain order without sorting. The + * domains array is identical on all processes after the exchange above, + * so the first occurrence of each token is globally deterministic. + * + * As we scan domains[], keep the unique tokens compacted in the prefix + * domains[0..unique_count). A candidate token is unique only if it does + * not match any token already in that prefix. Once a candidate is known + * to be unique, check whether it is the local token before moving it + * into domains[unique_count]; unique_count is then exactly the color for + * that token. Stop as soon as the local token is assigned, because later + * colors do not affect this process. */ + for (int i = 0; MPI_UNDEFINED == color && i < max_domains; ++i) { + int *domain = domains + i * OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS; + int *unique_domain = domains + unique_count * OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS; + bool seen = false; + + for (int j = 0; j < unique_count; ++j) { + int *prior_domain = domains + j * OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_INTS; + + if (0 == ompi_comm_split_type_nvlink_domain_compare(domain, prior_domain)) { + seen = true; + break; + } + } + + if (seen) { + continue; + } + + if (0 == ompi_comm_split_type_nvlink_domain_compare(my_token, domain)) { + color = unique_count; + /* We only need to enumerate colors up to the local process' + * NVLink domain. */ + break; + } + if (domain != unique_domain) { + memcpy(unique_domain, domain, OMPI_COMM_SPLIT_TYPE_NVLINK_TOKEN_SIZE); + } + ++unique_count; + } + } + + free(domains); + + return ompi_comm_split_with_info(comm, color, (int) my_domain.clique_id, info, newcomm, + false); + +failure: + /* Reaching this path means the split-time collective exchange failed, so + * the collective behavior of this function is already compromised. A future + * failure path should revoke the input communicator and return MPI_COMM_NULL + * as the new communicator. */ + free(domains); + return ompi_comm_split_with_info(comm, MPI_UNDEFINED, 0, info, newcomm, false); +} + /** * ompi_comm_split_type_core: Perform common processing for a MPI_Comm_type_split * function call. @@ -1116,13 +1390,16 @@ static int ompi_comm_split_unguided(ompi_communicator_t *comm, int split_type, i * calling ompi_comm_split_type specifying the split type as * MPI_COMM_TYPE_HW_GUIDED using the next lower topology class until a * split results in a smaller size communicator than the input communicator. - * The search starts with OMPI_COMM_TYPE_CU since that is the highest possible - * topology class where the communicator size can be smaller than MPI_COMM_WORLD. */ original_size = ompi_comm_size(unguided_comm); split_info = OBJ_NEW(opal_info_t); - i = 1; + i = 0; while (NULL != ompi_comm_split_type_hw_guided_support[i].info_value) { + if (!ompi_comm_split_type_hw_guided_support[i].use_for_unguided) { + i = i + 1; + continue; + } + /* MPI_COMM_TYPE_HW_GUIDED splits require mpi_hw_resource_type to be set */ opal_info_set(split_info, "mpi_hw_resource_type", ompi_comm_split_type_hw_guided_support[i].info_value); @@ -1305,6 +1582,8 @@ int ompi_comm_split_type (ompi_communicator_t *comm, int split_type, int key, return ompi_comm_split_unguided( comm, split_type, key, need_split, no_reorder, no_undefined, info, newcomm ); + } else if (OMPI_COMM_TYPE_NVLINK == global_split_type) { + return ompi_comm_split_type_nvlink(comm, split_type, info, newcomm); } else { return ompi_comm_split_type_core( comm, global_split_type, split_type, key, need_split, no_reorder, @@ -2169,8 +2448,6 @@ static int ompi_comm_allgather_emulate_intra( void *inbuf, int incount, int ompi_comm_free( ompi_communicator_t **comm ) { int ret; - int cid = (*comm)->c_index; - int is_extra_retain = OMPI_COMM_IS_EXTRA_RETAIN(*comm); /* Release attributes. We do this now instead of during the communicator destructor for 2 reasons: @@ -2199,7 +2476,9 @@ int ompi_comm_free( ompi_communicator_t **comm ) } if ( OMPI_COMM_IS_INTER(*comm) ) { - if ( ! OMPI_COMM_IS_INTRINSIC((*comm)->c_local_comm)) { + if (NULL != (*comm)->c_local_comm + && ! OMPI_COMM_IS_INTRINSIC((*comm)->c_local_comm)) { + OBJ_RELEASE((*comm)->c_local_comm); ompi_comm_free (&(*comm)->c_local_comm); } } @@ -2222,29 +2501,6 @@ int ompi_comm_free( ompi_communicator_t **comm ) } OBJ_RELEASE( (*comm) ); - if ( is_extra_retain) { - /* This communicator has been marked as an "extra retain" - * communicator. This can happen if a communicator creates - * 'dependent' subcommunicators (e.g. for inter - * communicators or when using hierarch collective - * module *and* the cid of the dependent communicator - * turned out to be lower than of the parent one. - * In that case, the reference counter has been increased - * by one more, in order to handle the scenario, - * that the user did not free the communicator. - * Note, that if we enter this routine, we can - * decrease the counter by one more therefore. However, - * in ompi_comm_finalize, we only used OBJ_RELEASE instead - * of ompi_comm_free(), and the increased reference counter - * makes sure that the pointer to the dependent communicator - * still contains a valid object. - */ - ompi_communicator_t *tmpcomm = ompi_comm_lookup(cid); - if ( NULL != tmpcomm ){ - ompi_comm_free(&tmpcomm); - } - } - *comm = MPI_COMM_NULL; return OMPI_SUCCESS; } diff --git a/ompi/communicator/comm_cid.c b/ompi/communicator/comm_cid.c index ddf1657b9ab..86df2f6e48a 100644 --- a/ompi/communicator/comm_cid.c +++ b/ompi/communicator/comm_cid.c @@ -26,6 +26,7 @@ * Copyright (c) 2021 Nanook Consulting. All rights reserved. * Copyright (c) 2020-2026 Triad National Security, LLC. All rights * reserved. + * Copyright (c) 2026 NVIDIA Corporation. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -60,8 +61,6 @@ /* for use when we don't have a PMIx that supports CID generation */ opal_atomic_int64_t ompi_comm_next_base_cid = 1; -/* A macro comparing two CIDs */ -#define OMPI_COMM_CID_IS_LOWER(comm1,comm2) ( ((comm1)->c_index < (comm2)->c_index)? 1:0) struct ompi_comm_cid_context_t; @@ -926,30 +925,6 @@ static int ompi_comm_activate_complete (ompi_comm_cid_context_t *context) return ret; } - /* For an inter communicator, we have to deal with the potential - * problem of what is happening if the local_comm that we created - * has a lower CID than the parent comm. This is not a problem - * as long as the user calls MPI_Comm_free on the inter communicator. - * However, if the communicators are not freed by the user but released - * by Open MPI in MPI_Finalize, we walk through the list of still available - * communicators and free them one by one. Thus, local_comm is freed before - * the actual inter-communicator. However, the local_comm pointer in the - * inter communicator will still contain the 'previous' address of the local_comm - * and thus this will lead to a segmentation violation. In order to prevent - * that from happening, we increase the reference counter local_comm - * by one if its CID is lower than the parent. We cannot increase however - * its reference counter if the CID of local_comm is larger than - * the CID of the inter communicators, since a regular MPI_Comm_free would - * leave in that the case the local_comm hanging around and thus we would not - * recycle CID's properly, which was the reason and the cause for this trouble. - */ - if (OMPI_COMM_IS_INTER(*newcomm)) { - if (OMPI_COMM_CID_IS_LOWER(*newcomm, comm)) { - OMPI_COMM_SET_EXTRA_RETAIN (*newcomm); - OBJ_RETAIN (*newcomm); - } - } - /* done */ return OMPI_SUCCESS; } diff --git a/ompi/communicator/comm_init.c b/ompi/communicator/comm_init.c index e348fb364d3..8cec884d24a 100644 --- a/ompi/communicator/comm_init.c +++ b/ompi/communicator/comm_init.c @@ -26,7 +26,7 @@ * Copyright (c) 2018-2024 Triad National Security, LLC. All rights * reserved. * Copyright (c) 2023-2024 Advanced Micro Devices, Inc. All rights reserved. - * Copyright (c) 2023 NVIDIA Corporation. All rights reserved. + * Copyright (c) 2023-2026 NVIDIA Corporation. All rights reserved. * Copyright (c) 2025 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * @@ -343,9 +343,6 @@ static int ompi_comm_finalize (void) /* tear down MPI-3 predefined communicators (not initialized unless using MPI_Init) */ OBJ_DESTRUCT( &ompi_mpi_comm_self ); ompi_attr_delete_predefined_keyvals_for_wm(); - /* Destroy the keyhash even is user defined attributes are still attached. */ - OBJ_DESTRUCT(ompi_mpi_comm_world.comm.c_keyhash); - ompi_mpi_comm_world.comm.c_keyhash = NULL; OBJ_DESTRUCT( &ompi_mpi_comm_world ); ompi_comm_intrinsic_init = false; @@ -388,31 +385,26 @@ static int ompi_comm_finalize (void) for ( i=3; ic_name); + ompi_comm_dump ( comm); + OBJ_RELEASE(comm); } } +#endif /* OPAL_ENABLE_DEBUG */ OBJ_DESTRUCT (&ompi_mpi_communicators); OBJ_DESTRUCT (&ompi_comm_hash); @@ -521,12 +513,24 @@ static void ompi_comm_destruct(ompi_communicator_t* comm) MCA_PML_CALL(del_comm (comm)); } + /* Release the attributes */ + if( NULL != comm->c_keyhash) { + OBJ_RELEASE(comm->c_keyhash); + comm->c_keyhash = NULL; + } + /* Release topology module */ if (NULL != comm->c_topo) { OBJ_RELEASE(comm->c_topo); comm->c_topo = NULL; } + if (OMPI_COMM_IS_INTER(comm) && NULL != comm->c_local_comm + && !OMPI_COMM_IS_INTRINSIC(comm->c_local_comm)) { + OBJ_RELEASE(comm->c_local_comm); + comm->c_local_comm = NULL; + } + if (NULL != comm->c_local_group) { OBJ_RELEASE ( comm->c_local_group ); comm->c_local_group = NULL; diff --git a/ompi/communicator/communicator.h b/ompi/communicator/communicator.h index 914230702d5..3e0958cebc6 100644 --- a/ompi/communicator/communicator.h +++ b/ompi/communicator/communicator.h @@ -25,7 +25,7 @@ * Copyright (c) 2018-2026 Triad National Security, LLC. All rights * reserved. * Copyright (c) 2023 Advanced Micro Devices, Inc. All rights reserved. - * Copyright (c) 2024 NVIDIA Corporation. All rights reserved. + * Copyright (c) 2024-2026 NVIDIA Corporation. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -69,7 +69,6 @@ OMPI_DECLSPEC OBJ_CLASS_DECLARATION(ompi_communicator_t); #define OMPI_COMM_GRAPH 0x00000200 #define OMPI_COMM_DIST_GRAPH 0x00000400 #define OMPI_COMM_PML_ADDED 0x00001000 -#define OMPI_COMM_EXTRA_RETAIN 0x00004000 #define OMPI_COMM_MAPBY_NODE 0x00008000 #define OMPI_COMM_GLOBAL_INDEX 0x00010000 @@ -86,7 +85,6 @@ OMPI_DECLSPEC OBJ_CLASS_DECLARATION(ompi_communicator_t); #define OMPI_COMM_IS_DISJOINT_SET(comm) ((comm)->c_flags & OMPI_COMM_DISJOINT_SET) #define OMPI_COMM_IS_DISJOINT(comm) ((comm)->c_flags & OMPI_COMM_DISJOINT) #define OMPI_COMM_IS_PML_ADDED(comm) ((comm)->c_flags & OMPI_COMM_PML_ADDED) -#define OMPI_COMM_IS_EXTRA_RETAIN(comm) ((comm)->c_flags & OMPI_COMM_EXTRA_RETAIN) #define OMPI_COMM_IS_TOPO(comm) (OMPI_COMM_IS_CART((comm)) || \ OMPI_COMM_IS_GRAPH((comm)) || \ OMPI_COMM_IS_DIST_GRAPH((comm))) @@ -97,7 +95,6 @@ OMPI_DECLSPEC OBJ_CLASS_DECLARATION(ompi_communicator_t); #define OMPI_COMM_SET_INVALID(comm) ((comm)->c_flags |= OMPI_COMM_INVALID) #define OMPI_COMM_SET_PML_ADDED(comm) ((comm)->c_flags |= OMPI_COMM_PML_ADDED) -#define OMPI_COMM_SET_EXTRA_RETAIN(comm) ((comm)->c_flags |= OMPI_COMM_EXTRA_RETAIN) #define OMPI_COMM_SET_MAPBY_NODE(comm) ((comm)->c_flags |= OMPI_COMM_MAPBY_NODE) #define OMPI_COMM_ASSERT_NO_ANY_TAG 0x00000001 @@ -148,8 +145,6 @@ OMPI_DECLSPEC OBJ_CLASS_DECLARATION(ompi_communicator_t); */ #define OMPI_COMM_SENTINEL 0x00000001 -/* A macro comparing two CIDs */ -#define OMPI_COMM_CID_IS_LOWER(comm1,comm2) ( ((comm1)->c_index < (comm2)->c_index)? 1:0) OMPI_DECLSPEC extern opal_hash_table_t ompi_comm_hash; OMPI_DECLSPEC extern opal_pointer_array_t ompi_mpi_communicators; diff --git a/ompi/datatype/ompi_datatype.h b/ompi/datatype/ompi_datatype.h index 495609d9a7b..c9ae0018513 100644 --- a/ompi/datatype/ompi_datatype.h +++ b/ompi/datatype/ompi_datatype.h @@ -13,6 +13,7 @@ * Copyright (c) 2021 IBM Corporation. All rights reserved. * Copyright (c) 2025 Triad National Security, LLC. All rights reserved. * Copyright (c) 2026 NVIDIA Corporation. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -41,6 +42,7 @@ #include "ompi/constants.h" #include "opal/datatype/opal_convertor.h" #include "opal/util/output.h" +#include "ompi/util/count_disp_array.h" #include "mpi.h" BEGIN_C_DECLS @@ -125,7 +127,7 @@ OMPI_DECLSPEC int32_t ompi_datatype_default_convertors_init( void ); OMPI_DECLSPEC int32_t ompi_datatype_default_convertors_fini( void ); OMPI_DECLSPEC void ompi_datatype_dump (const ompi_datatype_t* pData); -OMPI_DECLSPEC ompi_datatype_t* ompi_datatype_create( int32_t expectedSize ); +OMPI_DECLSPEC ompi_datatype_t* ompi_datatype_create( size_t expectedSize ); static inline int32_t ompi_datatype_is_committed( const ompi_datatype_t* type ) @@ -152,7 +154,7 @@ ompi_datatype_is_predefined( const ompi_datatype_t* type ) } static inline int32_t -ompi_datatype_is_contiguous_memory_layout( const ompi_datatype_t* type, int32_t count ) +ompi_datatype_is_contiguous_memory_layout( const ompi_datatype_t* type, size_t count ) { return opal_datatype_is_contiguous_memory_layout(&type->super, count); } @@ -190,27 +192,27 @@ ompi_datatype_add( ompi_datatype_t* pdtBase, const ompi_datatype_t* pdtAdd, size OMPI_DECLSPEC int32_t ompi_datatype_duplicate( const ompi_datatype_t* oldType, ompi_datatype_t** newType ); -OMPI_DECLSPEC int32_t ompi_datatype_create_contiguous( int count, const ompi_datatype_t* oldType, ompi_datatype_t** newType ); -OMPI_DECLSPEC int32_t ompi_datatype_create_vector( int count, int bLength, int stride, +OMPI_DECLSPEC int32_t ompi_datatype_create_contiguous( size_t count, const ompi_datatype_t* oldType, ompi_datatype_t** newType ); +OMPI_DECLSPEC int32_t ompi_datatype_create_vector( size_t count, size_t bLength, ptrdiff_t stride, const ompi_datatype_t* oldType, ompi_datatype_t** newType ); -OMPI_DECLSPEC int32_t ompi_datatype_create_hvector( int count, int bLength, ptrdiff_t stride, +OMPI_DECLSPEC int32_t ompi_datatype_create_hvector( size_t count, size_t bLength, ptrdiff_t stride, const ompi_datatype_t* oldType, ompi_datatype_t** newType ); -OMPI_DECLSPEC int32_t ompi_datatype_create_indexed( int count, const int* pBlockLength, const int* pDisp, +OMPI_DECLSPEC int32_t ompi_datatype_create_indexed( size_t count, const ompi_count_array_t pBlockLength, const ompi_disp_array_t pDisp, const ompi_datatype_t* oldType, ompi_datatype_t** newType ); -OMPI_DECLSPEC int32_t ompi_datatype_create_hindexed( int count, const int* pBlockLength, const ptrdiff_t* pDisp, +OMPI_DECLSPEC int32_t ompi_datatype_create_hindexed( size_t count, const ompi_count_array_t pBlockLength, const ompi_disp_array_t pDisp, const ompi_datatype_t* oldType, ompi_datatype_t** newType ); -OMPI_DECLSPEC int32_t ompi_datatype_create_indexed_block( int count, int bLength, const int* pDisp, +OMPI_DECLSPEC int32_t ompi_datatype_create_indexed_block( size_t count, size_t bLength, const ompi_disp_array_t pDisp, const ompi_datatype_t* oldType, ompi_datatype_t** newType ); -OMPI_DECLSPEC int32_t ompi_datatype_create_hindexed_block( int count, int bLength, const ptrdiff_t* pDisp, +OMPI_DECLSPEC int32_t ompi_datatype_create_hindexed_block( size_t count, size_t bLength, const ompi_disp_array_t pDisp, const ompi_datatype_t* oldType, ompi_datatype_t** newType ); -OMPI_DECLSPEC int32_t ompi_datatype_create_struct( int count, const int* pBlockLength, const ptrdiff_t* pDisp, +OMPI_DECLSPEC int32_t ompi_datatype_create_struct( size_t count, const ompi_count_array_t pBlockLength, const ompi_disp_array_t pDisp, ompi_datatype_t* const* pTypes, ompi_datatype_t** newType ); -OMPI_DECLSPEC int32_t ompi_datatype_create_darray( int size, int rank, int ndims, int const* gsize_array, - int const* distrib_array, int const* darg_array, - int const* psize_array, int order, const ompi_datatype_t* oldtype, +OMPI_DECLSPEC int32_t ompi_datatype_create_darray( int size, int rank, int ndims, const ompi_count_array_t gsize_array, + const int* distrib_array, const int* darg_array, + const int* psize_array, int order, const ompi_datatype_t* oldtype, ompi_datatype_t** newtype); -OMPI_DECLSPEC int32_t ompi_datatype_create_subarray(int ndims, int const* size_array, int const* subsize_array, - int const* start_array, int order, +OMPI_DECLSPEC int32_t ompi_datatype_create_subarray(int ndims, const ompi_count_array_t size_array, const ompi_count_array_t subsize_array, + const ompi_count_array_t start_array, int order, const ompi_datatype_t* oldtype, ompi_datatype_t** newtype); static inline int32_t ompi_datatype_create_resized( const ompi_datatype_t* oldType, @@ -297,25 +299,26 @@ ompi_datatype_copy_content_same_ddt( const ompi_datatype_t* type, size_t count, return 0; } -OMPI_DECLSPEC const ompi_datatype_t* ompi_datatype_match_size( int size, uint16_t datakind, uint16_t datalang ); +OMPI_DECLSPEC const ompi_datatype_t* ompi_datatype_match_size( size_t size, uint16_t datakind, uint16_t datalang ); /* * */ -OMPI_DECLSPEC int32_t ompi_datatype_sndrcv( const void *sbuf, int32_t scount, const ompi_datatype_t* sdtype, - void *rbuf, int32_t rcount, const ompi_datatype_t* rdtype); +OMPI_DECLSPEC int32_t ompi_datatype_sndrcv( const void *sbuf, size_t scount, const ompi_datatype_t* sdtype, + void *rbuf, size_t rcount, const ompi_datatype_t* rdtype); /* * */ OMPI_DECLSPEC int32_t ompi_datatype_get_args( const ompi_datatype_t* pData, int32_t which, - int32_t * ci, int32_t * i, - int32_t * ca, ptrdiff_t* a, - int32_t * cd, ompi_datatype_t** d, int32_t * type); + size_t * ci, int* i, + size_t * cl, MPI_Count* l, + size_t * ca, ptrdiff_t* a, + size_t * cd, ompi_datatype_t** d, int32_t * type); OMPI_DECLSPEC int32_t ompi_datatype_set_args( ompi_datatype_t* pData, - int32_t ci, const int32_t ** i, - int32_t ca, const ptrdiff_t* a, - int32_t cd, ompi_datatype_t* const * d,int32_t type); + size_t ci, size_t cl, const ompi_count_array_t *counts, + size_t ca, const ompi_disp_array_t a, + size_t cd, ompi_datatype_t* const * d,int32_t type); OMPI_DECLSPEC int32_t ompi_datatype_copy_args( const ompi_datatype_t* source_data, ompi_datatype_t* dest_data ); OMPI_DECLSPEC int32_t ompi_datatype_release_args( ompi_datatype_t* pData ); diff --git a/ompi/datatype/ompi_datatype_args.c b/ompi/datatype/ompi_datatype_args.c index 22e3c3f51f2..98a95d2ef92 100644 --- a/ompi/datatype/ompi_datatype_args.c +++ b/ompi/datatype/ompi_datatype_args.c @@ -16,6 +16,8 @@ * Copyright (c) 2015-2019 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2017 IBM Corporation. All rights reserved. + * Copyright (c) 2025-2026 Stony Brook University. All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -41,60 +43,73 @@ static inline int __ompi_datatype_pack_description( ompi_datatype_t* datatype, void** packed_buffer, int* next_index ); static ompi_datatype_t* -__ompi_datatype_create_from_args( int32_t* i, ptrdiff_t * a, +__ompi_datatype_create_from_args( const int* i, const size_t *l, const ptrdiff_t * a, ompi_datatype_t** d, int32_t type ); typedef struct __dt_args { opal_atomic_int32_t ref_count; int32_t create_type; size_t total_pack_size; - int32_t ci; - int32_t ca; - int32_t cd; - int* i; - ptrdiff_t* a; - ompi_datatype_t** d; + size_t ci; + size_t ca; + size_t cd; + size_t cl; + ptrdiff_t* a; + ompi_datatype_t** d; + size_t* l; // array of size_t counts + int* i; // array of integer counts } ompi_datatype_args_t; /** - * Some architectures really don't like having unaligned - * accesses. We'll be int aligned, because any sane system will - * require that. But we might not be long aligned, and some - * architectures will complain if a long is accessed on int - * alignment (but not long alignment). On those architectures, - * copy the buffer into an aligned buffer first. + * The packed datatype description interleaves 32-bit ints with word-size + * (size_t / ptrdiff_t) values. After writing an int the running pointer is + * only int-aligned, so accessing the following word-size value through it is + * an unaligned access -- which is undefined behavior and faults on + * strict-alignment architectures (e.g. RISC-V). Realign the pointer up to + * word size before every word-size access. The pack and unpack paths apply + * this identically, so the on-the-wire layout stays consistent. */ -#if OPAL_ALIGN_WORD_SIZE_INTEGERS #define OMPI_DATATYPE_ALIGN_PTR(PTR, TYPE) \ (PTR) = OPAL_ALIGN_PTR((PTR), sizeof(ptrdiff_t), TYPE) -#else -#define OMPI_DATATYPE_ALIGN_PTR(PTR, TYPE) -#endif /* OPAL_ALIGN_WORD_SIZE_INTEGERS */ + +/** + * Copies count elements from the given count array into either + * the integer or size_t destination depending on whether the + * count array is 32 or 64 bit. Advances the destination pointer. + */ +static inline void copy_count_array(size_t count, int**__restrict__ desti, size_t**__restrict__ destc, ompi_count_array_t array) { + size_t elem_size = opal_count_array_is_64bit(array) ? sizeof(size_t) : sizeof(int); + void *dest = opal_count_array_is_64bit(array) ? (void*)*destc : (void*)*desti; + memcpy(dest, opal_count_array_ptr(array), count * elem_size); + if (opal_count_array_is_64bit(array)) { + *destc += count; + } else { + *desti += count; + } +} int32_t ompi_datatype_set_args( ompi_datatype_t* pData, - int32_t ci, const int32_t** i, - int32_t ca, const ptrdiff_t* a, - int32_t cd, ompi_datatype_t* const * d, int32_t type) + size_t ci, size_t cl, const ompi_count_array_t *counts, + size_t ca, const opal_disp_array_t a, + size_t cd, ompi_datatype_t* const * d, int32_t type) { - int pos; + size_t pos; assert( NULL == pData->args ); - int length = sizeof(ompi_datatype_args_t) + ci * sizeof(int) + - ca * sizeof(ptrdiff_t) + cd * sizeof(MPI_Datatype); + + size_t length = sizeof(ompi_datatype_args_t) + ci * sizeof(int) + + cl * sizeof(size_t) + ca * sizeof(ptrdiff_t) + + cd * sizeof(MPI_Datatype); char* buf = (char*)malloc( length ); ompi_datatype_args_t* pArgs = (ompi_datatype_args_t*)buf; + size_t *pl = NULL; + int *pi = NULL; pArgs->ci = ci; pArgs->i = NULL; + pArgs->cl = cl; pArgs->l = NULL; pArgs->ca = ca; pArgs->a = NULL; pArgs->cd = cd; pArgs->d = NULL; pArgs->create_type = type; - /** - * Some architectures require 64 bits pointers (to pointers) to - * be 64 bits aligned. As in the ompi_datatype_args_t structure we have - * 2 such array of pointers and one to an array of ints, if we start by - * setting the 64 bits aligned one we will not have any trouble. Problem - * originally reported on SPARC 64. - */ buf += sizeof(ompi_datatype_args_t); if( 0 != pArgs->ca ) { pArgs->a = (ptrdiff_t*)buf; @@ -104,10 +119,18 @@ int32_t ompi_datatype_set_args( ompi_datatype_t* pData, pArgs->d = (ompi_datatype_t**)buf; buf += pArgs->cd * sizeof(MPI_Datatype); } - if( 0 != pArgs->ci ) pArgs->i = (int*)buf; + if (0 != pArgs->cl ) { + pArgs->l = pl = (size_t*)buf; + buf += pArgs->cl * sizeof(size_t); + } + if( 0 != pArgs->ci ) { + pArgs->i = pi = (int*)buf; + buf += pArgs->ci * sizeof(int); + } pArgs->ref_count = 1; - pArgs->total_pack_size = (4 + ci) * sizeof(int) + + pArgs->total_pack_size = 5 * sizeof(size_t) + ci * sizeof(int) + + cl * sizeof(size_t) + cd * sizeof(MPI_Datatype) + ca * sizeof(ptrdiff_t); switch(type) { @@ -117,92 +140,120 @@ int32_t ompi_datatype_set_args( ompi_datatype_t* pData, break; case MPI_COMBINER_CONTIGUOUS: - pArgs->i[0] = i[0][0]; + copy_count_array(1, &pi, &pl, counts[0]); break; case MPI_COMBINER_VECTOR: - pArgs->i[0] = i[0][0]; - pArgs->i[1] = i[1][0]; - pArgs->i[2] = i[2][0]; + copy_count_array(1, &pi, &pl, counts[0]); + copy_count_array(1, &pi, &pl, counts[1]); + copy_count_array(1, &pi, &pl, counts[2]); break; case MPI_COMBINER_HVECTOR_INTEGER: case MPI_COMBINER_HVECTOR: - pArgs->i[0] = i[0][0]; - pArgs->i[1] = i[1][0]; + copy_count_array(1, &pi, &pl, counts[0]); + copy_count_array(1, &pi, &pl, counts[1]); + if (cl > 0) { + // copy the stride + memcpy(pl, opal_count_array_ptr(counts[2]), sizeof(MPI_Count)); + pl++; + } break; - case MPI_COMBINER_INDEXED: - pos = 1; - pArgs->i[0] = i[0][0]; - memcpy( pArgs->i + pos, i[1], i[0][0] * sizeof(int) ); - pos += i[0][0]; - memcpy( pArgs->i + pos, i[2], i[0][0] * sizeof(int) ); + case MPI_COMBINER_INDEXED: { + size_t count = opal_count_array_get(counts[0], 0); + copy_count_array(1, &pi, &pl, counts[0]); + copy_count_array(count, &pi, &pl, counts[1]); + copy_count_array(count, &pi, &pl, counts[2]); break; + } case MPI_COMBINER_HINDEXED_INTEGER: - case MPI_COMBINER_HINDEXED: - pArgs->i[0] = i[0][0]; - memcpy( pArgs->i + 1, i[1], i[0][0] * sizeof(int) ); + case MPI_COMBINER_HINDEXED: { + size_t count = opal_count_array_get(counts[0], 0); + copy_count_array(1, &pi, &pl, counts[0]); + copy_count_array(count, &pi, &pl, counts[1]); + if (cl > 0) { + // copy the displacements + memcpy(pl, opal_count_array_ptr(counts[2]), count * sizeof(MPI_Count)); + pl += count; + } break; + } - case MPI_COMBINER_INDEXED_BLOCK: - pArgs->i[0] = i[0][0]; - pArgs->i[1] = i[1][0]; - memcpy( pArgs->i + 2, i[2], i[0][0] * sizeof(int) ); + case MPI_COMBINER_INDEXED_BLOCK: { + size_t count = opal_count_array_get(counts[0], 0); + copy_count_array(1, &pi, &pl, counts[0]); + copy_count_array(1, &pi, &pl, counts[1]); + copy_count_array(count, &pi, &pl, counts[2]); break; + } case MPI_COMBINER_STRUCT_INTEGER: - case MPI_COMBINER_STRUCT: - pArgs->i[0] = i[0][0]; - memcpy( pArgs->i + 1, i[1], i[0][0] * sizeof(int) ); + case MPI_COMBINER_STRUCT: { + size_t count = opal_count_array_get(counts[0], 0); + copy_count_array(1, &pi, &pl, counts[0]); + copy_count_array(count, &pi, &pl, counts[1]); + if (cl > 0) { + // copy the displacements + memcpy(pl, opal_count_array_ptr(counts[2]), count * sizeof(MPI_Count)); + pl += count; + } break; + } - case MPI_COMBINER_SUBARRAY: - pos = 1; - pArgs->i[0] = i[0][0]; - memcpy( pArgs->i + pos, i[1], pArgs->i[0] * sizeof(int) ); - pos += pArgs->i[0]; - memcpy( pArgs->i + pos, i[2], pArgs->i[0] * sizeof(int) ); - pos += pArgs->i[0]; - memcpy( pArgs->i + pos, i[3], pArgs->i[0] * sizeof(int) ); - pos += pArgs->i[0]; - pArgs->i[pos] = i[4][0]; + case MPI_COMBINER_SUBARRAY: { + size_t count = opal_count_array_get(counts[0], 0); + copy_count_array(1, &pi, &pl, counts[0]); + copy_count_array(count, &pi, &pl, counts[1]); + copy_count_array(count, &pi, &pl, counts[2]); + copy_count_array(count, &pi, &pl, counts[3]); + copy_count_array(1, &pi, &pl, counts[4]); break; + } - case MPI_COMBINER_DARRAY: - pos = 3; - pArgs->i[0] = i[0][0]; - pArgs->i[1] = i[1][0]; - pArgs->i[2] = i[2][0]; - - memcpy( pArgs->i + pos, i[3], i[2][0] * sizeof(int) ); - pos += i[2][0]; - memcpy( pArgs->i + pos, i[4], i[2][0] * sizeof(int) ); - pos += i[2][0]; - memcpy( pArgs->i + pos, i[5], i[2][0] * sizeof(int) ); - pos += i[2][0]; - memcpy( pArgs->i + pos, i[6], i[2][0] * sizeof(int) ); - pos += i[2][0]; - pArgs->i[pos] = i[7][0]; + case MPI_COMBINER_DARRAY: { + size_t ndim = opal_count_array_get(counts[2], 0); + copy_count_array(1, &pi, &pl, counts[0]); + copy_count_array(1, &pi, &pl, counts[1]); + copy_count_array(1, &pi, &pl, counts[2]); + copy_count_array(ndim, &pi, &pl, counts[3]); + copy_count_array(ndim, &pi, &pl, counts[4]); + copy_count_array(ndim, &pi, &pl, counts[5]); + copy_count_array(ndim, &pi, &pl, counts[6]); + copy_count_array(1, &pi, &pl, counts[7]); break; + } case MPI_COMBINER_F90_REAL: case MPI_COMBINER_F90_COMPLEX: - pArgs->i[0] = i[0][0]; - pArgs->i[1] = i[1][0]; + copy_count_array(1, &pi, &pl, counts[0]); + copy_count_array(1, &pi, &pl, counts[1]); break; case MPI_COMBINER_F90_INTEGER: - pArgs->i[0] = i[0][0]; + copy_count_array(1, &pi, &pl, counts[0]); break; case MPI_COMBINER_RESIZED: + /* The large-count interface passes lb and extent as two MPI_Count + * values in counts[0]; the classic interface passes them through + * the displacement array instead (cl == 0 here, handled by the + * generic MPI_Aint copy below). */ + if (cl > 0) { + copy_count_array(2, &pi, &pl, counts[0]); + } break; case MPI_COMBINER_HINDEXED_BLOCK: - pArgs->i[0] = i[0][0]; - pArgs->i[1] = i[1][0]; + copy_count_array(1, &pi, &pl, counts[0]); + copy_count_array(1, &pi, &pl, counts[1]); + if (cl > 0) { + // copy the displacements + size_t count = opal_count_array_get(counts[0], 0); + memcpy(pl, opal_count_array_ptr(counts[2]), count * sizeof(MPI_Count)); + pl += count; + } break; default: @@ -211,7 +262,7 @@ int32_t ompi_datatype_set_args( ompi_datatype_t* pData, /* copy the array of MPI_Aint, aka ptrdiff_t */ if( pArgs->a != NULL ) - memcpy( pArgs->a, a, ca * sizeof(ptrdiff_t) ); + memcpy( pArgs->a, ompi_disp_array_ptr(a), ca * sizeof(ptrdiff_t) ); for( pos = 0; pos < cd; pos++ ) { pArgs->d[pos] = d[pos]; @@ -239,7 +290,7 @@ int32_t ompi_datatype_set_args( ompi_datatype_t* pData, int32_t ompi_datatype_print_args( const ompi_datatype_t* pData ) { - int32_t i; + size_t i; ompi_datatype_args_t* pArgs = (ompi_datatype_args_t*)pData->args; if( ompi_datatype_is_predefined(pData) ) { @@ -249,15 +300,22 @@ int32_t ompi_datatype_print_args( const ompi_datatype_t* pData ) if( pArgs == NULL ) return MPI_ERR_INTERN; - printf( "type %d count ints %d count disp %d count datatype %d\n", - pArgs->create_type, pArgs->ci, pArgs->ca, pArgs->cd ); + printf( "type %d count ints %zu count counts %zu count disp %zu count datatype %zu\n", + pArgs->create_type, pArgs->ci, pArgs->cl, pArgs->ca, pArgs->cd ); if( pArgs->i != NULL ) { - printf( "ints: " ); + printf( "ints: "); for( i = 0; i < pArgs->ci; i++ ) { printf( "%d ", pArgs->i[i] ); } printf( "\n" ); } + if( pArgs->l != NULL ) { + printf( "counts: "); + for( i = 0; i < pArgs->cl; i++ ) { + printf( "%zu ", pArgs->l[i] ); + } + printf( "\n" ); + } if( pArgs->a != NULL ) { printf( "MPI_Aint: " ); for( i = 0; i < pArgs->ca; i++ ) { @@ -309,9 +367,10 @@ int32_t ompi_datatype_print_args( const ompi_datatype_t* pData ) int32_t ompi_datatype_get_args( const ompi_datatype_t* pData, int32_t which, - int32_t* ci, int32_t* i, - int32_t* ca, ptrdiff_t* a, - int32_t* cd, ompi_datatype_t** d, int32_t* type) + size_t* ci, int* i, + size_t* cl, MPI_Count* l, + size_t* ca, ptrdiff_t* a, + size_t* cd, ompi_datatype_t** d, int32_t* type) { ompi_datatype_args_t* pArgs = (ompi_datatype_args_t*)pData->args; @@ -320,6 +379,7 @@ int32_t ompi_datatype_get_args( const ompi_datatype_t* pData, int32_t which, switch(which){ case 0: *ci = 0; + *cl = 0; *ca = 0; *cd = 0; *type = MPI_COMBINER_NAMED; @@ -335,17 +395,21 @@ int32_t ompi_datatype_get_args( const ompi_datatype_t* pData, int32_t which, switch(which){ case 0: /* GET THE LENGTHS */ *ci = pArgs->ci; + *cl = pArgs->cl; *ca = pArgs->ca; *cd = pArgs->cd; *type = pArgs->create_type; break; case 1: /* GET THE ARGUMENTS */ - if(*ci < pArgs->ci || *ca < pArgs->ca || *cd < pArgs->cd) { + if(*ci < pArgs->ci || *cl < pArgs->cl || *ca < pArgs->ca || *cd < pArgs->cd) { return MPI_ERR_ARG; } if( (NULL != i) && (NULL != pArgs->i) ) { memcpy( i, pArgs->i, pArgs->ci * sizeof(int) ); } + if( (NULL != l) && (NULL != pArgs->l) ) { + memcpy( l, pArgs->l, pArgs->cl * sizeof(size_t) ); + } if( (NULL != a) && (NULL != pArgs->a) ) { memcpy( a, pArgs->a, pArgs->ca * sizeof(ptrdiff_t) ); } @@ -384,7 +448,7 @@ int32_t ompi_datatype_copy_args( const ompi_datatype_t* source_data, */ int32_t ompi_datatype_release_args( ompi_datatype_t* pData ) { - int i; + size_t i; ompi_datatype_args_t* pArgs = (ompi_datatype_args_t*)pData->args; assert( 0 < pArgs->ref_count ); @@ -409,13 +473,16 @@ int32_t ompi_datatype_release_args( ompi_datatype_t* pData ) static inline int __ompi_datatype_pack_description( ompi_datatype_t* datatype, void** packed_buffer, int* next_index ) { - int i, *position = (int*)*packed_buffer; + size_t i; + int *iposition = NULL; ompi_datatype_args_t* args = (ompi_datatype_args_t*)datatype->args; char* next_packed = (char*)*packed_buffer; + iposition = (int*)next_packed; + if( ompi_datatype_is_predefined(datatype) ) { - position[0] = MPI_COMBINER_NAMED; - position[1] = datatype->id; /* On the OMPI - layer, copy the ompi_datatype.id */ + iposition[0] = MPI_COMBINER_NAMED; + iposition[1] = datatype->id; /* On the OMPI - layer, copy the ompi_datatype.id */ next_packed += (2 * sizeof(int)); *packed_buffer = next_packed; return OMPI_SUCCESS; @@ -427,38 +494,43 @@ static inline int __ompi_datatype_pack_description( ompi_datatype_t* datatype, packed_buffer, next_index ); } - position[0] = args->create_type; - position[1] = args->ci; - position[2] = args->ca; - position[3] = args->cd; - next_packed += (4 * sizeof(int)); - /* Spoiler: We will access the data in this storage structure, and thus we - * need to align it to the expected boundaries (special thanks to Sparc64). - * The simplest way is to ensure that prior to each type that must be 64 - * bits aligned, we have a pointer that is 64 bits aligned. That will minimize - * the memory requirements in all cases where no displacements are stored. - */ + iposition[0] = args->create_type; + next_packed += sizeof(int); + /* align pointer to 64 bits */ + OMPI_DATATYPE_ALIGN_PTR(next_packed, char*); + size_t *cposition = ((size_t*)next_packed); + cposition[0] = args->ci; + cposition[1] = args->cl; + cposition[2] = args->ca; + cposition[3] = args->cd; + next_packed += (4 * sizeof(size_t)); if( 0 < args->ca ) { - /* description of the displacements must be 64 bits aligned */ - OMPI_DATATYPE_ALIGN_PTR(next_packed, char*); - memcpy( next_packed, args->a, sizeof(ptrdiff_t) * args->ca ); next_packed += sizeof(ptrdiff_t) * args->ca; } - position = (int*)next_packed; + if ( 0 < args->cl ) { + memcpy( next_packed, args->l, sizeof(size_t) * args->cl ); + next_packed += sizeof(size_t) * args->cl; + } + /* advance int pointer */ + iposition = (int*)next_packed; + + /* skip the datatypes */ next_packed += sizeof(int) * args->cd; - /* copy the array of counts (32 bits aligned) */ - memcpy( next_packed, args->i, sizeof(int) * args->ci ); - next_packed += args->ci * sizeof(int); + /* copy the array of 32bit counts at the end */ + if( 0 < args->ci ) { + memcpy( next_packed, args->i, sizeof(int) * args->ci ); + next_packed += args->ci * sizeof(int); + } /* copy the rest of the data */ for( i = 0; i < args->cd; i++ ) { ompi_datatype_t* temp_data = args->d[i]; if( ompi_datatype_is_predefined(temp_data) ) { - position[i] = temp_data->id; /* On the OMPI - layer, copy the ompi_datatype.id */ + iposition[i] = temp_data->id; /* On the OMPI - layer, copy the ompi_datatype.id */ } else { - position[i] = *next_index; + iposition[i] = *next_index; (*next_index)++; __ompi_datatype_pack_description( temp_data, (void**)&next_packed, @@ -548,13 +620,16 @@ size_t ompi_datatype_pack_description_length( ompi_datatype_t* datatype ) static ompi_datatype_t* __ompi_datatype_create_from_packed_description( void** packed_buffer, const struct ompi_proc_t* remote_processor ) { - int* position; + int* iposition; + size_t *cposition; ompi_datatype_t* datatype = NULL; ompi_datatype_t** array_of_datatype; ptrdiff_t* array_of_disp; - int* array_of_length; - int number_of_length, number_of_disp, number_of_datatype, data_id; - int create_type, i; + int* array_of_ints; + size_t *array_of_counts = NULL; + size_t number_of_ints, number_of_counts, number_of_disp, number_of_datatype, data_id; + int create_type; + size_t i; char* next_buffer; #if OPAL_ENABLE_HETEROGENEOUS_SUPPORT @@ -567,9 +642,14 @@ static ompi_datatype_t* __ompi_datatype_create_from_packed_description( void** p #endif next_buffer = (char*)*packed_buffer; - position = (int*)next_buffer; - - create_type = position[0]; + cposition = (size_t*)next_buffer; + iposition = (int*)next_buffer; + + create_type = (int)iposition[0]; + next_buffer += sizeof(int); + /* align pointer to 64 bits */ + OMPI_DATATYPE_ALIGN_PTR(next_buffer, char*); + cposition = (size_t*)next_buffer; #if OPAL_ENABLE_HETEROGENEOUS_SUPPORT if (need_swap) { create_type = opal_swap_bytes4(create_type); @@ -577,48 +657,50 @@ static ompi_datatype_t* __ompi_datatype_create_from_packed_description( void** p #endif if( MPI_COMBINER_NAMED == create_type ) { /* there we have a simple predefined datatype */ - data_id = position[1]; + data_id = iposition[1]; #if OPAL_ENABLE_HETEROGENEOUS_SUPPORT if (need_swap) { data_id = opal_swap_bytes4(data_id); } #endif assert( data_id < OMPI_DATATYPE_MAX_PREDEFINED ); - *packed_buffer = position + 2; + *packed_buffer = iposition + 2; return (ompi_datatype_t*)ompi_datatype_basicDatatypes[data_id]; } - number_of_length = position[1]; - number_of_disp = position[2]; - number_of_datatype = position[3]; + number_of_ints = cposition[0]; + number_of_counts = cposition[1]; + number_of_disp = cposition[2]; + number_of_datatype = cposition[3]; #if OPAL_ENABLE_HETEROGENEOUS_SUPPORT if (need_swap) { - number_of_length = opal_swap_bytes4(number_of_length); - number_of_disp = opal_swap_bytes4(number_of_disp); - number_of_datatype = opal_swap_bytes4(number_of_datatype); + number_of_ints = opal_swap_bytes8(number_of_ints); + number_of_counts = opal_swap_bytes8(number_of_counts); + number_of_disp = opal_swap_bytes8(number_of_disp); + number_of_datatype = opal_swap_bytes8(number_of_datatype); } #endif array_of_datatype = (ompi_datatype_t**)malloc( sizeof(ompi_datatype_t*) * number_of_datatype ); - next_buffer += (4 * sizeof(int)); /* move after the header */ - - /* description of the displacements (if ANY !) should always be aligned - on MPI_Aint, aka ptrdiff_t */ - if (number_of_disp > 0) { - OMPI_DATATYPE_ALIGN_PTR(next_buffer, char*); - } - + next_buffer += (4 * sizeof(size_t)); /* move after the header */ + /* the array of displacements */ array_of_disp = (ptrdiff_t*)next_buffer; next_buffer += number_of_disp * sizeof(ptrdiff_t); + if (number_of_counts > 0) { + array_of_counts = (size_t*)next_buffer; + next_buffer += number_of_counts * sizeof(size_t); + } /* the other datatypes */ - position = (int*)next_buffer; + iposition = (int*)next_buffer; next_buffer += number_of_datatype * sizeof(int); /* the array of lengths (32 bits aligned) */ - array_of_length = (int*)next_buffer; - next_buffer += (number_of_length * sizeof(int)); + if (number_of_ints > 0) { + array_of_ints = (int*)next_buffer; + next_buffer += number_of_ints * sizeof(int); + } for( i = 0; i < number_of_datatype; i++ ) { - data_id = position[i]; + data_id = iposition[i]; #if OPAL_ENABLE_HETEROGENEOUS_SUPPORT if (need_swap) { data_id = opal_swap_bytes4(data_id); @@ -644,8 +726,11 @@ static ompi_datatype_t* __ompi_datatype_create_from_packed_description( void** p #if OPAL_ENABLE_HETEROGENEOUS_SUPPORT if (need_swap) { - for (i = 0 ; i < number_of_length ; ++i) { - array_of_length[i] = opal_swap_bytes4(array_of_length[i]); + for (i = 0 ; i < number_of_ints ; ++i) { + array_of_ints[i] = opal_swap_bytes4(array_of_ints[i]); + } + for (i = 0 ; i < number_of_counts ; ++i) { + array_of_counts[i] = opal_swap_bytes8(array_of_counts[i]); } for (i = 0 ; i < number_of_disp ; ++i) { #if SIZEOF_PTRDIFF_T == 4 @@ -658,7 +743,7 @@ static ompi_datatype_t* __ompi_datatype_create_from_packed_description( void** p } } #endif - datatype = __ompi_datatype_create_from_args( array_of_length, array_of_disp, + datatype = __ompi_datatype_create_from_args( array_of_ints, array_of_counts, array_of_disp, array_of_datatype, create_type ); *packed_buffer = next_buffer; cleanup_and_exit: @@ -671,11 +756,14 @@ static ompi_datatype_t* __ompi_datatype_create_from_packed_description( void** p return datatype; } -static ompi_datatype_t* __ompi_datatype_create_from_args( int32_t* i, MPI_Aint* a, +static ompi_datatype_t* __ompi_datatype_create_from_args( const int* i, const size_t *l, const ptrdiff_t* a, ompi_datatype_t** d, int32_t type ) { + size_t count, ci = 0, cl = 0; ompi_datatype_t* datatype = NULL; + ompi_disp_array_t disp_array = OMPI_DISP_ARRAY_CREATE(a); + switch(type){ /******************************************************************/ case MPI_COMBINER_DUP: @@ -684,81 +772,243 @@ static ompi_datatype_t* __ompi_datatype_create_from_args( int32_t* i, MPI_Aint* assert(0); /* shouldn't happen */ break; /******************************************************************/ - case MPI_COMBINER_CONTIGUOUS: - ompi_datatype_create_contiguous( i[0], d[0], &datatype ); - ompi_datatype_set_args( datatype, 1, (const int **) &i, 0, NULL, 1, d, MPI_COMBINER_CONTIGUOUS ); + case MPI_COMBINER_CONTIGUOUS: { + ompi_count_array_t a_i[1]; + if (l == NULL) { + count = i[0]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(i); + ci = 1; + } else { // large count variant + count = l[0]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(l); + cl = 1; + } + ompi_datatype_create_contiguous( count, d[0], &datatype ); + ompi_datatype_set_args( datatype, ci, cl, a_i, 0, OMPI_DISP_ARRAY_NULL, 1, d, MPI_COMBINER_CONTIGUOUS ); break; + } /******************************************************************/ - case MPI_COMBINER_VECTOR: - ompi_datatype_create_vector( i[0], i[1], i[2], d[0], &datatype ); - { - const int* a_i[3] = {&i[0], &i[1], &i[2]}; - ompi_datatype_set_args( datatype, 3, a_i, 0, NULL, 1, d, MPI_COMBINER_VECTOR ); + case MPI_COMBINER_VECTOR: { + size_t blocklength, stride; + opal_count_array_t a_i[3]; + if (l == NULL) { + count = i[0]; + blocklength= i[1]; + stride = i[2]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(i); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(i + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(i + 2); + ci = 3; + } else { // large count variant + count = l[0]; + blocklength= l[1]; + stride = l[2]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(l); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(l + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(l + 2); + cl = 3; } + ompi_datatype_create_vector( count, blocklength, stride, d[0], &datatype ); + ompi_datatype_set_args( datatype, ci, cl, a_i, 0, OMPI_DISP_ARRAY_NULL, 1, d, MPI_COMBINER_VECTOR ); break; + } /******************************************************************/ case MPI_COMBINER_HVECTOR_INTEGER: case MPI_COMBINER_HVECTOR: - ompi_datatype_create_hvector( i[0], i[1], a[0], d[0], &datatype ); { - const int* a_i[2] = {&i[0], &i[1]}; - ompi_datatype_set_args( datatype, 2, a_i, 1, a, 1, d, MPI_COMBINER_HVECTOR ); + size_t blocklength; + ptrdiff_t stride; + opal_count_array_t a_i[3]; + size_t ca = 0; + if (l == NULL) { + count = i[0]; + blocklength = i[1]; + stride = a[0]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(i); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(i + 1); + ci = 2; + ca = 1; // stride stored in disp_array + } else { // large count variant + count = l[0]; + blocklength = l[1]; + stride = l[2]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(l); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(l + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(l + 2); + cl = 3; + } + ompi_datatype_create_hvector( count, blocklength, stride, d[0], &datatype ); + ompi_datatype_set_args( datatype, ci, cl, a_i, ca, disp_array, 1, d, MPI_COMBINER_HVECTOR ); } break; /******************************************************************/ case MPI_COMBINER_INDEXED: /* TO CHECK */ - ompi_datatype_create_indexed( i[0], &(i[1]), &(i[1+i[0]]), d[0], &datatype ); { - const int* a_i[3] = {&i[0], &i[1], &(i[1+i[0]])}; - ompi_datatype_set_args( datatype, 2 * i[0] + 1, a_i, 0, NULL, 1, d, MPI_COMBINER_INDEXED ); + opal_count_array_t a_i[3]; + if (l == NULL) { + count = i[0]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(i); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(i + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(i + 1 + count); + ci = 2 * count + 1; + } else { + count = l[0]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(l); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(l + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(l + 1 + count); + cl = 2 * count + 1; + } + ompi_datatype_create_indexed( count, a_i[1], a_i[2], d[0], &datatype ); + ompi_datatype_set_args( datatype, ci, cl, a_i, 0, OMPI_DISP_ARRAY_NULL, 1, d, MPI_COMBINER_INDEXED ); } break; /******************************************************************/ case MPI_COMBINER_HINDEXED_INTEGER: case MPI_COMBINER_HINDEXED: - ompi_datatype_create_hindexed( i[0], &(i[1]), a, d[0], &datatype ); { - const int* a_i[2] = {&i[0], &i[1]}; - ompi_datatype_set_args( datatype, i[0] + 1, a_i, i[0], a, 1, d, MPI_COMBINER_HINDEXED ); + opal_count_array_t a_i[3]; + size_t ca = 0; + opal_disp_array_t disp_args; // for set_args + opal_disp_array_t displacements; // for create_hindexed + if (l == NULL) { + count = i[0]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(i); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(i + 1); + ci = count+1; + ca = count; + disp_args = disp_array; + displacements = disp_array; + } else { + count = l[0]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(l); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(l + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(l + 1 + count); // displacements are MPI_Count + cl = 2*count+1; + disp_args = OMPI_DISP_ARRAY_NULL; + displacements = OMPI_DISP_ARRAY_CREATE(l + 1 + count); + } + ompi_datatype_create_hindexed( count, a_i[1], displacements, d[0], &datatype ); + ompi_datatype_set_args( datatype, ci, cl, a_i, ca, disp_args, 1, d, MPI_COMBINER_HINDEXED ); } break; /******************************************************************/ case MPI_COMBINER_INDEXED_BLOCK: - ompi_datatype_create_indexed_block( i[0], i[1], &(i[2]), d[0], &datatype ); { - const int* a_i[3] = {&i[0], &i[1], &i[2]}; - ompi_datatype_set_args( datatype, i[0] + 2, a_i, 0, NULL, 1, d, MPI_COMBINER_INDEXED_BLOCK ); + opal_count_array_t a_i[3]; + size_t blocklength; + if (l == NULL) { + count = i[0]; + blocklength = i[1]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(i); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(i + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(i + 2); + ci = 2 + count; + } else { + count = l[0]; + blocklength = l[1]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(l); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(l + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(l + 2); + cl = 2 + count; + } + ompi_datatype_create_indexed_block( count, blocklength, a_i[2], d[0], &datatype ); + ompi_datatype_set_args( datatype, ci, cl, a_i, 0, OMPI_DISP_ARRAY_NULL, 1, d, MPI_COMBINER_INDEXED_BLOCK ); } break; /******************************************************************/ case MPI_COMBINER_STRUCT_INTEGER: case MPI_COMBINER_STRUCT: - ompi_datatype_create_struct( i[0], &(i[1]), a, d, &datatype ); { - const int* a_i[2] = {&i[0], &i[1]}; - ompi_datatype_set_args( datatype, i[0] + 1, a_i, i[0], a, i[0], d, MPI_COMBINER_STRUCT ); + opal_count_array_t a_i[3]; + opal_disp_array_t displacements; + opal_disp_array_t disp_args; + size_t ca = 0; + if (l == NULL) { + count = i[0]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(i); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(i + 1); + ci = 2 * count + 1; + displacements = disp_array; + disp_args = disp_array; + ca = count; + } else { + count = l[0]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(l); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(l + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(l + 1 + count); + displacements = OMPI_DISP_ARRAY_CREATE(l + 1 + count); + disp_args = OMPI_DISP_ARRAY_NULL; + cl = 2*count + 1; + } + ompi_datatype_create_struct( count, a_i[1], displacements, d, &datatype ); + ompi_datatype_set_args( datatype, ci, cl, a_i, ca, disp_args, count, d, MPI_COMBINER_STRUCT ); } break; /******************************************************************/ case MPI_COMBINER_SUBARRAY: - ompi_datatype_create_subarray( i[0], &i[1 + 0 * i[0]], &i[1 + 1 * i[0]], - &i[1 + 2 * i[0]], i[1 + 3 * i[0]], - d[0], &datatype ); { - const int* a_i[5] = {&i[0], &i[1 + 0 * i[0]], &i[1 + 1 * i[0]], &i[1 + 2 * i[0]], &i[1 + 3 * i[0]]}; - ompi_datatype_set_args( datatype, 3 * i[0] + 2, a_i, 0, NULL, 1, d, MPI_COMBINER_SUBARRAY); + count = i[0]; // first element in int array + int order; + opal_count_array_t a_i[5]; + if (l == NULL) { + a_i[0] = OMPI_COUNT_ARRAY_CREATE(i); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(i + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(i + 1 + count); + a_i[3] = OMPI_COUNT_ARRAY_CREATE(i + 1 + 2*count); + a_i[4] = OMPI_COUNT_ARRAY_CREATE(i + 1 + 3*count); + order = i[3*count+1]; // last element in int array + ci = 3 * count + 2; + } else { + a_i[0] = OMPI_COUNT_ARRAY_CREATE(i); // ndim + a_i[1] = OMPI_COUNT_ARRAY_CREATE(l); // sizes + a_i[2] = OMPI_COUNT_ARRAY_CREATE(l + count); // subsizes + a_i[3] = OMPI_COUNT_ARRAY_CREATE(l + 2*count); // starts + a_i[4] = OMPI_COUNT_ARRAY_CREATE(i+1); // order + order = i[1]; // second (and last) element in int array + cl = 3 * count; + ci = 2; + } + ompi_datatype_create_subarray( count, a_i[1], a_i[2], a_i[3], order, d[0], &datatype ); + ompi_datatype_set_args( datatype, ci, cl, a_i, 0, OMPI_DISP_ARRAY_NULL, 1, d, MPI_COMBINER_SUBARRAY ); } break; /******************************************************************/ case MPI_COMBINER_DARRAY: - ompi_datatype_create_darray( i[0] /* size */, i[1] /* rank */, i[2] /* ndims */, - &i[3 + 0 * i[2]], &i[3 + 1 * i[2]], - &i[3 + 2 * i[2]], &i[3 + 3 * i[2]], - i[3 + 4 * i[2]], d[0], &datatype ); { - const int* a_i[8] = {&i[0], &i[1], &i[2], &i[3 + 0 * i[2]], &i[3 + 1 * i[2]], &i[3 + 2 * i[2]], - &i[3 + 3 * i[2]], &i[3 + 4 * i[2]]}; - ompi_datatype_set_args( datatype, 4 * i[2] + 4, a_i, 0, NULL, 1, d, MPI_COMBINER_DARRAY); + int size = i[0]; + int rank = i[1]; + int ndims = i[2]; + ompi_count_array_t gsize_array; + const int *distrib_array; + const int *darg_array; + const int *psize_array; + int order; + if (l == NULL) { + gsize_array = OMPI_COUNT_ARRAY_CREATE(i + 3); + distrib_array = &i[3 + 1*ndims]; + darg_array = &i[3 + 2*ndims]; + psize_array = &i[3 + 3*ndims]; + order = i[3 + 4*ndims]; + ci = 4 + 4 * ndims; + } else { + gsize_array = OMPI_COUNT_ARRAY_CREATE(l); + distrib_array = &i[3 + 0*ndims]; + darg_array = &i[3 + 1*ndims]; + psize_array = &i[3 + 2*ndims]; + order = i[3 + 3*ndims]; + ci = 4 + 3 * ndims; + cl = ndims; + } + opal_count_array_t a_i[8] = {OMPI_COUNT_ARRAY_CREATE(&size), + OMPI_COUNT_ARRAY_CREATE(&rank), + OMPI_COUNT_ARRAY_CREATE(&ndims), + gsize_array, + OMPI_COUNT_ARRAY_CREATE(distrib_array), + OMPI_COUNT_ARRAY_CREATE(darg_array), + OMPI_COUNT_ARRAY_CREATE(psize_array), + OMPI_COUNT_ARRAY_CREATE(&order)}; + ompi_datatype_create_darray( size, rank, ndims, gsize_array, distrib_array, darg_array, psize_array, order, d[0], &datatype ); + ompi_datatype_set_args( datatype, ci, cl, a_i, 0, OMPI_DISP_ARRAY_NULL, 1, d, MPI_COMBINER_DARRAY); } break; /******************************************************************/ @@ -774,15 +1024,53 @@ static ompi_datatype_t* __ompi_datatype_create_from_args( int32_t* i, MPI_Aint* break; /******************************************************************/ case MPI_COMBINER_RESIZED: - ompi_datatype_create_resized(d[0], a[0], a[1], &datatype); - ompi_datatype_set_args( datatype, 0, NULL, 2, a, 1, d, MPI_COMBINER_RESIZED ); + if (NULL == l) { + /* classic: lb/extent were stored as MPI_Aint displacements */ + ompi_datatype_create_resized(d[0], a[0], a[1], &datatype); + ompi_datatype_set_args( datatype, 0, 0, NULL, 2, disp_array, 1, d, MPI_COMBINER_RESIZED ); + } else { + /* large count: lb/extent were stored as MPI_Count large counts. + * lb may be negative; the size_t -> ptrdiff_t cast restores the + * signed value via a two's-complement round-trip. */ + ompi_count_array_t a_i[1] = {OMPI_COUNT_ARRAY_CREATE(l)}; + ompi_datatype_create_resized(d[0], (ptrdiff_t) l[0], (ptrdiff_t) l[1], &datatype); + ompi_datatype_set_args( datatype, 0, 2, a_i, 0, OMPI_DISP_ARRAY_NULL, 1, d, MPI_COMBINER_RESIZED ); + } break; /******************************************************************/ case MPI_COMBINER_HINDEXED_BLOCK: - ompi_datatype_create_hindexed_block( i[0], i[1], a, d[0], &datatype ); { - const int* a_i[2] = {&i[0], &i[1]}; - ompi_datatype_set_args( datatype, 2, a_i, i[0], a, 1, d, MPI_COMBINER_HINDEXED_BLOCK ); + size_t bLength = 0; + size_t ca; + opal_disp_array_t displacements; // for create_hindexed_block + opal_disp_array_t disp_args; // for set_args + opal_count_array_t a_i[3]; + if (l == NULL) { + count = i[0]; + bLength = i[1]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(i); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(i + 1); + ci = 2; + displacements = disp_array; + disp_args = disp_array; + ca = count; // displacements stored in disp_array + } else { + count = l[0]; + bLength = l[1]; + a_i[0] = OMPI_COUNT_ARRAY_CREATE(l); + a_i[1] = OMPI_COUNT_ARRAY_CREATE(l + 1); + a_i[2] = OMPI_COUNT_ARRAY_CREATE(l + 2); + /* count + blocklength + count displacements; hardcoding 3 + * here (the count == 1 case) under-allocates the large-count + * array in set_args and corrupts the envelope. */ + cl = 2 + count; + displacements = OMPI_DISP_ARRAY_CREATE(l + 2); // displacements are MPI_Count + disp_args = OMPI_DISP_ARRAY_NULL; + ca = 0; + } + ompi_datatype_create_hindexed_block( count, bLength, displacements, d[0], &datatype ); + + ompi_datatype_set_args( datatype, ci, cl, a_i, ca, disp_args, 1, d, MPI_COMBINER_HINDEXED_BLOCK ); } break; /******************************************************************/ @@ -793,6 +1081,15 @@ static ompi_datatype_t* __ompi_datatype_create_from_args( int32_t* i, MPI_Aint* return datatype; } +/* + * Note: *packed_buffer must be at least word-size (sizeof(ptrdiff_t)) + * aligned. The description interleaves 32-bit ints with word-size values + * and the unpack code realigns the running pointer to word size before each + * word-size access (see OMPI_DATATYPE_ALIGN_PTR); that realignment is + * relative to the buffer base, so the base must itself be word aligned for + * the reads to land on the same offsets the packer wrote. Buffers produced + * by ompi_datatype_get_pack_description() (malloc'd) satisfy this. + */ ompi_datatype_t* ompi_datatype_create_from_packed_description( void** packed_buffer, struct ompi_proc_t* remote_processor ) { @@ -816,7 +1113,7 @@ ompi_datatype_t* ompi_datatype_get_single_predefined_type_from_args( ompi_dataty { ompi_datatype_t *predef = NULL, *current_type, *current_predef; ompi_datatype_args_t* args = (ompi_datatype_args_t*)type->args; - int i; + size_t i; if( ompi_datatype_is_predefined(type) ) return type; diff --git a/ompi/datatype/ompi_datatype_create.c b/ompi/datatype/ompi_datatype_create.c index 76e87a0593f..64f2740270e 100644 --- a/ompi/datatype/ompi_datatype_create.c +++ b/ompi/datatype/ompi_datatype_create.c @@ -13,6 +13,7 @@ * Copyright (c) 2018 Amazon.com, Inc. or its affiliates. All Rights reserved. * Copyright (c) 2025 Triad National Security, LLC. All rights * reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -71,7 +72,7 @@ static void __ompi_datatype_release(ompi_datatype_t * datatype) OBJ_CLASS_INSTANCE(ompi_datatype_t, opal_datatype_t, __ompi_datatype_allocate, __ompi_datatype_release); -ompi_datatype_t * ompi_datatype_create( int32_t expectedSize ) +ompi_datatype_t * ompi_datatype_create( size_t expectedSize ) { int ret; ompi_datatype_t * datatype = (ompi_datatype_t*)OBJ_NEW(ompi_datatype_t); diff --git a/ompi/datatype/ompi_datatype_create_contiguous.c b/ompi/datatype/ompi_datatype_create_contiguous.c index 6a287caa41c..e0a7503713e 100644 --- a/ompi/datatype/ompi_datatype_create_contiguous.c +++ b/ompi/datatype/ompi_datatype_create_contiguous.c @@ -12,6 +12,7 @@ * All rights reserved. * Copyright (c) 2009 Sun Microsystems, Inc. All rights reserved. * Copyright (c) 2009 Oak Ridge National Labs. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -24,7 +25,7 @@ #include "ompi/datatype/ompi_datatype_internal.h" #include "mpi.h" -int32_t ompi_datatype_create_contiguous( int count, const ompi_datatype_t* oldType, +int32_t ompi_datatype_create_contiguous( size_t count, const ompi_datatype_t* oldType, ompi_datatype_t** newType ) { ompi_datatype_t* pdt; diff --git a/ompi/datatype/ompi_datatype_create_darray.c b/ompi/datatype/ompi_datatype_create_darray.c index e0292755c4b..ddc6ad1e1e8 100644 --- a/ompi/datatype/ompi_datatype_create_darray.c +++ b/ompi/datatype/ompi_datatype_create_darray.c @@ -16,6 +16,7 @@ * Copyright (c) 2016 Los Alamos National Security, LLC. All rights * reserved. * Copyright (c) 2017 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -30,18 +31,24 @@ #include "ompi/datatype/ompi_datatype.h" static int -block(const int *gsize_array, int dim, int ndims, int nprocs, +block(ompi_count_array_t gsize_array, int dim, int ndims, int nprocs, int rank, int darg, int order, ptrdiff_t orig_extent, ompi_datatype_t *type_old, ompi_datatype_t **type_new, ptrdiff_t *st_offset) { - int blksize, global_size, mysize, i, j, rc, start_loop, step; + int i, rc, start_loop, step; + /* global_size and the derived block/local sizes can exceed INT_MAX for + * big-count gsizes, so they must be 64 bit (signed: mysize/j can go + * negative before clamping). */ + ptrdiff_t blksize, global_size, mysize, j; ptrdiff_t stride, disps[2]; - global_size = gsize_array[dim]; + global_size = ompi_count_array_get(gsize_array, dim); if (darg == MPI_DISTRIBUTE_DFLT_DARG) - blksize = (global_size + nprocs - 1) / nprocs; + /* ceil(global_size/nprocs) without the "+ nprocs - 1" addition, which + * would overflow ptrdiff_t for a global_size near PTRDIFF_MAX. */ + blksize = global_size / nprocs + (0 != global_size % nprocs); else { blksize = darg; } @@ -58,13 +65,13 @@ block(const int *gsize_array, int dim, int ndims, int nprocs, stride = orig_extent; if (dim == start_loop) { - rc = ompi_datatype_create_contiguous(mysize, type_old, type_new); + rc = ompi_datatype_create_contiguous((size_t) mysize, type_old, type_new); if (OMPI_SUCCESS != rc) return rc; } else { for (i = start_loop ; i != dim ; i += step) { - stride *= gsize_array[i]; + stride *= ompi_count_array_get(gsize_array, i); } - rc = ompi_datatype_create_hvector(mysize, 1, stride, type_old, type_new); + rc = ompi_datatype_create_hvector((size_t) mysize, 1, stride, type_old, type_new); if (OMPI_SUCCESS != rc) return rc; } @@ -76,11 +83,11 @@ block(const int *gsize_array, int dim, int ndims, int nprocs, disps[0] = 0; disps[1] = orig_extent; if (order == MPI_ORDER_FORTRAN) { for(i=0; i<=dim; i++) { - disps[1] *= gsize_array[i]; + disps[1] *= ompi_count_array_get(gsize_array, i); } } else { for(i=ndims-1; i>=dim; i--) { - disps[1] *= gsize_array[i]; + disps[1] *= ompi_count_array_get(gsize_array, i); } } rc = opal_datatype_resize( &(*type_new)->super, disps[0], disps[1] ); @@ -91,12 +98,16 @@ block(const int *gsize_array, int dim, int ndims, int nprocs, static int -cyclic(const int *gsize_array, int dim, int ndims, int nprocs, +cyclic(ompi_count_array_t gsize_array, int dim, int ndims, int nprocs, int rank, int darg, int order, ptrdiff_t orig_extent, ompi_datatype_t* type_old, ompi_datatype_t **type_new, ptrdiff_t *st_offset) { - int blksize, i, blklens[2], st_index, end_index, local_size, rem, count, rc; + int blksize, i, blklens[2], rc; + /* The global index range and local element count can exceed INT_MAX + * for big-count gsizes (blksize stays int -- it is the int-typed + * distribution argument). */ + ptrdiff_t st_index, end_index, local_size, rem, count; ptrdiff_t stride, disps[2]; ompi_datatype_t *type_tmp, *types[2]; @@ -106,32 +117,35 @@ cyclic(const int *gsize_array, int dim, int ndims, int nprocs, blksize = darg; } - st_index = rank * blksize; - end_index = gsize_array[dim] - 1; + /* blksize stays int (it is the int distribution argument), but force the + * index/stride products to 64 bits so they cannot overflow before being + * stored in the (widened) ptrdiff_t locals. */ + st_index = (ptrdiff_t) rank * blksize; + end_index = ompi_count_array_get(gsize_array, dim) - 1; if (end_index < st_index) { local_size = 0; } else { - local_size = ((end_index - st_index + 1)/(nprocs*blksize))*blksize; - rem = (end_index - st_index + 1) % (nprocs*blksize); + local_size = ((end_index - st_index + 1)/((ptrdiff_t) nprocs*blksize))*blksize; + rem = (end_index - st_index + 1) % ((ptrdiff_t) nprocs*blksize); local_size += rem < blksize ? rem : blksize; } count = local_size / blksize; rem = local_size % blksize; - stride = nprocs*blksize*orig_extent; + stride = (ptrdiff_t) nprocs*blksize*orig_extent; if (order == MPI_ORDER_FORTRAN) { for (i=0; idim; i--) { - stride *= gsize_array[i]; + stride *= ompi_count_array_get(gsize_array, i); } } - rc = ompi_datatype_create_hvector(count, blksize, stride, type_old, type_new); + rc = ompi_datatype_create_hvector((size_t) count, blksize, stride, type_old, type_new); if (OMPI_SUCCESS != rc) return rc; if (rem) { @@ -140,9 +154,9 @@ cyclic(const int *gsize_array, int dim, int ndims, int nprocs, types [0] = *type_new; types [1] = type_old; disps [0] = 0; disps [1] = count*stride; - blklens[0] = 1; blklens[1] = rem; + blklens[0] = 1; blklens[1] = (int) rem; /* rem < blksize (int) */ - rc = ompi_datatype_create_struct(2, blklens, disps, types, &type_tmp); + rc = ompi_datatype_create_struct(2, OMPI_COUNT_ARRAY_CREATE(blklens), OMPI_DISP_ARRAY_CREATE(disps), types, &type_tmp); ompi_datatype_destroy(type_new); /* even in error condition, need to destroy type_new, so check for error after destroy. */ @@ -154,17 +168,17 @@ cyclic(const int *gsize_array, int dim, int ndims, int nprocs, disps[0] = 0; disps[1] = orig_extent; if (order == MPI_ORDER_FORTRAN) { for(i=0; i<=dim; i++) { - disps[1] *= gsize_array[i]; + disps[1] *= ompi_count_array_get(gsize_array, i); } } else { for(i=ndims-1; i>=dim; i--) { - disps[1] *= gsize_array[i]; + disps[1] *= ompi_count_array_get(gsize_array, i); } } rc = opal_datatype_resize( &(*type_new)->super, disps[0], disps[1] ); if (OMPI_SUCCESS != rc) return rc; - *st_offset = rank * blksize; + *st_offset = (ptrdiff_t) rank * blksize; /* in terms of no. of elements of type oldtype in this dimension */ if (local_size == 0) *st_offset = 0; @@ -174,10 +188,10 @@ cyclic(const int *gsize_array, int dim, int ndims, int nprocs, int32_t ompi_datatype_create_darray(int size, int rank, int ndims, - int const* gsize_array, - int const* distrib_array, - int const* darg_array, - int const* psize_array, + ompi_count_array_t gsize_array, + const int* distrib_array, + const int* darg_array, + const int* psize_array, int order, const ompi_datatype_t* oldtype, ompi_datatype_t** newtype) @@ -209,7 +223,7 @@ int32_t ompi_datatype_create_darray(int size, coords[i] = tmp_rank / procs; tmp_rank = tmp_rank % procs; /* compute the upper bound of the datatype, including all dimensions */ - displs[1] *= gsize_array[i]; + displs[1] *= ompi_count_array_get(gsize_array, i); } } @@ -275,7 +289,7 @@ int32_t ompi_datatype_create_darray(int size, */ displs[0] = st_offsets[start_loop]; for (i = start_loop + step; i != end_loop; i += step) { - tmp_size *= gsize_array[i - step]; + tmp_size *= ompi_count_array_get(gsize_array, i - step); displs[0] += tmp_size * st_offsets[i]; } displs[0] *= orig_extent; diff --git a/ompi/datatype/ompi_datatype_create_indexed.c b/ompi/datatype/ompi_datatype_create_indexed.c index 2684d9d7df0..29f8d8875c8 100644 --- a/ompi/datatype/ompi_datatype_create_indexed.c +++ b/ompi/datatype/ompi_datatype_create_indexed.c @@ -16,6 +16,7 @@ * Copyright (c) 2015-2017 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2019 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -31,38 +32,38 @@ /* We try to merge together data that are contiguous */ -int32_t ompi_datatype_create_indexed( int count, const int* pBlockLength, const int* pDisp, +int32_t ompi_datatype_create_indexed( size_t count, const ompi_count_array_t pBlockLength, const ompi_disp_array_t pDisp, const ompi_datatype_t* oldType, ompi_datatype_t** newType ) { ptrdiff_t extent, disp, endat; ompi_datatype_t* pdt; size_t dLength; - int i; + size_t i; /* ignore all cases that lead to an empty type */ ompi_datatype_type_size(oldType, &dLength); - for( i = 0; (i < count) && (0 == pBlockLength[i]); i++ ); /* find first non zero */ + for( i = 0; (i < count) && (0 == ompi_count_array_get(pBlockLength, i)); i++ ); /* find first non zero */ if( (i == count) || (0 == dLength) ) { return ompi_datatype_duplicate( &ompi_mpi_datatype_null.dt, newType); } - disp = pDisp[i]; - dLength = pBlockLength[i]; + disp = ompi_disp_array_get(pDisp, i); + dLength = ompi_count_array_get(pBlockLength, i); endat = disp + dLength; ompi_datatype_type_extent( oldType, &extent ); pdt = ompi_datatype_create( (count - i) * (2 + oldType->super.desc.used) ); for( i += 1; i < count; i++ ) { - if( 0 == pBlockLength[i] ) /* ignore empty length */ + if( 0 == ompi_count_array_get(pBlockLength, i) ) /* ignore empty length */ continue; - if( endat == pDisp[i] ) { /* contiguous with the previsious */ - dLength += pBlockLength[i]; - endat += pBlockLength[i]; + if( endat == ompi_disp_array_get(pDisp, i) ) { /* contiguous with the previsious */ + dLength += ompi_count_array_get(pBlockLength, i); + endat += ompi_count_array_get(pBlockLength, i); } else { ompi_datatype_add( pdt, oldType, dLength, disp * extent, extent ); - disp = pDisp[i]; - dLength = pBlockLength[i]; - endat = disp + pBlockLength[i]; + disp = ompi_disp_array_get(pDisp, i); + dLength = ompi_count_array_get(pBlockLength, i); + endat = disp + dLength; } } ompi_datatype_add( pdt, oldType, dLength, disp * extent, extent ); @@ -72,38 +73,38 @@ int32_t ompi_datatype_create_indexed( int count, const int* pBlockLength, const } -int32_t ompi_datatype_create_hindexed( int count, const int* pBlockLength, const ptrdiff_t* pDisp, +int32_t ompi_datatype_create_hindexed( size_t count, const ompi_count_array_t pBlockLength, const ompi_disp_array_t pDisp, const ompi_datatype_t* oldType, ompi_datatype_t** newType ) { ptrdiff_t extent, disp, endat; ompi_datatype_t* pdt; size_t dLength; - int i; + size_t i; /* ignore all cases that lead to an empty type */ ompi_datatype_type_size(oldType, &dLength); - for( i = 0; (i < count) && (0 == pBlockLength[i]); i++ ); /* find first non zero */ + for( i = 0; (i < count) && (0 == ompi_count_array_get(pBlockLength, i)); i++ ); /* find first non zero */ if( (i == count) || (0 == dLength) ) { return ompi_datatype_duplicate( &ompi_mpi_datatype_null.dt, newType); } ompi_datatype_type_extent( oldType, &extent ); - disp = pDisp[i]; - dLength = pBlockLength[i]; + disp = ompi_disp_array_get(pDisp, i); + dLength = ompi_count_array_get(pBlockLength, i); endat = disp + dLength * extent; pdt = ompi_datatype_create( (count - i) * (2 + oldType->super.desc.used) ); for( i += 1; i < count; i++ ) { - if( 0 == pBlockLength[i] ) /* ignore empty length */ + if( 0 == ompi_count_array_get(pBlockLength, i) ) /* ignore empty length */ continue; - if( endat == pDisp[i] ) { /* contiguous with the previsious */ - dLength += pBlockLength[i]; - endat += pBlockLength[i] * extent; + if( endat == ompi_disp_array_get(pDisp, i) ) { /* contiguous with the previsious */ + dLength += ompi_count_array_get(pBlockLength, i); + endat += ompi_count_array_get(pBlockLength, i) * extent; } else { ompi_datatype_add( pdt, oldType, dLength, disp, extent ); - disp = pDisp[i]; - dLength = pBlockLength[i]; - endat = disp + pBlockLength[i] * extent; + disp = ompi_disp_array_get(pDisp, i); + dLength = ompi_count_array_get(pBlockLength, i); + endat = disp + dLength * extent; } } ompi_datatype_add( pdt, oldType, dLength, disp, extent ); @@ -113,30 +114,30 @@ int32_t ompi_datatype_create_hindexed( int count, const int* pBlockLength, const } -int32_t ompi_datatype_create_indexed_block( int count, int bLength, const int* pDisp, +int32_t ompi_datatype_create_indexed_block( size_t count, size_t bLength, const ompi_disp_array_t pDisp, const ompi_datatype_t* oldType, ompi_datatype_t** newType ) { ptrdiff_t extent, disp, endat; ompi_datatype_t* pdt; size_t dLength; - int i; + size_t i; if( (count == 0) || (bLength == 0) ) { return ompi_datatype_duplicate(&ompi_mpi_datatype_null.dt, newType); } ompi_datatype_type_extent( oldType, &extent ); pdt = ompi_datatype_create( count * (2 + oldType->super.desc.used) ); - disp = pDisp[0]; + disp = ompi_disp_array_get(pDisp, 0); dLength = bLength; endat = disp + dLength; for( i = 1; i < count; i++ ) { - if( endat == pDisp[i] ) { + if( endat == ompi_disp_array_get(pDisp, i) ) { /* contiguous with the previsious */ dLength += bLength; endat += bLength; } else { ompi_datatype_add( pdt, oldType, dLength, disp * extent, extent ); - disp = pDisp[i]; + disp = ompi_disp_array_get(pDisp, i); dLength = bLength; endat = disp + bLength; } @@ -147,30 +148,30 @@ int32_t ompi_datatype_create_indexed_block( int count, int bLength, const int* p return OMPI_SUCCESS; } -int32_t ompi_datatype_create_hindexed_block( int count, int bLength, const ptrdiff_t* pDisp, +int32_t ompi_datatype_create_hindexed_block( size_t count, size_t bLength, const ompi_disp_array_t pDisp, const ompi_datatype_t* oldType, ompi_datatype_t** newType ) { ptrdiff_t extent, disp, endat; ompi_datatype_t* pdt; size_t dLength; - int i; + size_t i; if( (count == 0) || (bLength == 0) ) { return ompi_datatype_duplicate(&ompi_mpi_datatype_null.dt, newType); } ompi_datatype_type_extent( oldType, &extent ); pdt = ompi_datatype_create( count * (2 + oldType->super.desc.used) ); - disp = pDisp[0]; + disp = ompi_disp_array_get(pDisp, 0); dLength = bLength; endat = disp + dLength * extent; for( i = 1; i < count; i++ ) { - if( endat == pDisp[i] ) { + if( endat == ompi_disp_array_get(pDisp, i) ) { /* contiguous with the previsious */ dLength += bLength; endat += bLength * extent; } else { ompi_datatype_add( pdt, oldType, dLength, disp, extent ); - disp = pDisp[i]; + disp = ompi_disp_array_get(pDisp, i); dLength = bLength; endat = disp + bLength * extent; } diff --git a/ompi/datatype/ompi_datatype_create_struct.c b/ompi/datatype/ompi_datatype_create_struct.c index 72d3251b936..bf4a05017c8 100644 --- a/ompi/datatype/ompi_datatype_create_struct.c +++ b/ompi/datatype/ompi_datatype_create_struct.c @@ -15,6 +15,7 @@ * Copyright (c) 2010 Cisco Systems, Inc. All rights reserved. * Copyright (c) 2017 Research Organization for Information Science * and Technology (RIST). All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -28,16 +29,16 @@ #include "ompi/datatype/ompi_datatype.h" -int32_t ompi_datatype_create_struct( int count, const int* pBlockLength, const ptrdiff_t* pDisp, +int32_t ompi_datatype_create_struct( size_t count, const ompi_count_array_t pBlockLength, const ompi_disp_array_t pDisp, ompi_datatype_t* const * pTypes, ompi_datatype_t** newType ) { ptrdiff_t disp = 0, endto, lastExtent, lastDisp; ompi_datatype_t *pdt, *lastType; - int i, start_from; + size_t i, start_from; size_t lastBlock; /* Find first non-zero length element */ - for( i = 0; (i < count) && (0 == pBlockLength[i]); i++ ); + for( i = 0; (i < count) && (0 == ompi_count_array_get(pBlockLength, i)); i++ ); if( i == count ) { /* either nothing or nothing relevant */ return ompi_datatype_duplicate( &ompi_mpi_datatype_null.dt, newType); } @@ -46,22 +47,22 @@ int32_t ompi_datatype_create_struct( int count, const int* pBlockLength, const p */ start_from = i; lastType = (ompi_datatype_t*)pTypes[start_from]; - lastBlock = pBlockLength[start_from]; + lastBlock = ompi_count_array_get(pBlockLength, start_from); lastExtent = lastType->super.ub - lastType->super.lb; - lastDisp = pDisp[start_from]; - endto = pDisp[start_from] + lastExtent * lastBlock; + lastDisp = ompi_disp_array_get(pDisp, start_from); + endto = lastDisp + lastExtent * lastBlock; for( i = (start_from + 1); i < count; i++ ) { - if( (pTypes[i] == lastType) && (pDisp[i] == endto) ) { - lastBlock += pBlockLength[i]; + if( (pTypes[i] == lastType) && (ompi_disp_array_get(pDisp, i) == endto) ) { + lastBlock += ompi_count_array_get(pBlockLength, i); endto = lastDisp + lastBlock * lastExtent; } else { disp += lastType->super.desc.used; if( lastBlock > 1 ) disp += 2; lastType = (ompi_datatype_t*)pTypes[i]; lastExtent = lastType->super.ub - lastType->super.lb; - lastBlock = pBlockLength[i]; - lastDisp = pDisp[i]; + lastBlock = ompi_count_array_get(pBlockLength, i); + lastDisp = ompi_disp_array_get(pDisp, i); endto = lastDisp + lastExtent * lastBlock; } } @@ -69,24 +70,24 @@ int32_t ompi_datatype_create_struct( int count, const int* pBlockLength, const p if( lastBlock != 1 ) disp += 2; lastType = (ompi_datatype_t*)pTypes[start_from]; - lastBlock = pBlockLength[start_from]; + lastBlock = ompi_count_array_get(pBlockLength, start_from); lastExtent = lastType->super.ub - lastType->super.lb; - lastDisp = pDisp[start_from]; - endto = pDisp[start_from] + lastExtent * lastBlock; + lastDisp = ompi_disp_array_get(pDisp, start_from); + endto = lastDisp + lastExtent * lastBlock; pdt = ompi_datatype_create( (int32_t)disp ); /* Do again the same loop but now add the elements */ for( i = (start_from + 1); i < count; i++ ) { - if( (pTypes[i] == lastType) && (pDisp[i] == endto) ) { - lastBlock += pBlockLength[i]; + if( (pTypes[i] == lastType) && (ompi_disp_array_get(pDisp, i) == endto) ) { + lastBlock += ompi_count_array_get(pBlockLength, i); endto = lastDisp + lastBlock * lastExtent; } else { ompi_datatype_add( pdt, lastType, lastBlock, lastDisp, lastExtent ); lastType = (ompi_datatype_t*)pTypes[i]; lastExtent = lastType->super.ub - lastType->super.lb; - lastBlock = pBlockLength[i]; - lastDisp = pDisp[i]; + lastBlock = ompi_count_array_get(pBlockLength, i); + lastDisp = ompi_disp_array_get(pDisp, i); endto = lastDisp + lastExtent * lastBlock; } } diff --git a/ompi/datatype/ompi_datatype_create_subarray.c b/ompi/datatype/ompi_datatype_create_subarray.c index fcf44407725..d82251e708c 100644 --- a/ompi/datatype/ompi_datatype_create_subarray.c +++ b/ompi/datatype/ompi_datatype_create_subarray.c @@ -15,6 +15,7 @@ * Copyright (c) 2010 Cisco Systems, Inc. All rights reserved. * Copyright (c) 2014-2015 Research Organization for Information Science * and Technology (RIST). All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -29,9 +30,9 @@ #include "ompi/datatype/ompi_datatype.h" int32_t ompi_datatype_create_subarray(int ndims, - int const* size_array, - int const* subsize_array, - int const* start_array, + const ompi_count_array_t size_array, + const ompi_count_array_t subsize_array, + const ompi_count_array_t start_array, int order, const ompi_datatype_t* oldtype, ompi_datatype_t** newtype) @@ -54,9 +55,9 @@ int32_t ompi_datatype_create_subarray(int ndims, ompi_datatype_duplicate(&ompi_mpi_datatype_null.dt, newtype); return MPI_SUCCESS; } - ompi_datatype_create_contiguous( subsize_array[0], oldtype, &last_type ); - size = size_array[0]; - displ = start_array[0]; + ompi_datatype_create_contiguous( ompi_count_array_get(subsize_array, 0), oldtype, &last_type ); + size = ompi_count_array_get(size_array, 0); + displ = ompi_count_array_get(start_array, 0); goto replace_subarray_type; } @@ -74,19 +75,22 @@ int32_t ompi_datatype_create_subarray(int ndims, * first dimension data outside the loop, such that we dont have to create * a duplicate of the oldtype just to be able to free it. */ - ompi_datatype_create_vector( subsize_array[i+step], subsize_array[i], size_array[i], + ompi_datatype_create_vector( ompi_count_array_get(subsize_array, i+step), + ompi_count_array_get(subsize_array, i), + ompi_count_array_get(size_array, i), oldtype, newtype ); last_type = *newtype; - size = (MPI_Aint)size_array[i] * (MPI_Aint)size_array[i+step]; - displ = (MPI_Aint)start_array[i] + (MPI_Aint)start_array[i+step] * (MPI_Aint)size_array[i]; + size = (MPI_Aint)ompi_count_array_get(size_array, i) * (MPI_Aint)ompi_count_array_get(size_array, i+step); + displ = (MPI_Aint)ompi_count_array_get(start_array, i) + + (MPI_Aint)ompi_count_array_get(start_array, i+step) * (MPI_Aint)ompi_count_array_get(size_array, i); for( i += 2 * step; i != end_loop; i += step ) { - ompi_datatype_create_hvector( subsize_array[i], 1, size * extent, + ompi_datatype_create_hvector( ompi_count_array_get(subsize_array, i), 1, size * extent, last_type, newtype ); ompi_datatype_destroy( &last_type ); - displ += size * start_array[i]; - size *= size_array[i]; + displ += size * ompi_count_array_get(start_array, i); + size *= ompi_count_array_get(size_array, i); last_type = *newtype; } diff --git a/ompi/datatype/ompi_datatype_create_vector.c b/ompi/datatype/ompi_datatype_create_vector.c index c4829a4b54c..134bcfc50e5 100644 --- a/ompi/datatype/ompi_datatype_create_vector.c +++ b/ompi/datatype/ompi_datatype_create_vector.c @@ -15,6 +15,7 @@ * Copyright (c) 2010 Cisco Systems, Inc. All rights reserved. * Copyright (c) 2017 Research Organization for Information Science * and Technology (RIST). All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -28,7 +29,7 @@ #include "ompi/datatype/ompi_datatype.h" -int32_t ompi_datatype_create_vector( int count, int bLength, int stride, +int32_t ompi_datatype_create_vector( size_t count, size_t bLength, ptrdiff_t stride, const ompi_datatype_t* oldType, ompi_datatype_t** newType ) { ompi_datatype_t *pTempData, *pData; @@ -39,7 +40,7 @@ int32_t ompi_datatype_create_vector( int count, int bLength, int stride, } pData = ompi_datatype_create( oldType->super.desc.used + 2 ); - if( (bLength == stride) || (1 >= count) ) { /* the elements are contiguous */ + if( (bLength == (size_t)stride) || (1 >= count) ) { /* the elements are contiguous */ ompi_datatype_add( pData, oldType, (size_t)count * bLength, 0, extent ); } else { if( 1 == bLength ) { @@ -57,7 +58,7 @@ int32_t ompi_datatype_create_vector( int count, int bLength, int stride, } -int32_t ompi_datatype_create_hvector( int count, int bLength, ptrdiff_t stride, +int32_t ompi_datatype_create_hvector( size_t count, size_t bLength, ptrdiff_t stride, const ompi_datatype_t* oldType, ompi_datatype_t** newType ) { ompi_datatype_t *pTempData, *pData; @@ -68,7 +69,7 @@ int32_t ompi_datatype_create_hvector( int count, int bLength, ptrdiff_t stride, } pTempData = ompi_datatype_create( oldType->super.desc.used + 2 ); - if( ((extent * bLength) == stride) || (1 >= count) ) { /* contiguous */ + if( ((extent * bLength) == (size_t)stride) || (1 >= count) ) { /* contiguous */ pData = pTempData; ompi_datatype_add( pData, oldType, count * bLength, 0, extent ); } else { diff --git a/ompi/datatype/ompi_datatype_match_size.c b/ompi/datatype/ompi_datatype_match_size.c index 1e036c7003b..6aefb33f6e5 100644 --- a/ompi/datatype/ompi_datatype_match_size.c +++ b/ompi/datatype/ompi_datatype_match_size.c @@ -12,6 +12,8 @@ * All rights reserved. * Copyright (c) 2009 Sun Microsystems, Inc. All rights reserved. * Copyright (c) 2009 Oak Ridge National Labs. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -26,7 +28,7 @@ extern int32_t ompi_datatype_number_of_predefined_data; -const ompi_datatype_t* ompi_datatype_match_size( int size, uint16_t datakind, uint16_t datalang ) +const ompi_datatype_t* ompi_datatype_match_size( size_t size, uint16_t datakind, uint16_t datalang ) { int32_t i; const ompi_datatype_t* datatype; @@ -41,11 +43,21 @@ const ompi_datatype_t* ompi_datatype_match_size( int size, uint16_t datakind, ui datatype = (ompi_datatype_t*)opal_pointer_array_get_item(&ompi_datatype_f_to_c_table, i); + /* Only basic scalar predefined types are valid matches for a + * (typeclass, size) request. Requiring OPAL_DATATYPE_FLAG_BASIC + * skips two kinds of entries that would otherwise produce bogus + * matches: types that are unavailable in this build (e.g. the + * Fortran types in a --disable-mpi-fortran build, which have size 0 + * and would spuriously match a size-0 request), and composite + * predefined types (e.g. MPI_2REAL, a pair of REALs whose 8-byte + * size would otherwise be returned for a request of REAL/8). */ + if( (datatype->super.flags & OPAL_DATATYPE_FLAG_BASIC) != OPAL_DATATYPE_FLAG_BASIC ) + continue; if( (datatype->super.flags & OMPI_DATATYPE_FLAG_DATA_LANGUAGE) != datalang ) continue; if( (datatype->super.flags & OMPI_DATATYPE_FLAG_DATA_TYPE) != datakind ) continue; - if( (size_t)size == datatype->super.size ) { + if( size == datatype->super.size ) { return datatype; } } diff --git a/ompi/datatype/ompi_datatype_module.c b/ompi/datatype/ompi_datatype_module.c index 2a11e6ef090..3cf875069a5 100644 --- a/ompi/datatype/ompi_datatype_module.c +++ b/ompi/datatype/ompi_datatype_module.c @@ -22,6 +22,7 @@ * reserved. * Copyright (c) 2025 Jeffrey M. Squyres. All rights reserved. * Copyright (c) 2026 NVIDIA Corporation. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -441,7 +442,9 @@ opal_pointer_array_t ompi_datatype_f_to_c_table = {{0}}; displ[1] = (ptrdiff_t)(&(s[0].v2)); \ displ[1] -= base; \ \ - ompi_datatype_create_struct( 2, bLength, displ, types, &ptype ); \ + ompi_datatype_create_struct( 2, OMPI_COUNT_ARRAY_CREATE(bLength), \ + OMPI_DISP_ARRAY_CREATE(displ), types, \ + &ptype ); \ displ[0] = (ptrdiff_t)(&(s[1])); \ displ[0] -= base; \ if( displ[0] != (displ[1] + (ptrdiff_t)sizeof(type2)) ) \ diff --git a/ompi/datatype/ompi_datatype_sndrcv.c b/ompi/datatype/ompi_datatype_sndrcv.c index 967c7509271..c8877a9cf33 100644 --- a/ompi/datatype/ompi_datatype_sndrcv.c +++ b/ompi/datatype/ompi_datatype_sndrcv.c @@ -13,6 +13,7 @@ * Copyright (c) 2009 Oak Ridge National Labs. All rights reserved. * Copyright (c) 2014-2015 Research Organization for Information Science * and Technology (RIST). All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -42,8 +43,8 @@ * - communicator * Returns: - MPI_SUCCESS or error code */ -int32_t ompi_datatype_sndrcv( const void *sbuf, int32_t scount, const ompi_datatype_t* sdtype, - void *rbuf, int32_t rcount, const ompi_datatype_t* rdtype) +int32_t ompi_datatype_sndrcv( const void *sbuf, size_t scount, const ompi_datatype_t* sdtype, + void *rbuf, size_t rcount, const ompi_datatype_t* rdtype) { opal_convertor_t send_convertor, recv_convertor; struct iovec iov; @@ -73,11 +74,11 @@ int32_t ompi_datatype_sndrcv( const void *sbuf, int32_t scount, const ompi_datat iov_count = 1; iov.iov_base = (IOVBASE_TYPE*)rbuf; iov.iov_len = scount * sdtype->super.size; - if( (int32_t)iov.iov_len > rcount ) iov.iov_len = rcount; + if( iov.iov_len > rcount ) iov.iov_len = rcount; opal_convertor_pack( &send_convertor, &iov, &iov_count, &max_data ); OBJ_DESTRUCT( &send_convertor ); - return ((max_data < (size_t)rcount) ? MPI_ERR_TRUNCATE : MPI_SUCCESS); + return ((max_data < rcount) ? MPI_ERR_TRUNCATE : MPI_SUCCESS); } /* If send packed. */ @@ -90,11 +91,11 @@ int32_t ompi_datatype_sndrcv( const void *sbuf, int32_t scount, const ompi_datat iov_count = 1; iov.iov_base = (IOVBASE_TYPE*)sbuf; iov.iov_len = rcount * rdtype->super.size; - if( (int32_t)iov.iov_len > scount ) iov.iov_len = scount; + if( iov.iov_len > scount ) iov.iov_len = scount; opal_convertor_unpack( &recv_convertor, &iov, &iov_count, &max_data ); OBJ_DESTRUCT( &recv_convertor ); - return (((size_t)scount > max_data) ? MPI_ERR_TRUNCATE : MPI_SUCCESS); + return ((scount > max_data) ? MPI_ERR_TRUNCATE : MPI_SUCCESS); } iov.iov_len = length = 64 * 1024; diff --git a/ompi/errhandler/errcode.c b/ompi/errhandler/errcode.c index 8c6a13cdb82..d04b6f9069c 100644 --- a/ompi/errhandler/errcode.c +++ b/ompi/errhandler/errcode.c @@ -129,6 +129,9 @@ static ompi_mpi_errcode_t ompi_err_revoked; static ompi_mpi_errcode_t ompi_err_session; static ompi_mpi_errcode_t ompi_err_value_too_large; static ompi_mpi_errcode_t ompi_err_errhandler; +static ompi_mpi_errcode_t ompi_t_err_not_accessible; +static ompi_mpi_errcode_t ompi_t_err_not_supported; +static ompi_mpi_errcode_t ompi_err_rma_notification; static void ompi_mpi_errcode_construct(ompi_mpi_errcode_t* errcode); static void ompi_mpi_errcode_destruct(ompi_mpi_errcode_t* errcode); @@ -248,6 +251,9 @@ int ompi_mpi_errcode_init (void) CONSTRUCT_ERRCODE( ompi_err_session, MPI_ERR_SESSION, "MPI_ERR_SESSION: Invalid session handle" ); CONSTRUCT_ERRCODE( ompi_err_value_too_large, MPI_ERR_VALUE_TOO_LARGE, "MPI_ERR_VALUE_TOO_LARGE: Value is too large to store" ); CONSTRUCT_ERRCODE( ompi_err_errhandler, MPI_ERR_ERRHANDLER, "MPI_ERR_ERRHANDLER: Invalid error handler handle" ); + CONSTRUCT_ERRCODE( ompi_t_err_not_accessible, MPI_T_ERR_NOT_ACCESSIBLE, "MPI_T_ERR_NOT_ACCESSIBLE: Requested functionality is not accessible" ); + CONSTRUCT_ERRCODE( ompi_t_err_not_supported, MPI_T_ERR_NOT_SUPPORTED, "MPI_T_ERR_NOT_SUPPORTED: Requested functionality not supported" ); + CONSTRUCT_ERRCODE( ompi_err_rma_notification, MPI_ERR_RMA_NOTIFICATION, "MPI_ERR_RMA_NOTIFICATION: Invalid notification index passed to MPI call" ); /* Per MPI-3 p353:27-32, MPI_LASTUSEDCODE must be >= MPI_ERR_LASTCODE. So just start it as == MPI_ERR_LASTCODE. */ @@ -367,6 +373,9 @@ int ompi_mpi_errcode_finalize (void) OBJ_DESTRUCT(&ompi_err_session); OBJ_DESTRUCT(&ompi_err_value_too_large); OBJ_DESTRUCT(&ompi_err_errhandler); + OBJ_DESTRUCT(&ompi_t_err_not_accessible); + OBJ_DESTRUCT(&ompi_t_err_not_supported); + OBJ_DESTRUCT(&ompi_err_rma_notification); OBJ_DESTRUCT(&ompi_mpi_errcodes); ompi_mpi_errcode_lastpredefined = 0; opal_mutex_unlock(&errcode_lock); diff --git a/ompi/errhandler/errhandler.c b/ompi/errhandler/errhandler.c index 17fd48dbe9b..4130c9d7d5b 100644 --- a/ompi/errhandler/errhandler.c +++ b/ompi/errhandler/errhandler.c @@ -202,6 +202,7 @@ int ompi_errhandler_init(void) ompi_mpi_errors_abort.eh.eh_comm_fn = ompi_mpi_errors_abort_comm_handler; ompi_mpi_errors_abort.eh.eh_file_fn = ompi_mpi_errors_abort_file_handler; ompi_mpi_errors_abort.eh.eh_win_fn = ompi_mpi_errors_abort_win_handler ; + ompi_mpi_errors_abort.eh.eh_instance_fn = ompi_mpi_errors_abort_instance_handler; ompi_mpi_errors_abort.eh.eh_fort_fn = NULL; opal_string_copy(ompi_mpi_errors_abort.eh.eh_name, "MPI_ERRORS_ABORT", diff --git a/ompi/errhandler/errhandler_invoke.c b/ompi/errhandler/errhandler_invoke.c index 7cb4e343d50..e4ffd0b96ce 100644 --- a/ompi/errhandler/errhandler_invoke.c +++ b/ompi/errhandler/errhandler_invoke.c @@ -85,13 +85,17 @@ int ompi_errhandler_invoke(ompi_errhandler_t *errhandler, void *mpi_object, comm = (ompi_communicator_t *) mpi_object; switch (errhandler->eh_lang) { case OMPI_ERRHANDLER_LANG_C: - errhandler->eh_comm_fn(&comm, &err_code, message, NULL); + if (NULL != errhandler->eh_comm_fn) { + errhandler->eh_comm_fn(&comm, &err_code, message, NULL); + } break; case OMPI_ERRHANDLER_LANG_FORTRAN: - fortran_handle = OMPI_INT_2_FINT(comm->c_f_to_c_index); - errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code); - err_code = OMPI_FINT_2_INT(fortran_err_code); + if (NULL != errhandler->eh_fort_fn) { + fortran_handle = OMPI_INT_2_FINT(comm->c_f_to_c_index); + errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code); + err_code = OMPI_FINT_2_INT(fortran_err_code); + } break; } break; @@ -100,13 +104,17 @@ int ompi_errhandler_invoke(ompi_errhandler_t *errhandler, void *mpi_object, win = (ompi_win_t *) mpi_object; switch (errhandler->eh_lang) { case OMPI_ERRHANDLER_LANG_C: - errhandler->eh_win_fn(&win, &err_code, message, NULL); + if (NULL != errhandler->eh_win_fn) { + errhandler->eh_win_fn(&win, &err_code, message, NULL); + } break; case OMPI_ERRHANDLER_LANG_FORTRAN: - fortran_handle = OMPI_INT_2_FINT(win->w_f_to_c_index); - errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code); - err_code = OMPI_FINT_2_INT(fortran_err_code); + if (NULL != errhandler->eh_fort_fn) { + fortran_handle = OMPI_INT_2_FINT(win->w_f_to_c_index); + errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code); + err_code = OMPI_FINT_2_INT(fortran_err_code); + } break; } break; @@ -115,13 +123,17 @@ int ompi_errhandler_invoke(ompi_errhandler_t *errhandler, void *mpi_object, file = (ompi_file_t *) mpi_object; switch (errhandler->eh_lang) { case OMPI_ERRHANDLER_LANG_C: - errhandler->eh_file_fn(&file, &err_code, message, NULL); + if (NULL != errhandler->eh_file_fn) { + errhandler->eh_file_fn(&file, &err_code, message, NULL); + } break; case OMPI_ERRHANDLER_LANG_FORTRAN: - fortran_handle = OMPI_INT_2_FINT(file->f_f_to_c_index); - errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code); - err_code = OMPI_FINT_2_INT(fortran_err_code); + if (NULL != errhandler->eh_fort_fn) { + fortran_handle = OMPI_INT_2_FINT(file->f_f_to_c_index); + errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code); + err_code = OMPI_FINT_2_INT(fortran_err_code); + } break; } break; @@ -130,13 +142,17 @@ int ompi_errhandler_invoke(ompi_errhandler_t *errhandler, void *mpi_object, instance = (ompi_instance_t *) mpi_object; switch (errhandler->eh_lang) { case OMPI_ERRHANDLER_LANG_C: - errhandler->eh_instance_fn(&instance, &err_code, message, NULL); + if (NULL != errhandler->eh_instance_fn) { + errhandler->eh_instance_fn(&instance, &err_code, message, NULL); + } break; case OMPI_ERRHANDLER_LANG_FORTRAN: - fortran_handle = OMPI_INT_2_FINT(instance->i_f_to_c_index); - errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code); - err_code = OMPI_FINT_2_INT(fortran_err_code); + if (NULL != errhandler->eh_fort_fn) { + fortran_handle = OMPI_INT_2_FINT(instance->i_f_to_c_index); + errhandler->eh_fort_fn(&fortran_handle, &fortran_err_code); + err_code = OMPI_FINT_2_INT(fortran_err_code); + } break; } break; @@ -227,4 +243,4 @@ int ompi_errhandler_request_invoke(int count, ec, message); break; } -} +} \ No newline at end of file diff --git a/ompi/errhandler/errhandler_predefined.c b/ompi/errhandler/errhandler_predefined.c index 759025d4bd8..e32a358280c 100644 --- a/ompi/errhandler/errhandler_predefined.c +++ b/ompi/errhandler/errhandler_predefined.c @@ -48,6 +48,7 @@ #include "opal/util/printf.h" #include "opal/util/output.h" #include "ompi/runtime/mpiruntime.h" +#include "ompi/proc/proc.h" /* * Local functions @@ -185,6 +186,54 @@ void ompi_mpi_errors_abort_win_handler(struct ompi_win_t **win, va_end(arglist); } +void ompi_mpi_errors_abort_instance_handler(struct ompi_instance_t **instance, + int *error_code, ...) +{ + char *name; + va_list arglist; + int err = MPI_ERR_UNKNOWN; + opal_proc_t *local_proc; + pmix_proc_t my_pmix_proc = PMIX_PROC_STATIC_INIT; + + va_start(arglist, error_code); + + if (NULL != instance && NULL != *instance) { + name = (*instance)->i_name; + } else { + name = NULL; + } + + if (NULL != error_code) { + err = *error_code; + } + + /* We only want aggregation while the rte is initialized */ + if (ompi_rte_initialized) { + backend_abort_aggregate(false, "session", NULL, name, error_code, arglist); + } else { + backend_abort_no_aggregate(false, "session", NULL, name, error_code, arglist); + } + + va_end(arglist); + + /* + * MPI-5.0 Sec 9.3 (pp.447-448): ERRORS_ABORT on a session aborts ONLY the + * local MPI process. We signal the PMIx runtime that the local process + * is to be aborted by explicitly specifying the pmix_proc_t of the local + * process in the call to PMIx_Abort. Note that when using the PRRTe + * PMIx server one must use the mpirun --enable-recovery option + * to prevent the server from aborting the other processes in the job. + */ + + local_proc = opal_proc_local_get(); + OPAL_PMIX_CONVERT_NAME(&my_pmix_proc, &local_proc->proc_name); + PMIx_Abort(err, NULL, &my_pmix_proc, 1); + + /* Now Exit */ + _exit(err); + +} + void ompi_mpi_errors_are_fatal_instance_handler (struct ompi_instance_t **instance, int *error_code, ...) { diff --git a/ompi/errhandler/errhandler_predefined.h b/ompi/errhandler/errhandler_predefined.h index c663d962220..4c029e6710a 100644 --- a/ompi/errhandler/errhandler_predefined.h +++ b/ompi/errhandler/errhandler_predefined.h @@ -50,6 +50,8 @@ OMPI_DECLSPEC void ompi_mpi_errors_abort_file_handler(struct ompi_file_t **file, int *error_code, ...); OMPI_DECLSPEC void ompi_mpi_errors_abort_win_handler(struct ompi_win_t **win, int *error_code, ...); +OMPI_DECLSPEC void ompi_mpi_errors_abort_instance_handler(struct ompi_instance_t **instance, + int *error_code, ...); /** * Handler function for MPI_ERRORS_RETURN diff --git a/ompi/include/mpi.h.in b/ompi/include/mpi.h.in index e06865b182f..59b80615828 100644 --- a/ompi/include/mpi.h.in +++ b/ompi/include/mpi.h.in @@ -30,6 +30,7 @@ * Copyright (c) 2025 Advanced Micro Devices, Inc. All rights reserved * Copyright (c) 2025 Jeffrey M. Squyres. All rights reserved. * Copyright (c) 2025 UT-Battelle, LLC. All rights reserved. + * Copyright (c) 2026 NVIDIA Corporation. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -657,7 +658,7 @@ typedef MPI_Win_errhandler_function MPI_Win_errhandler_fn enum { /* MPI-1 */ MPI_TAG_UB, - MPI_HOST __mpi_interface_deprecated_in_mpi41__("MPI_HOST is deprecated since MPI 4.1"), + MPI_HOST __mpi_interface_deprecated_in_mpi41__("MPI_HOST is deprecated since MPI 4.1"), MPI_IO, MPI_WTIME_IS_GLOBAL, @@ -673,6 +674,12 @@ enum { /* MPI-4 */ MPI_FT, /* used by OPAL_ENABLE_FT_MPI */ + + /* MPI-5.1 used in notified RMA */ + MPI_WIN_NOTIFICATION_NUM_SB, + MPI_WIN_NOTIFICATION_NUM_UB, + MPI_WIN_NOTIFICATION_VALUE_UB, + MPI_ATTR_PREDEFINED_KEY_MAX, }; @@ -764,6 +771,9 @@ enum { #define MPI_ERR_SESSION 78 #define MPI_ERR_VALUE_TOO_LARGE 79 #define MPI_ERR_ERRHANDLER 80 +#define MPI_T_ERR_NOT_ACCESSIBLE 81 +#define MPI_T_ERR_NOT_SUPPORTED 82 +#define MPI_ERR_RMA_NOTIFICATION 83 /* Per MPI-3 p349 47, MPI_ERR_LASTCODE must be >= the last predefined MPI_ERR_ code. Set the last code to allow some room for adding @@ -868,7 +878,8 @@ enum { OMPI_COMM_TYPE_CLUSTER, MPI_COMM_TYPE_HW_UNGUIDED, MPI_COMM_TYPE_HW_GUIDED, - MPI_COMM_TYPE_RESOURCE_GUIDED + MPI_COMM_TYPE_RESOURCE_GUIDED, + OMPI_COMM_TYPE_NVLINK }; #define OMPI_COMM_TYPE_NODE MPI_COMM_TYPE_SHARED @@ -1458,6 +1469,14 @@ OMPI_DECLSPEC int MPI_Accumulate(const void *origin_addr, int origin_count, MPI OMPI_DECLSPEC int MPI_Accumulate_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Op op, MPI_Win win); +OMPI_DECLSPEC int MPI_Accumulate_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win); +OMPI_DECLSPEC int MPI_Accumulate_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win); OMPI_DECLSPEC int MPI_Add_error_class(int *errorclass); OMPI_DECLSPEC int MPI_Add_error_code(int errorclass, int *errorcode); OMPI_DECLSPEC int MPI_Add_error_string(int errorcode, const char *string); @@ -1917,6 +1936,14 @@ OMPI_DECLSPEC int MPI_Get_c(void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Win win); +OMPI_DECLSPEC int MPI_Get_notify(void *origin_addr, int origin_count, + MPI_Datatype origin_datatype, int target_rank, + MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, int notification_idx, MPI_Win win); +OMPI_DECLSPEC int MPI_Get_notify_c(void *origin_addr, MPI_Count origin_count, + MPI_Datatype origin_datatype, int target_rank, + MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, int notification_idx, MPI_Win win); OMPI_DECLSPEC int MPI_Get_accumulate(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, void *result_addr, int result_count, MPI_Datatype result_datatype, int target_rank, MPI_Aint target_disp, int target_count, @@ -1925,6 +1952,16 @@ OMPI_DECLSPEC int MPI_Get_accumulate_c(const void *origin_addr, MPI_Count origi void *result_addr, MPI_Count result_count, MPI_Datatype result_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Op op, MPI_Win win); +OMPI_DECLSPEC int MPI_Get_accumulate_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + void *result_addr, int result_count, MPI_Datatype result_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win); +OMPI_DECLSPEC int MPI_Get_accumulate_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + void *result_addr, MPI_Count result_count, MPI_Datatype result_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win); OMPI_DECLSPEC int MPI_Get_library_version(char *version, int *resultlen); OMPI_DECLSPEC int MPI_Get_processor_name(char *name, int *resultlen); OMPI_DECLSPEC int MPI_Get_version(int *version, int *subversion); @@ -2180,6 +2217,12 @@ OMPI_DECLSPEC int MPI_Put(const void *origin_addr, int origin_count, MPI_Dataty OMPI_DECLSPEC int MPI_Put_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Win win); +OMPI_DECLSPEC int MPI_Put_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, int notification_idx, MPI_Win win); +OMPI_DECLSPEC int MPI_Put_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, int notification_idx, MPI_Win win); OMPI_DECLSPEC int MPI_Query_thread(int *provided); OMPI_DECLSPEC int MPI_Raccumulate(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, int target_count, @@ -2187,6 +2230,14 @@ OMPI_DECLSPEC int MPI_Raccumulate(const void *origin_addr, int origin_count, MP OMPI_DECLSPEC int MPI_Raccumulate_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Op op, MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int MPI_Raccumulate_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int MPI_Raccumulate_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win, MPI_Request *request); OMPI_DECLSPEC int MPI_Recv_init(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request); OMPI_DECLSPEC int MPI_Recv_init_c(void *buf, MPI_Count count, MPI_Datatype datatype, int source, @@ -2265,6 +2316,14 @@ OMPI_DECLSPEC int MPI_Rget(void *origin_addr, int origin_count, MPI_Datatype or OMPI_DECLSPEC int MPI_Rget_c(void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int MPI_Rget_notify(void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, int notification_idx, + MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int MPI_Rget_notify_c(void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, int notification_idx, + MPI_Win win, MPI_Request *request); OMPI_DECLSPEC int MPI_Rget_accumulate(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, void *result_addr, int result_count, MPI_Datatype result_datatype, int target_rank, MPI_Aint target_disp, int target_count, @@ -2275,12 +2334,30 @@ OMPI_DECLSPEC int MPI_Rget_accumulate_c(const void *origin_addr, MPI_Count orig int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Op op, MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int MPI_Rget_accumulate_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + void *result_addr, int result_count, MPI_Datatype result_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int MPI_Rget_accumulate_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + void *result_addr, MPI_Count result_count, MPI_Datatype result_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win, MPI_Request *request); OMPI_DECLSPEC int MPI_Rput(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, int target_cout, MPI_Datatype target_datatype, MPI_Win win, MPI_Request *request); OMPI_DECLSPEC int MPI_Rput_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_cout, MPI_Datatype target_datatype, MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int MPI_Rput_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, int notification_idx, + MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int MPI_Rput_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, int notification_idx, + MPI_Win win, MPI_Request *request); OMPI_DECLSPEC int MPI_Rsend(const void *ibuf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm); OMPI_DECLSPEC int MPI_Rsend_c(const void *ibuf, MPI_Count count, MPI_Datatype datatype, int dest, @@ -2363,7 +2440,7 @@ OMPI_DECLSPEC int MPI_Sendrecv_replace(void * buf, int count, MPI_Datatype data OMPI_DECLSPEC int MPI_Sendrecv_replace_c(void * buf, MPI_Count count, MPI_Datatype datatype, int dest, int sendtag, int source, int recvtag, MPI_Comm comm, MPI_Status *status); -OMPI_DECLSPEC MPI_Fint MPI_Session_c2f (const MPI_Session session); +OMPI_DECLSPEC MPI_Fint MPI_Session_c2f (MPI_Session session); OMPI_DECLSPEC int MPI_Session_attach_buffer(MPI_Session session, void *buffer, int size); OMPI_DECLSPEC int MPI_Session_attach_buffer_c(MPI_Session session, void *buffer, MPI_Count size); OMPI_DECLSPEC int MPI_Session_call_errhandler(MPI_Session session, int errorcode); @@ -2604,6 +2681,10 @@ OMPI_DECLSPEC int MPI_Win_get_errhandler(MPI_Win win, MPI_Errhandler *errhandle OMPI_DECLSPEC int MPI_Win_get_group(MPI_Win win, MPI_Group *group); OMPI_DECLSPEC int MPI_Win_get_info(MPI_Win win, MPI_Info *info_used); OMPI_DECLSPEC int MPI_Win_get_name(MPI_Win win, char *win_name, int *resultlen); +OMPI_DECLSPEC int MPI_Win_get_notify_value(MPI_Win win, int notification_idx, MPI_Count *value); +OMPI_DECLSPEC int MPI_Win_reset_notify_value(MPI_Win win, int notification_idx, MPI_Count *value); +OMPI_DECLSPEC int MPI_Win_get_num_notify(MPI_Win win, int target_rank, int *num_notifications); +OMPI_DECLSPEC int MPI_Win_set_num_notify(MPI_Win win, MPI_Info info, int num_notifications); OMPI_DECLSPEC int MPI_Win_lock(int lock_type, int rank, int mpi_assert, MPI_Win win); OMPI_DECLSPEC int MPI_Win_lock_all(int mpi_assert, MPI_Win win); OMPI_DECLSPEC int MPI_Win_post(MPI_Group group, int mpi_assert, MPI_Win win); @@ -2632,6 +2713,14 @@ OMPI_DECLSPEC int PMPI_Accumulate(const void *origin_addr, int origin_count, MP OMPI_DECLSPEC int PMPI_Accumulate_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Op op, MPI_Win win); +OMPI_DECLSPEC int PMPI_Accumulate_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win); +OMPI_DECLSPEC int PMPI_Accumulate_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win); OMPI_DECLSPEC int PMPI_Add_error_class(int *errorclass); OMPI_DECLSPEC int PMPI_Add_error_code(int errorclass, int *errorcode); OMPI_DECLSPEC int PMPI_Add_error_string(int errorcode, const char *string); @@ -3091,6 +3180,14 @@ OMPI_DECLSPEC int PMPI_Get_c(void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Win win); +OMPI_DECLSPEC int PMPI_Get_notify(void *origin_addr, int origin_count, + MPI_Datatype origin_datatype, int target_rank, + MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, int notification_idx, MPI_Win win); +OMPI_DECLSPEC int PMPI_Get_notify_c(void *origin_addr, MPI_Count origin_count, + MPI_Datatype origin_datatype, int target_rank, + MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, int notification_idx, MPI_Win win); OMPI_DECLSPEC int PMPI_Get_accumulate(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, void *result_addr, int result_count, MPI_Datatype result_datatype, int target_rank, MPI_Aint target_disp, int target_count, @@ -3099,6 +3196,16 @@ OMPI_DECLSPEC int PMPI_Get_accumulate_c(const void *origin_addr, MPI_Count orig void *result_addr, MPI_Count result_count, MPI_Datatype result_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Op op, MPI_Win win); +OMPI_DECLSPEC int PMPI_Get_accumulate_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + void *result_addr, int result_count, MPI_Datatype result_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win); +OMPI_DECLSPEC int PMPI_Get_accumulate_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + void *result_addr, MPI_Count result_count, MPI_Datatype result_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win); OMPI_DECLSPEC int PMPI_Get_library_version(char *version, int *resultlen); OMPI_DECLSPEC int PMPI_Get_processor_name(char *name, int *resultlen); OMPI_DECLSPEC int PMPI_Get_version(int *version, int *subversion); @@ -3354,6 +3461,12 @@ OMPI_DECLSPEC int PMPI_Put(const void *origin_addr, int origin_count, MPI_Datat OMPI_DECLSPEC int PMPI_Put_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Win win); +OMPI_DECLSPEC int PMPI_Put_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, int notification_idx, MPI_Win win); +OMPI_DECLSPEC int PMPI_Put_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, int notification_idx, MPI_Win win); OMPI_DECLSPEC int PMPI_Query_thread(int *provided); OMPI_DECLSPEC int PMPI_Raccumulate(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, int target_count, @@ -3361,6 +3474,14 @@ OMPI_DECLSPEC int PMPI_Raccumulate(const void *origin_addr, int origin_count, M OMPI_DECLSPEC int PMPI_Raccumulate_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Op op, MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int PMPI_Raccumulate_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int PMPI_Raccumulate_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win, MPI_Request *request); OMPI_DECLSPEC int PMPI_Recv_init(void *buf, int count, MPI_Datatype datatype, int source, int tag, MPI_Comm comm, MPI_Request *request); OMPI_DECLSPEC int PMPI_Recv_init_c(void *buf, MPI_Count count, MPI_Datatype datatype, int source, @@ -3439,6 +3560,14 @@ OMPI_DECLSPEC int PMPI_Rget(void *origin_addr, int origin_count, MPI_Datatype o OMPI_DECLSPEC int PMPI_Rget_c(void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int PMPI_Rget_notify(void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, int notification_idx, + MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int PMPI_Rget_notify_c(void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, int notification_idx, + MPI_Win win, MPI_Request *request); OMPI_DECLSPEC int PMPI_Rget_accumulate(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, void *result_addr, int result_count, MPI_Datatype result_datatype, int target_rank, MPI_Aint target_disp, int target_count, @@ -3449,12 +3578,30 @@ OMPI_DECLSPEC int PMPI_Rget_accumulate_c(const void *origin_addr, MPI_Count ori int target_rank, MPI_Aint target_disp, MPI_Count target_count, MPI_Datatype target_datatype, MPI_Op op, MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int PMPI_Rget_accumulate_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + void *result_addr, int result_count, MPI_Datatype result_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int PMPI_Rget_accumulate_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + void *result_addr, MPI_Count result_count, MPI_Datatype result_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, MPI_Op op, int notification_idx, + MPI_Win win, MPI_Request *request); OMPI_DECLSPEC int PMPI_Rput(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, int target_cout, MPI_Datatype target_datatype, MPI_Win win, MPI_Request *request); OMPI_DECLSPEC int PMPI_Rput_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, int target_rank, MPI_Aint target_disp, MPI_Count target_cout, MPI_Datatype target_datatype, MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int PMPI_Rput_notify(const void *origin_addr, int origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, int target_count, + MPI_Datatype target_datatype, int notification_idx, + MPI_Win win, MPI_Request *request); +OMPI_DECLSPEC int PMPI_Rput_notify_c(const void *origin_addr, MPI_Count origin_count, MPI_Datatype origin_datatype, + int target_rank, MPI_Aint target_disp, MPI_Count target_count, + MPI_Datatype target_datatype, int notification_idx, + MPI_Win win, MPI_Request *request); OMPI_DECLSPEC int PMPI_Rsend(const void *ibuf, int count, MPI_Datatype datatype, int dest, int tag, MPI_Comm comm); OMPI_DECLSPEC int PMPI_Rsend_c(const void *ibuf, MPI_Count count, MPI_Datatype datatype, int dest, @@ -3537,7 +3684,7 @@ OMPI_DECLSPEC int PMPI_Sendrecv_replace(void * buf, int count, MPI_Datatype dat OMPI_DECLSPEC int PMPI_Sendrecv_replace_c(void * buf, MPI_Count count, MPI_Datatype datatype, int dest, int sendtag, int source, int recvtag, MPI_Comm comm, MPI_Status *status); -OMPI_DECLSPEC MPI_Fint PMPI_Session_c2f (const MPI_Session session); +OMPI_DECLSPEC MPI_Fint PMPI_Session_c2f (MPI_Session session); OMPI_DECLSPEC int PMPI_Session_attach_buffer(MPI_Session session, void *buffer, int size); OMPI_DECLSPEC int PMPI_Session_attach_buffer_c(MPI_Session session, void *buffer, MPI_Count size); OMPI_DECLSPEC int PMPI_Session_call_errhandler(MPI_Session session, int errorcode); @@ -3689,7 +3836,7 @@ OMPI_DECLSPEC int PMPI_Type_get_envelope(MPI_Datatype type, int *num_integers, int *num_addresses, int *num_datatypes, int *combiner); OMPI_DECLSPEC int PMPI_Type_get_envelope_c(MPI_Datatype type, MPI_Count *num_integers, - MPI_Count *num_addresses, MPI_Count *num_large_counts, + MPI_Count *num_addresses, MPI_Count *num_large_counts, MPI_Count *num_datatypes, int *combiner); OMPI_DECLSPEC int PMPI_Type_get_extent(MPI_Datatype type, MPI_Aint *lb, MPI_Aint *extent); @@ -3778,6 +3925,10 @@ OMPI_DECLSPEC int PMPI_Win_get_errhandler(MPI_Win win, MPI_Errhandler *errhandl OMPI_DECLSPEC int PMPI_Win_get_group(MPI_Win win, MPI_Group *group); OMPI_DECLSPEC int PMPI_Win_get_info(MPI_Win win, MPI_Info *info_used); OMPI_DECLSPEC int PMPI_Win_get_name(MPI_Win win, char *win_name, int *resultlen); +OMPI_DECLSPEC int PMPI_Win_get_notify_value(MPI_Win win, int notification_idx, MPI_Count *value); +OMPI_DECLSPEC int PMPI_Win_reset_notify_value(MPI_Win win, int notification_idx, MPI_Count *value); +OMPI_DECLSPEC int PMPI_Win_get_num_notify(MPI_Win win, int target_rank, int *num_notifications); +OMPI_DECLSPEC int PMPI_Win_set_num_notify(MPI_Win win, MPI_Info info, int num_notifications); OMPI_DECLSPEC int PMPI_Win_lock(int lock_type, int rank, int mpi_assert, MPI_Win win); OMPI_DECLSPEC int PMPI_Win_lock_all(int mpi_assert, MPI_Win win); OMPI_DECLSPEC int PMPI_Win_post(MPI_Group group, int mpi_assert, MPI_Win win); diff --git a/ompi/include/mpif-values.py b/ompi/include/mpif-values.py index 53159d5d8dd..bfaca964abf 100755 --- a/ompi/include/mpif-values.py +++ b/ompi/include/mpif-values.py @@ -11,6 +11,7 @@ # Copyright (c) 2025 Jeffrey M. Squyres. All rights reserved. # Copyright (c) 2025 Triad National Security, LLC. All rights # reserved. +# Copyright (c) 2026 NVIDIA Corporation. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -194,6 +195,9 @@ 'MPI_WIN_CREATE_FLAVOR': 10, 'MPI_WIN_MODEL': 11, 'MPI_FT': 12, + 'MPI_WIN_NOTIFICATION_NUM_SB': 13, + 'MPI_WIN_NOTIFICATION_NUM_UB': 14, + 'MPI_WIN_NOTIFICATION_VALUE_UB': 15, 'MPI_WIN_FLAVOR_CREATE': 1, 'MPI_WIN_FLAVOR_ALLOCATE': 2, 'MPI_WIN_FLAVOR_DYNAMIC': 3, @@ -301,6 +305,9 @@ 'MPI_ERR_SESSION': 78, 'MPI_ERR_VALUE_TOO_LARGE': 79, 'MPI_ERR_ERRHANDLER': 80, + 'MPI_T_ERR_NOT_ACCESSIBLE': 81, + 'MPI_T_ERR_NOT_SUPPORTED': 82, + 'MPI_ERR_RMA_NOTIFICATION': 83, 'MPI_ERR_LASTCODE': 92, 'MPI_IDENT': 0, 'MPI_CONGRUENT': 1, @@ -342,6 +349,7 @@ 'MPI_COMM_TYPE_HW_UNGUIDED': 12, 'MPI_COMM_TYPE_HW_GUIDED': 13, 'MPI_COMM_TYPE_RESOURCE_GUIDED': 14, + 'OMPI_COMM_TYPE_NVLINK': 15, } # IO Constants diff --git a/ompi/instance/instance.c b/ompi/instance/instance.c index 6d50d32ffb2..3b93abcf82f 100644 --- a/ompi/instance/instance.c +++ b/ompi/instance/instance.c @@ -1,6 +1,6 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* - * Copyright (c) 2018-2025 Triad National Security, LLC. All rights + * Copyright (c) 2018-2026 Triad National Security, LLC. All rights * reserved. * Copyright (c) 2022 Cisco Systems, Inc. All rights reserved. * Copyright (c) 2022 The University of Tennessee and The University @@ -9,6 +9,7 @@ * Copyright (c) 2023 Jeffrey M. Squyres. All rights reserved. * Copyright (c) 2024 NVIDIA Corporation. All rights reserved. * Copyright (c) 2026 Nanook Consulting All rights reserved. + * Copyright (c) 2026 BULL S.A.S. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -108,8 +109,9 @@ static opal_finalize_domain_t ompi_instance_common_domain; static void ompi_instance_construct (ompi_instance_t *instance) { instance->i_f_to_c_index = opal_pointer_array_add (&ompi_instance_f_to_c_table, instance); + instance->i_name = (char*) malloc (MPI_MAX_OBJECT_NAME); instance->i_name[0] = '\0'; - instance->i_flags = 0; + instance->i_flags = 0; instance->i_keyhash = NULL; OBJ_CONSTRUCT(&instance->s_lock, opal_mutex_t); instance->errhandler_type = OMPI_ERRHANDLER_TYPE_INSTANCE; @@ -118,6 +120,8 @@ static void ompi_instance_construct (ompi_instance_t *instance) static void ompi_instance_destruct(ompi_instance_t *instance) { + free(instance->i_name); + instance->i_name = NULL; OBJ_DESTRUCT(&instance->s_lock); } @@ -421,6 +425,14 @@ static int ompi_mpi_instance_init_common (int argc, char **argv) return ompi_instance_print_error ("ompi_mpi_init: ompi_rte_init failed", ret); } + /* ompi_rte_init() called opal_init(), which set the current finalize + * domain to its own. Restore ours, or every cleanup we register from here + * on lands in OPAL's domain and runs from inside opal_finalize() -- after + * we have already torn the RTE down, and before we close the frameworks + * whose components those cleanups touch. + */ + opal_finalize_set_domain (&ompi_instance_common_domain); + /* open the ompi hook framework */ for (int i = 0 ; ompi_framework_dependencies[i] ; ++i) { ret = mca_base_framework_open (ompi_framework_dependencies[i], 0); @@ -844,6 +856,24 @@ static int ompi_mpi_instance_init_common (int argc, char **argv) return OMPI_SUCCESS; } +/* MPI-5.0 sec 11.3.1: the Session's actually-provided thread level is fixed + * for the Session's lifetime, so always re-assert it regardless of later + * info changes. The returned strings are string literals (static storage), + * which opal_info_set() copies, so no allocation/lifetime management is + * needed. The callback signature matches opal_key_interest_callback_t. */ +static const char *ompi_info_thread_level_cb (opal_infosubscriber_t *obj, + const char *key, const char *value) +{ + ompi_instance_t *instance = (ompi_instance_t *) obj; + switch (instance->i_thread_level) { + case MPI_THREAD_FUNNELED: return "MPI_THREAD_FUNNELED"; + case MPI_THREAD_SERIALIZED: return "MPI_THREAD_SERIALIZED"; + case MPI_THREAD_MULTIPLE: return "MPI_THREAD_MULTIPLE"; + case MPI_THREAD_SINGLE: + default: return "MPI_THREAD_SINGLE"; + } +} + int ompi_mpi_instance_init (int ts_level, opal_info_t *info, ompi_errhandler_t *errhandler, ompi_instance_t **instance, int argc, char **argv) { ompi_instance_t *new_instance; @@ -884,13 +914,34 @@ int ompi_mpi_instance_init (int ts_level, opal_info_t *info, ompi_errhandler_t new_instance->error_handler = errhandler; OBJ_RETAIN(new_instance->error_handler); + /* Store the actually-provided thread level so it can be published via + * MPI_Session_get_info (MPI-5.0 sec 11.3.1 / sec 11.3.3). */ + new_instance->i_thread_level = ts_level; + + + /* Ensure s_info is allocated before subscribing pre-defined keys. */ + if (NULL == new_instance->super.s_info) { + new_instance->super.s_info = OBJ_NEW(opal_info_t); + } + /* Copy info if there is one. */ + if (OPAL_UNLIKELY(NULL != info)) { + opal_info_dup(info, &new_instance->super.s_info); + } + + /* MPI-5.0 sec 11.3.1 / sec 11.3.3: the actually-provided thread support + * level must be retrievable via MPI_Session_get_info. Publish it into the + * Session info so it round-trips through session_get_info(). */ + opal_infosubscribe_subscribe (&new_instance->super, "thread_level", + ompi_info_thread_level_cb (&new_instance->super, + "thread_level", NULL), + ompi_info_thread_level_cb); + if (OPAL_UNLIKELY(NULL != info)) { opal_cstring_t *memkind_requested; ompi_info_memkind_assert_type type; int flag; - - new_instance->super.s_info = OBJ_NEW(opal_info_t); + opal_info_get(info, "mpi_memory_alloc_kinds", &memkind_requested, &flag); if (1 == flag) { char *memkind_provided; @@ -900,10 +951,6 @@ int ompi_mpi_instance_init (int ts_level, opal_info_t *info, ompi_errhandler_t free (memkind_provided); OBJ_RELEASE(memkind_requested); } - - if (info) { - opal_info_dup(info, &new_instance->super.s_info); - } } *instance = new_instance; @@ -972,13 +1019,13 @@ static int ompi_mpi_instance_finalize_common (void) ompi_ulfm_pmix_err_handler = 0; } - /* Leave the RTE */ - if (OMPI_SUCCESS != (ret = ompi_rte_finalize())) { - return ret; - } - - ompi_rte_initialized = false; - + /* Close our frameworks before leaving the RTE. ompi_rte_finalize() calls + * opal_finalize(), and the BML opens the *OPAL* BTL framework on our + * behalf -- so leaving these open across ompi_rte_finalize() means + * finalizing OPAL while an OPAL framework is still open, and its + * components (e.g., the TCP BTL) still have events registered on + * opal_sync_event_base. + */ for (int i = 0 ; ompi_lazy_frameworks[i] ; ++i) { if (0 < ompi_lazy_frameworks[i]->framework_refcnt) { /* May have been "opened" multiple times. We want it closed now! */ @@ -1003,6 +1050,13 @@ static int ompi_mpi_instance_finalize_common (void) } } + /* Leave the RTE */ + if (OMPI_SUCCESS != (ret = ompi_rte_finalize())) { + return ret; + } + + ompi_rte_initialized = false; + ompi_proc_finalize(); ompi_mpi_instance_release (); diff --git a/ompi/instance/instance.h b/ompi/instance/instance.h index ce5fb25919c..fc79dfe1b15 100644 --- a/ompi/instance/instance.h +++ b/ompi/instance/instance.h @@ -29,7 +29,7 @@ struct ompi_instance_t { opal_infosubscriber_t super; opal_mutex_t s_lock; int i_thread_level; - char i_name[MPI_MAX_OBJECT_NAME]; + char *i_name; uint32_t i_flags; /* Attributes */ diff --git a/ompi/mca/coll/accelerator/coll_accelerator_reduce_scatter.c b/ompi/mca/coll/accelerator/coll_accelerator_reduce_scatter.c index 222e9401910..9c4bfeba69c 100644 --- a/ompi/mca/coll/accelerator/coll_accelerator_reduce_scatter.c +++ b/ompi/mca/coll/accelerator/coll_accelerator_reduce_scatter.c @@ -43,7 +43,7 @@ mca_coll_accelerator_reduce_scatter(const void *sbuf, void *rbuf, ompi_count_arr ptrdiff_t gap; char *rbuf1 = NULL, *sbuf1 = NULL, *rbuf2 = NULL; int sbuf_dev, rbuf_dev; - size_t sbufsize, rbufsize, elemsize; + size_t sbufsize, rbufsize, rbuf_in_size, elemsize; int rc, i; int comm_size = ompi_comm_size(comm); int total_count = 0; @@ -73,13 +73,17 @@ mca_coll_accelerator_reduce_scatter(const void *sbuf, void *rbuf, ompi_count_arr goto exit; } rbufsize = elemsize * ompi_count_array_get(rcounts, ompi_comm_rank(comm)); + /* With MPI_IN_PLACE the input lives entirely in rbuf and spans the full + * sum(rcounts); stage that span device->host so the fallback collective + * doesn't read past the host allocation. */ + rbuf_in_size = (MPI_IN_PLACE == sbuf) ? sbufsize : rbufsize; if (0 < rc) { - rbuf1 = (char*)malloc(rbufsize); + rbuf1 = (char*)malloc(rbuf_in_size); if (NULL == rbuf1) { rc = OMPI_ERR_OUT_OF_RESOURCE; goto exit; } - mca_coll_accelerator_memcpy(rbuf1, MCA_ACCELERATOR_NO_DEVICE_ID, rbuf, rbuf_dev, rbufsize, + mca_coll_accelerator_memcpy(rbuf1, MCA_ACCELERATOR_NO_DEVICE_ID, rbuf, rbuf_dev, rbuf_in_size, MCA_ACCELERATOR_TRANSFER_DTOH); rbuf2 = rbuf; /* save away original buffer */ rbuf = rbuf1 - gap; diff --git a/ompi/mca/coll/accelerator/coll_accelerator_reduce_scatter_block.c b/ompi/mca/coll/accelerator/coll_accelerator_reduce_scatter_block.c index 5f0fd61914f..7df1881ce00 100644 --- a/ompi/mca/coll/accelerator/coll_accelerator_reduce_scatter_block.c +++ b/ompi/mca/coll/accelerator/coll_accelerator_reduce_scatter_block.c @@ -43,12 +43,16 @@ mca_coll_accelerator_reduce_scatter_block(const void *sbuf, void *rbuf, size_t r ptrdiff_t gap; char *rbuf1 = NULL, *sbuf1 = NULL, *rbuf2 = NULL; int sbuf_dev, rbuf_dev; - size_t sbufsize, rbufsize; + size_t sbufsize, rbufsize, rbuf_in_size; int rc; rbufsize = opal_datatype_span(&dtype->super, rcount, &gap); sbufsize = rbufsize * ompi_comm_size(comm); + /* With MPI_IN_PLACE the input lives entirely in rbuf and spans + * comm_size * rcount elements; stage the full span device->host so the + * fallback collective doesn't read past the host allocation. */ + rbuf_in_size = (MPI_IN_PLACE == sbuf) ? sbufsize : rbufsize; rc = mca_coll_accelerator_check_buf((void *)sbuf, &sbuf_dev); if (rc < 0) { return rc; @@ -67,12 +71,12 @@ mca_coll_accelerator_reduce_scatter_block(const void *sbuf, void *rbuf, size_t r return rc; } if (rc > 0) { - rbuf1 = (char*)malloc(rbufsize); + rbuf1 = (char*)malloc(rbuf_in_size); if (NULL == rbuf1) { if (NULL != sbuf1) free(sbuf1); return OMPI_ERR_OUT_OF_RESOURCE; } - mca_coll_accelerator_memcpy(rbuf1, MCA_ACCELERATOR_NO_DEVICE_ID, rbuf, rbuf_dev, rbufsize, + mca_coll_accelerator_memcpy(rbuf1, MCA_ACCELERATOR_NO_DEVICE_ID, rbuf, rbuf_dev, rbuf_in_size, MCA_ACCELERATOR_TRANSFER_DTOH); rbuf2 = rbuf; /* save away original buffer */ rbuf = rbuf1 - gap; diff --git a/ompi/mca/coll/acoll/coll_acoll.h b/ompi/mca/coll/acoll/coll_acoll.h index fe1b44081e1..e5528aa62c4 100644 --- a/ompi/mca/coll/acoll/coll_acoll.h +++ b/ompi/mca/coll/acoll/coll_acoll.h @@ -1,6 +1,7 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* * Copyright (c) 2024 - 2025 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2026 NVIDIA Corporation. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -265,4 +266,24 @@ struct mca_coll_acoll_module_t { typedef struct mca_coll_acoll_module_t mca_coll_acoll_module_t; OMPI_DECLSPEC OBJ_CLASS_DECLARATION(mca_coll_acoll_module_t); +/** + * Free a sub-communicator that was OBJ_RETAIN'd by the module. + * Releases the ownership reference safely: ompi_comm_free handles + * attribute cleanup + one OBJ_RELEASE; if the sub-comm survives + * (ownership ref still held), ompi_comm_lookup finds it and + * OBJ_RELEASE drops it to zero. + */ +static inline void coll_acoll_subcomm_free(ompi_communicator_t **comm) +{ + if (NULL != *comm) { + int cid = (*comm)->c_index; + ompi_comm_free(comm); + ompi_communicator_t *tmp = ompi_comm_lookup(cid); + if (NULL != tmp) { + OBJ_RELEASE(tmp); + } + *comm = NULL; + } +} + #endif /* MCA_COLL_ACOLL_EXPORT_H */ diff --git a/ompi/mca/coll/acoll/coll_acoll_component.c b/ompi/mca/coll/acoll/coll_acoll_component.c index d364058990c..a318a8c14ef 100644 --- a/ompi/mca/coll/acoll/coll_acoll_component.c +++ b/ompi/mca/coll/acoll/coll_acoll_component.c @@ -1,6 +1,7 @@ /* -*- Mode: C; c-acoll-offset:4 ; indent-tabs-mode:nil -*- */ /* * Copyright (c) 2024 - 2025 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2026 NVIDIA Corporation. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -322,48 +323,22 @@ static void mca_coll_acoll_module_destruct(mca_coll_acoll_module_t *module) } } - if (subc->local_comm != NULL) { - ompi_comm_free(&(subc->local_comm)); - subc->local_comm = NULL; - } - - if (subc->local_r_comm != NULL) { - ompi_comm_free(&(subc->local_r_comm)); - subc->local_r_comm = NULL; - } - - if (subc->leader_comm != NULL) { - ompi_comm_free(&(subc->leader_comm)); - subc->leader_comm = NULL; - } - - if (subc->subgrp_comm != NULL) { - ompi_comm_free(&(subc->subgrp_comm)); - subc->subgrp_comm = NULL; - } - if (subc->socket_comm != NULL) { - ompi_comm_free(&(subc->socket_comm)); - subc->socket_comm = NULL; - } - - if (subc->socket_ldr_comm != NULL) { - ompi_comm_free(&(subc->socket_ldr_comm)); - subc->socket_ldr_comm = NULL; - } + coll_acoll_subcomm_free(&(subc->local_comm)); + coll_acoll_subcomm_free(&(subc->local_r_comm)); + coll_acoll_subcomm_free(&(subc->leader_comm)); + coll_acoll_subcomm_free(&(subc->subgrp_comm)); + coll_acoll_subcomm_free(&(subc->socket_comm)); + coll_acoll_subcomm_free(&(subc->socket_ldr_comm)); + coll_acoll_subcomm_free(&(subc->numa_comm)); + coll_acoll_subcomm_free(&(subc->numa_comm_ldrs)); for (int k = 0; k < MCA_COLL_ACOLL_NUM_BASE_LYRS; k++) { for (int j = 0; j < MCA_COLL_ACOLL_NUM_LAYERS; j++) { - if (subc->base_comm[k][j] != NULL) { - ompi_comm_free(&(subc->base_comm[k][j])); - subc->base_comm[k][j] = NULL; - } + coll_acoll_subcomm_free(&(subc->base_comm[k][j])); } } for (int k = 0; k < MCA_COLL_ACOLL_SPLIT_FACTOR_LIST_LEN; ++k) { - if (subc->split_comm[k] != NULL) { - ompi_comm_free(&(subc->split_comm[k])); - subc->split_comm[k] = NULL; - } + coll_acoll_subcomm_free(&(subc->split_comm[k])); } subc->initialized = 0; free(subc); diff --git a/ompi/mca/coll/acoll/coll_acoll_utils.h b/ompi/mca/coll/acoll/coll_acoll_utils.h index e15d38b7aaa..41d02381b5f 100644 --- a/ompi/mca/coll/acoll/coll_acoll_utils.h +++ b/ompi/mca/coll/acoll/coll_acoll_utils.h @@ -1,6 +1,7 @@ /* -*- Mode: C; indent-tabs-mode:nil -*- */ /* * Copyright (c) 2024 - 2025 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2026 NVIDIA Corporation. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -286,6 +287,7 @@ static inline int mca_coll_acoll_create_base_comm(ompi_communicator_t **parent_c err = ompi_comm_split(parent_comm[i], color, rank[i], &subc->base_comm[base_lyr][i], false); if (MPI_SUCCESS != err) return err; + OBJ_RETAIN(subc->base_comm[base_lyr][i]); /* Find out local rank of root in base comm */ err = comm_grp_ranks_local(parent_comm[i], subc->base_comm[base_lyr][i], &is_root_node, @@ -448,12 +450,14 @@ static inline int mca_coll_acoll_comm_split_init(ompi_communicator_t *comm, if (MPI_SUCCESS != err) { return err; } + OBJ_RETAIN(subc->local_comm); /* Create socket-level subcommunicator */ err = ompi_comm_split_type(comm, OMPI_COMM_TYPE_SOCKET, 0, &comm_info, &(subc->socket_comm)); if (MPI_SUCCESS != err) { return err; } + OBJ_RETAIN(subc->socket_comm); OBJ_DESTRUCT(&comm_info); OBJ_CONSTRUCT(&comm_info, opal_info_t); opal_info_set(&comm_info, "ompi_comm_coll_preference", "libnbc,basic,^acoll"); @@ -463,10 +467,12 @@ static inline int mca_coll_acoll_comm_split_init(ompi_communicator_t *comm, if (MPI_SUCCESS != err) { return err; } + OBJ_RETAIN(subc->subgrp_comm); err = ompi_comm_split_type(comm, OMPI_COMM_TYPE_NUMA, 0, &comm_info, &(subc->numa_comm)); if (MPI_SUCCESS != err) { return err; } + OBJ_RETAIN(subc->numa_comm); subc->subgrp_size = ompi_comm_size(subc->subgrp_comm); OBJ_DESTRUCT(&comm_info); @@ -514,18 +520,14 @@ static inline int mca_coll_acoll_comm_split_init(ompi_communicator_t *comm, if (subc->initialized) { if (subc->num_nodes > 1) { - ompi_comm_free(&(subc->leader_comm)); - subc->leader_comm = NULL; + coll_acoll_subcomm_free(&(subc->leader_comm)); } - ompi_comm_free(&(subc->socket_ldr_comm)); - subc->socket_ldr_comm = NULL; + coll_acoll_subcomm_free(&(subc->socket_ldr_comm)); } for (int i = 0; i < MCA_COLL_ACOLL_NUM_LAYERS; i++) { if (subc->initialized) { - ompi_comm_free(&(subc->base_comm[MCA_COLL_ACOLL_L3CACHE][i])); - subc->base_comm[MCA_COLL_ACOLL_L3CACHE][i] = NULL; - ompi_comm_free(&(subc->base_comm[MCA_COLL_ACOLL_NUMA][i])); - subc->base_comm[MCA_COLL_ACOLL_NUMA][i] = NULL; + coll_acoll_subcomm_free(&(subc->base_comm[MCA_COLL_ACOLL_L3CACHE][i])); + coll_acoll_subcomm_free(&(subc->base_comm[MCA_COLL_ACOLL_NUMA][i])); } subc->base_root[MCA_COLL_ACOLL_L3CACHE][i] = -1; subc->base_root[MCA_COLL_ACOLL_NUMA][i] = -1; @@ -577,6 +579,7 @@ static inline int mca_coll_acoll_comm_split_init(ompi_communicator_t *comm, if (MPI_SUCCESS != err) { return err; } + OBJ_RETAIN(subc->leader_comm); /* Find out local rank of root in leader comm */ err = comm_grp_ranks_local(comm, subc->leader_comm, &is_root_node, &subc->outer_grp_root, @@ -596,6 +599,7 @@ static inline int mca_coll_acoll_comm_split_init(ompi_communicator_t *comm, err = ompi_comm_split(comm, color, rank, &subc->socket_ldr_comm, false); if (MPI_SUCCESS != err) return err; + OBJ_RETAIN(subc->socket_ldr_comm); /* Find out local rank of root in socket leader comm */ err = comm_grp_ranks_local(comm, subc->socket_ldr_comm, &is_root_socket, @@ -661,6 +665,7 @@ static inline int mca_coll_acoll_comm_split_init(ompi_communicator_t *comm, if (MPI_SUCCESS != err) { return err; } + OBJ_RETAIN(subc->socket_ldr_comm); /* Find out local rank of root in socket leader comm */ err = comm_grp_ranks_local(comm, subc->socket_ldr_comm, &is_root_socket, @@ -693,6 +698,10 @@ static inline int mca_coll_acoll_comm_split_init(ompi_communicator_t *comm, numa_rank = ompi_comm_rank(subc->numa_comm); color = (0 == numa_rank) ? 0 : 1; err = ompi_comm_split(subc->local_comm, color, rank, &subc->numa_comm_ldrs, false); + if (MPI_SUCCESS != err) { + return err; + } + OBJ_RETAIN(subc->numa_comm_ldrs); /* Find out local rank of root in numa comm */ err = comm_grp_ranks_local(comm, subc->numa_comm, &subc->is_root_numa, &subc->numa_root, @@ -745,6 +754,7 @@ static inline int mca_coll_acoll_comm_split_init(ompi_communicator_t *comm, if (MPI_SUCCESS != err) { return err; } + OBJ_RETAIN(subc->local_r_comm); } err = mca_coll_acoll_derive_r2r_latency(comm, subc, acoll_module); @@ -770,6 +780,7 @@ static inline int mca_coll_acoll_comm_split_init(ompi_communicator_t *comm, if (MPI_SUCCESS != err) { return err; } + OBJ_RETAIN(subc->split_comm[ii]); } subc->derived_node_size = (size + subc->num_nodes - 1) / subc->num_nodes; diff --git a/ompi/mca/coll/base/coll_base_allgatherv.c b/ompi/mca/coll/base/coll_base_allgatherv.c index 337e09f7c77..9d86772181f 100644 --- a/ompi/mca/coll/base/coll_base_allgatherv.c +++ b/ompi/mca/coll/base/coll_base_allgatherv.c @@ -16,6 +16,7 @@ * Copyright (c) 2015-2016 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2017 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -101,7 +102,8 @@ int ompi_coll_base_allgatherv_intra_bruck(const void *sbuf, size_t scount, mca_coll_base_module_t *module) { int line = -1, err = 0, rank, size, sendto, recvfrom, distance, blockcount, i; - int *new_rcounts = NULL, *new_rdispls = NULL, *new_scounts = NULL, *new_sdispls = NULL; + size_t *new_rcounts = NULL, *new_scounts = NULL; + ptrdiff_t *new_rdispls = NULL, *new_sdispls = NULL; ptrdiff_t rlb, rext; char *tmpsend = NULL, *tmprecv = NULL; struct ompi_datatype_t *new_rdtype, *new_sdtype; @@ -142,11 +144,11 @@ int ompi_coll_base_allgatherv_intra_bruck(const void *sbuf, size_t scount, blockcount = 1; tmpsend = (char*) rbuf; - new_rcounts = (int*) calloc(4*size, sizeof(int)); + new_rcounts = (size_t*) calloc(4*size, sizeof(size_t)); if (NULL == new_rcounts) { err = -1; line = __LINE__; goto err_hndl; } - new_rdispls = new_rcounts + size; - new_scounts = new_rdispls + size; - new_sdispls = new_scounts + size; + new_scounts = new_rcounts + size; + new_rdispls = (ptrdiff_t*) (new_scounts + size); + new_sdispls = new_rdispls + size; for (distance = 1; distance < size; distance<<=1) { @@ -168,10 +170,12 @@ int ompi_coll_base_allgatherv_intra_bruck(const void *sbuf, size_t scount, new_rcounts[i] = ompi_count_array_get(rcounts, tmp_rrank); new_rdispls[i] = ompi_disp_array_get(rdispls, tmp_rrank); } - err = ompi_datatype_create_indexed(blockcount, new_scounts, new_sdispls, + err = ompi_datatype_create_indexed(blockcount, OMPI_COUNT_ARRAY_CREATE(new_scounts), + OMPI_DISP_ARRAY_CREATE(new_sdispls), rdtype, &new_sdtype); if (MPI_SUCCESS != err) { line = __LINE__; goto err_hndl; } - err = ompi_datatype_create_indexed(blockcount, new_rcounts, new_rdispls, + err = ompi_datatype_create_indexed(blockcount, OMPI_COUNT_ARRAY_CREATE(new_rcounts), + OMPI_DISP_ARRAY_CREATE(new_rdispls), rdtype, &new_rdtype); err = ompi_datatype_commit(&new_sdtype); @@ -513,7 +517,8 @@ ompi_coll_base_allgatherv_intra_neighborexchange(const void *sbuf, size_t scount int neighbor[2], offset_at_step[2], recv_data_from[2], send_data_from; size_t new_scounts[2], new_rcounts[2]; ptrdiff_t new_sdispls[2], new_rdispls[2]; - int tmp_new_scounts[2], tmp_new_rcounts[2], tmp_new_sdispls[2], tmp_new_rdispls[2]; + size_t tmp_new_scounts[2], tmp_new_rcounts[2]; + ptrdiff_t tmp_new_sdispls[2], tmp_new_rdispls[2]; ptrdiff_t rlb, rext; char *tmpsend = NULL, *tmprecv = NULL; struct ompi_datatype_t *new_rdtype, *new_sdtype; @@ -611,7 +616,8 @@ ompi_coll_base_allgatherv_intra_neighborexchange(const void *sbuf, size_t scount tmp_new_scounts[1] = new_scounts[1]; tmp_new_sdispls[0] = new_sdispls[0]; tmp_new_sdispls[1] = new_sdispls[1]; - err = ompi_datatype_create_indexed(2, tmp_new_scounts, tmp_new_sdispls, rdtype, + err = ompi_datatype_create_indexed(2, OMPI_COUNT_ARRAY_CREATE(tmp_new_scounts), + OMPI_DISP_ARRAY_CREATE(tmp_new_sdispls), rdtype, &new_sdtype); if (MPI_SUCCESS != err) { line = __LINE__; goto err_hndl; } err = ompi_datatype_commit(&new_sdtype); @@ -626,7 +632,8 @@ ompi_coll_base_allgatherv_intra_neighborexchange(const void *sbuf, size_t scount tmp_new_rcounts[1] = new_rcounts[1]; tmp_new_rdispls[0] = new_rdispls[0]; tmp_new_rdispls[1] = new_rdispls[1]; - err = ompi_datatype_create_indexed(2, tmp_new_rcounts, tmp_new_rdispls, rdtype, + err = ompi_datatype_create_indexed(2, OMPI_COUNT_ARRAY_CREATE(tmp_new_rcounts), + OMPI_DISP_ARRAY_CREATE(tmp_new_rdispls), rdtype, &new_rdtype); if (MPI_SUCCESS != err) { line = __LINE__; goto err_hndl; } err = ompi_datatype_commit(&new_rdtype); @@ -757,7 +764,6 @@ ompi_coll_base_allgatherv_intra_basic_default(const void *sbuf, size_t scount, MPI_Aint extent, lb; char *send_buf = NULL; struct ompi_datatype_t *newtype, *send_type; - int *tmp_rcounts, *tmp_disps; size = ompi_comm_size(comm); rank = ompi_comm_rank(comm); @@ -801,22 +807,8 @@ ompi_coll_base_allgatherv_intra_basic_default(const void *sbuf, size_t scount, * datatype. */ - /* TODO:BIGCOUNT: Remove temporaries once ompi_datatype interface is updated */ - tmp_rcounts = malloc(size * sizeof(int)); - if (NULL == tmp_rcounts) { - return OMPI_ERR_OUT_OF_RESOURCE; - } - tmp_disps = malloc(size * sizeof(int)); - if (NULL == tmp_disps) { - return OMPI_ERR_OUT_OF_RESOURCE; - } - for (int i = 0; i < size; i++) { - tmp_rcounts[i] = ompi_count_array_get(rcounts, i); - tmp_disps[i] = ompi_disp_array_get(disps, i); - } - err = ompi_datatype_create_indexed(size,tmp_rcounts,tmp_disps,rdtype,&newtype); - free(tmp_rcounts); - free(tmp_disps); + err = ompi_datatype_create_indexed(size, rcounts, disps, + rdtype, &newtype); if (MPI_SUCCESS != err) { return err; } diff --git a/ompi/mca/coll/base/coll_base_reduce_scatter_block.c b/ompi/mca/coll/base/coll_base_reduce_scatter_block.c index f72469d1a00..ca4a6989bec 100644 --- a/ompi/mca/coll/base/coll_base_reduce_scatter_block.c +++ b/ompi/mca/coll/base/coll_base_reduce_scatter_block.c @@ -19,6 +19,7 @@ * and Information Sciences. All rights reserved. * Copyright (c) 2022 IBM Corporation. All rights reserved. * Copyright (c) 2023 Jeffrey M. Squyres. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -202,7 +203,8 @@ ompi_coll_base_reduce_scatter_block_intra_recursivedoubling( struct ompi_datatype_t *dtypesend = NULL, *dtyperecv = NULL; char *tmprecv_raw = NULL, *tmpbuf_raw = NULL, *tmprecv, *tmpbuf; ptrdiff_t span, gap, totalcount, extent; - int blocklens[2], displs[2]; + size_t blocklens[2]; + ptrdiff_t displs[2]; int err = MPI_SUCCESS; int comm_size = ompi_comm_size(comm); int rank = ompi_comm_rank(comm); @@ -270,7 +272,8 @@ ompi_coll_base_reduce_scatter_block_intra_recursivedoubling( rcount * (comm_size - cur_tree_root - mask) : 0; displs[0] = 0; displs[1] = comm_size * rcount - blocklens[1]; - err = ompi_datatype_create_indexed(2, blocklens, displs, dtype, &dtypesend); + err = ompi_datatype_create_indexed(2, OMPI_COUNT_ARRAY_CREATE(blocklens), + OMPI_DISP_ARRAY_CREATE(displs), dtype, &dtypesend); if (MPI_SUCCESS != err) { goto cleanup_and_return; } err = ompi_datatype_commit(&dtypesend); if (MPI_SUCCESS != err) { goto cleanup_and_return; } @@ -281,7 +284,8 @@ ompi_coll_base_reduce_scatter_block_intra_recursivedoubling( rcount * (comm_size - remote_tree_root - mask) : 0; displs[0] = 0; displs[1] = comm_size * rcount - blocklens[1]; - err = ompi_datatype_create_indexed(2, blocklens, displs, dtype, &dtyperecv); + err = ompi_datatype_create_indexed(2, OMPI_COUNT_ARRAY_CREATE(blocklens), + OMPI_DISP_ARRAY_CREATE(displs), dtype, &dtyperecv); if (MPI_SUCCESS != err) { goto cleanup_and_return; } err = ompi_datatype_commit(&dtyperecv); if (MPI_SUCCESS != err) { goto cleanup_and_return; } diff --git a/ompi/mca/coll/base/coll_base_util.c b/ompi/mca/coll/base/coll_base_util.c index ba74aa01350..470a8bd4ab2 100644 --- a/ompi/mca/coll/base/coll_base_util.c +++ b/ompi/mca/coll/base/coll_base_util.c @@ -14,6 +14,8 @@ * Copyright (c) 2023 Jeffrey M. Squyres. All rights reserved. * * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2025 Triad National Security, LLC. All rights + * reserved. * * $COPYRIGHT$ * @@ -134,7 +136,7 @@ int ompi_rounddown(int num, int factor) /** * Release all objects and arrays stored into the nbc_request. * The release_arrays are temporary memory to stored the values - * converted from Fortran, and should disappear in same time as the + * converted from Fortran or elsewhere, and should disappear in same time as the * request itself. */ static void @@ -268,6 +270,7 @@ static void release_vecs_callback(ompi_coll_base_nbc_request_t *request) } request->data.refcounted.vecs.rtypes = NULL; } + release_objs_callback(request); } static int complete_vecs_callback(struct ompi_request_t *req) { @@ -346,13 +349,33 @@ int ompi_coll_base_retain_datatypes_w( ompi_request_t *req, return OMPI_SUCCESS; } +int ompi_coll_base_add_release_arrays_cb(ompi_request_t *req) +{ + ompi_coll_base_nbc_request_t *request = (ompi_coll_base_nbc_request_t *)req; + + assert(NULL != request); + + if (req->req_persistent && (NULL == req->req_free)) { + request->cb.req_free = req->req_free; + req->req_free = free_objs_callback; + } else if(NULL == req->req_complete_cb) { + request->cb.req_complete_cb = req->req_complete_cb; + request->req_complete_cb_data = req->req_complete_cb_data; + req->req_complete_cb = complete_objs_callback; + req->req_complete_cb_data = request; + } + return OMPI_SUCCESS; +} + static void nbc_req_constructor(ompi_coll_base_nbc_request_t *req) { req->cb.req_complete_cb = NULL; req->req_complete_cb_data = NULL; req->data.refcounted.objs.objs[0] = NULL; req->data.refcounted.objs.objs[1] = NULL; - req->data.release_arrays[0] = NULL; + for (int i = 0; i < OMPI_REQ_NB_RELEASE_ARRAYS; i++ ) { + req->data.release_arrays[i] = NULL; + } } OBJ_CLASS_INSTANCE(ompi_coll_base_nbc_request_t, ompi_request_t, nbc_req_constructor, NULL); diff --git a/ompi/mca/coll/base/coll_base_util.h b/ompi/mca/coll/base/coll_base_util.h index 7bceaa7dcc0..3a52cdebdd8 100644 --- a/ompi/mca/coll/base/coll_base_util.h +++ b/ompi/mca/coll/base/coll_base_util.h @@ -12,6 +12,8 @@ * Copyright (c) 2014-2020 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2024 NVIDIA CORPORATION. All rights reserved. + * Copyright (c) 2025 Triad National Security, LLC. All rights + * reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -95,6 +97,42 @@ ompi_coll_base_nbc_reserve_tags(ompi_communicator_t* comm, int32_t reserve) return tag; } +/** + * Append an array to a request object to be freed upon completion + * of the associated operation. + * The request object must be of type ompi_coll_base_nbc_request_t. + */ +__opal_attribute_always_inline__ static inline int +ompi_coll_base_append_array_to_release(struct ompi_request_t *req, void *array_ptr) +{ + int i, ret = OMPI_SUCCESS; + struct ompi_coll_base_nbc_request_t *request = (struct ompi_coll_base_nbc_request_t *)req; + + /* + * important sanity check - doing steps below on a non-libnbc request can lead + * to difficult to debug memory corruption problems + */ + assert(request->super.req_type == OMPI_REQUEST_COLL); + + for(i = 0; i < OMPI_REQ_NB_RELEASE_ARRAYS; i++ ) { + if (NULL == request->data.release_arrays[i]) { + break; + } + } + + if (OMPI_REQ_NB_RELEASE_ARRAYS > i) { + request->data.release_arrays[i] = array_ptr; + ++i; + if (OMPI_REQ_NB_RELEASE_ARRAYS > i) { + request->data.release_arrays[i] = NULL; + } + } else { + ret = OMPI_ERR_OUT_OF_RESOURCE; + } + + return ret; +} + typedef struct ompi_coll_base_nbc_request_t ompi_coll_base_nbc_request_t; /* @@ -188,6 +226,13 @@ int ompi_coll_base_retain_datatypes_w( ompi_request_t *request, ompi_datatype_t * const rtypes[], bool use_topo); +/** + * If necessary, set callback to free extra memory regions + * set in release_arrays. Not set if a callback is already + * associated with the request. + */ +int ompi_coll_base_add_release_arrays_cb(ompi_request_t *request); + /* File reading function */ int ompi_coll_base_file_getnext_long(FILE *fptr, int *fileline, long* val); int ompi_coll_base_file_getnext_size_t(FILE *fptr, int *fileline, size_t* val); diff --git a/ompi/mca/coll/base/coll_tags.h b/ompi/mca/coll/base/coll_tags.h index 5d3da7eafe5..fe9e1ce9424 100644 --- a/ompi/mca/coll/base/coll_tags.h +++ b/ompi/mca/coll/base/coll_tags.h @@ -61,9 +61,13 @@ #define MCA_COLL_BASE_TAG_UCC (MCA_COLL_BASE_TAG_FT_END - 1) -#define MCA_COLL_BASE_TAG_STATIC_END (MCA_COLL_BASE_TAG_UCC - 1) - +/* Distributed graph construction uses PML messages before the new + * communicator is fully created. Keep these tags out of the nonblocking + * collective tag range. */ +#define MCA_COLL_BASE_TAG_TOPO_DIST_EDGE_IN (MCA_COLL_BASE_TAG_UCC - 1) +#define MCA_COLL_BASE_TAG_TOPO_DIST_EDGE_OUT (MCA_COLL_BASE_TAG_TOPO_DIST_EDGE_IN - 1) +#define MCA_COLL_BASE_TAG_STATIC_END (MCA_COLL_BASE_TAG_TOPO_DIST_EDGE_OUT) #define MCA_COLL_BASE_TAG_NONBLOCKING_BASE (MCA_COLL_BASE_TAG_STATIC_END - 1) #define MCA_COLL_BASE_TAG_NONBLOCKING_END ((-1 * INT_MAX/2) + 1) diff --git a/ompi/mca/coll/han/coll_han.h b/ompi/mca/coll/han/coll_han.h index 2af2ae7a2c4..01237afc9f7 100644 --- a/ompi/mca/coll/han/coll_han.h +++ b/ompi/mca/coll/han/coll_han.h @@ -43,6 +43,7 @@ #include "ompi/mca/mca.h" #include "opal/util/output.h" #include "opal/mca/smsc/smsc.h" +#include "opal/class/opal_free_list.h" #include "ompi/mca/coll/base/coll_base_functions.h" #include "coll_han_trigger.h" #include "ompi/mca/coll/han/coll_han_dynamic.h" @@ -57,6 +58,68 @@ #define COLL_HAN_LOW_MODULES 3 #define COLL_HAN_UP_MODULES 2 +/** + * Fragment item for freelist-based buffer pool + * Used to provide stable buffer addresses across collective calls + */ +typedef struct fragment_item_s { + opal_free_list_item_t super; + void *buffer; /* Fixed-size buffer (han_fragment_size bytes) */ +} fragment_item_t; +OBJ_CLASS_DECLARATION(fragment_item_t); + +/** + * Large fragment item for freelist-based buffer pool. + * Size controlled by han_large_fragment_size MCA parameter (0 = disabled). + */ +typedef struct large_fragment_item_s { + opal_free_list_item_t super; + void *buffer; +} large_fragment_item_t; +OBJ_CLASS_DECLARATION(large_fragment_item_t); + +/** Source tag for tiered allocation (used by alloc/free helpers). */ +enum { + HAN_ALLOC_MALLOC = 0, + HAN_ALLOC_LARGE = 1, + HAN_ALLOC_SMALL = 2 +}; + +/** + * Grow a shared scratch buffer to at least 'needed' bytes (realloc-to-HWM). + * Returns the buffer pointer, or NULL on allocation failure. + */ +static inline char *han_scratch_alloc(char **buf, size_t *buf_size, size_t needed) +{ + if (0 == needed) { + /* Return a valid non-NULL pointer for zero-size allocations, + * matching malloc(0) behavior that callers rely on. */ + static char zero_len_sentinel; + return &zero_len_sentinel; + } + if (*buf_size < needed) { + char *p = realloc(*buf, needed); + if (NULL == p) return NULL; + *buf = p; + *buf_size = needed; + } + return *buf; +} + +/** + * Allocate from scratch buffer (persist mode) or malloc (non-persist). + * Returns NULL on allocation failure. + */ +static inline char *han_scratch_or_malloc(char **scratch, size_t *scratch_size, + size_t needed, bool persist) +{ + if (persist) { + return han_scratch_alloc(scratch, scratch_size, needed); + } + return (char *)malloc(needed); +} + + struct mca_coll_han_bcast_args_s { mca_coll_task_t *cur_task; ompi_communicator_t *up_comm; @@ -115,11 +178,15 @@ struct mca_coll_han_allreduce_args_s { }; typedef struct mca_coll_han_allreduce_args_s mca_coll_han_allreduce_args_t; +/* Forward declaration needed by scatter and gather arg structs */ +typedef struct mca_coll_han_module_t mca_coll_han_module_t; + struct mca_coll_han_scatter_args_s { mca_coll_task_t *cur_task; ompi_communicator_t *up_comm; ompi_communicator_t *low_comm; ompi_request_t *req; + mca_coll_han_module_t *han_module; void *sbuf; void *sbuf_inter_free; void *sbuf_reorder_free; @@ -133,6 +200,8 @@ struct mca_coll_han_scatter_args_s { int root_low_rank; int w_rank; bool noop; + opal_free_list_item_t *reorder_fl_item; /* freelist item for reorder buf */ + int reorder_fl_src; /* HAN_ALLOC_{MALLOC,LARGE,SMALL} */ }; typedef struct mca_coll_han_scatter_args_s mca_coll_han_scatter_args_t; @@ -141,6 +210,7 @@ struct mca_coll_han_gather_args_s { ompi_communicator_t *up_comm; ompi_communicator_t *low_comm; ompi_request_t *req; + mca_coll_han_module_t *han_module; void *sbuf; void *sbuf_inter_free; void *rbuf; @@ -174,6 +244,8 @@ struct mca_coll_han_allgather_s { bool noop; bool is_mapbycore; int *topo; + mca_coll_han_module_t *han_module; + opal_free_list_item_t *inter_frag; /* Fragment for inter-node buffer */ }; typedef struct mca_coll_han_allgather_s mca_coll_han_allgather_t; @@ -296,6 +368,13 @@ typedef struct mca_coll_han_component_t { opal_free_list_t pack_buffers; int64_t han_packbuf_max_count; int64_t han_packbuf_bytes; + + /* Persist-buffer optimization (0 = disabled, use malloc/free) */ + bool han_use_persist_buffers; + + /* Fragment size for buffer reuse optimization (0 = disabled) */ + size_t han_fragment_size; + size_t han_large_fragment_size; } mca_coll_han_component_t; /* @@ -386,6 +465,50 @@ typedef struct mca_coll_han_module_t { /* Sub-communicator */ struct ompi_communicator_t *sub_comm[NB_TOPO_LVL]; + + /* Fragment pool for buffer reuse (64KB items) */ + opal_free_list_t fragment_freelist; + /* Large fragment pool for pipeline reorder buffers (1MB items) */ + opal_free_list_t large_fragment_freelist; + /* Shared scratch buffers for all collectives (realloc-to-HWM). + * Since collectives don't run concurrently on the same communicator, + * all collectives share these two buffers. Two are needed because + * some collectives use two temporary buffers with overlapping lifetimes + * (e.g., allgather uses a gather buffer and a reorder buffer). */ + char *scratch_buf[2]; + size_t scratch_buf_size[2]; + + struct han_alltoall_cache { + char *bounce; + size_t bounce_size; + const void *cached_sbuf; + size_t cached_scount; + int cached_low_size; + char **low_bufs; + void **map_ctx; + void **gather_buf; + int cached_send_needs_bounce; + int cached_ii_push_data; + char *recv_buf; + size_t recv_buf_size; + } a2a_cache; + + struct han_alltoallv_cache { + uint8_t *serial_buf; + size_t serial_buf_size; + void *gather_out; + void *peers; + void *peer_types; + int low_size; + void **send_from; + void **recv_to; + size_t *send_counts; + size_t *recv_counts; + void **send_types; + void **recv_types; + bool smsc_decided; + int use_smsc; + } a2av_cache; } mca_coll_han_module_t; OBJ_CLASS_DECLARATION(mca_coll_han_module_t); @@ -564,7 +687,7 @@ ompi_coll_han_reorder_gather(const void *sbuf, void *rbuf, size_t rcount, struct ompi_datatype_t *rdtype, struct ompi_communicator_t *comm, - int * topo); + const int * topo); static inline struct mca_smsc_endpoint_t *mca_coll_han_get_smsc_endpoint (struct ompi_proc_t *proc) { extern opal_mutex_t mca_coll_han_lock; diff --git a/ompi/mca/coll/han/coll_han_allgather.c b/ompi/mca/coll/han/coll_han_allgather.c index 9d3a0825f83..6d561bf12ac 100644 --- a/ompi/mca/coll/han/coll_han_allgather.c +++ b/ompi/mca/coll/han/coll_han_allgather.c @@ -24,10 +24,95 @@ #include "ompi/mca/pml/pml.h" #include "coll_han_trigger.h" +/* Minimum number of fragments before the pipeline path is used. + * Below this threshold the simple path avoids pipeline setup overhead. */ +#define HAN_MIN_PIPELINE_FRAGS 4 + static int mca_coll_han_allgather_lb_task(void *task_args); static int mca_coll_han_allgather_lg_task(void *task_args); static int mca_coll_han_allgather_uag_task(void *task_args); +/** + * Allocate a buffer from the small fragment freelist, falling back to malloc. + * On return, *item is non-NULL if the buffer came from the freelist. + */ +static char *han_alloc_frag(opal_free_list_t *fl, size_t frag_size, + size_t needed, opal_free_list_item_t **item) +{ + *item = NULL; + if (mca_coll_han_component.han_use_persist_buffers + && frag_size > 0 && needed <= frag_size) { + fragment_item_t *fi = (fragment_item_t *)opal_free_list_get(fl); + if (fi != NULL) { + *item = (opal_free_list_item_t *)fi; + return (char *)fi->buffer; + } + } + return (char *)malloc(needed); +} + +/** + * Free a buffer: return to freelist if item is non-NULL, else free(). + */ +static void han_free_frag(opal_free_list_t *fl, opal_free_list_item_t *item, + char *buf) +{ + if (item != NULL) { + opal_free_list_return(fl, item); + } else { + free(buf); + } +} + +/** + * Tiered allocation: try large freelist, then small freelist, then malloc. + * Sets *item and *src (1=large, 2=small, 0=malloc) for han_free_tiered(). + */ +static char *han_alloc_tiered(opal_free_list_t *large_fl, size_t large_size, + opal_free_list_t *small_fl, size_t small_size, + size_t needed, opal_free_list_item_t **item, + int *src) +{ + *item = NULL; + *src = HAN_ALLOC_MALLOC; + if (!mca_coll_han_component.han_use_persist_buffers) { + return (char *)malloc(needed); + } + if (large_size > 0 && needed <= large_size) { + large_fragment_item_t *lfi = (large_fragment_item_t *)opal_free_list_get(large_fl); + if (lfi != NULL) { + *item = (opal_free_list_item_t *)lfi; + *src = HAN_ALLOC_LARGE; + return (char *)lfi->buffer; + } + } + if (small_size > 0 && needed <= small_size) { + fragment_item_t *fi = (fragment_item_t *)opal_free_list_get(small_fl); + if (fi != NULL) { + *item = (opal_free_list_item_t *)fi; + *src = HAN_ALLOC_SMALL; + return (char *)fi->buffer; + } + } + return (char *)malloc(needed); +} + +/** + * Free a tiered allocation based on src tag. + */ +static void han_free_tiered(opal_free_list_t *large_fl, + opal_free_list_t *small_fl, + opal_free_list_item_t *item, char *buf, int src) +{ + if (src == HAN_ALLOC_LARGE) { + opal_free_list_return(large_fl, item); + } else if (src == HAN_ALLOC_SMALL) { + opal_free_list_return(small_fl, item); + } else { + free(buf); + } +} + static inline void mca_coll_han_set_allgather_args(mca_coll_han_allgather_t * args, mca_coll_task_t * cur_task, @@ -45,7 +130,8 @@ mca_coll_han_set_allgather_args(mca_coll_han_allgather_t * args, bool noop, bool is_mapbycore, int *topo, - ompi_request_t * req) + ompi_request_t * req, + mca_coll_han_module_t *han_module) { args->cur_task = cur_task; args->sbuf = sbuf; @@ -63,6 +149,8 @@ mca_coll_han_set_allgather_args(mca_coll_han_allgather_t * args, args->is_mapbycore = is_mapbycore; args->topo = topo; args->req = req; + args->han_module = han_module; + args->inter_frag = NULL; } @@ -120,7 +208,7 @@ mca_coll_han_allgather_intra(const void *sbuf, size_t scount, mca_coll_han_set_allgather_args(lg_args, lg, (char *) sbuf, NULL, scount, sdtype, rbuf, rcount, rdtype, root_low_rank, up_comm, low_comm, w_rank, low_rank != root_low_rank, han_module->is_mapbycore, topo, - temp_request); + temp_request, han_module); /* Init and issue lg task */ init_task(lg, mca_coll_han_allgather_lg_task, (void *) (lg_args)); issue_task(lg); @@ -140,19 +228,73 @@ int mca_coll_han_allgather_lg_task(void *task_args) OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, "[%d] HAN Allgather: lg\n", t->w_rank)); - /* If the process is one of the node leader */ ptrdiff_t rlb, rext; ompi_datatype_get_extent (t->rdtype, &rlb, &rext); if (MPI_IN_PLACE == t->sbuf) { t->sdtype = t->rdtype; t->scount = t->rcount; } + + /* If the process is one of the node leaders, allocate receive buffer */ if (!t->noop) { int low_size = ompi_comm_size(t->low_comm); ptrdiff_t rsize, rgap = 0; + + /* Mapbycore in-place: gather directly into rbuf slot, skip tmp_buf */ + if (t->is_mapbycore && mca_coll_han_component.han_use_persist_buffers) { + int up_rank = ompi_comm_rank(t->up_comm); + size_t total_count = t->rcount * low_size; + char *my_slot = (char *)t->rbuf + + (ptrdiff_t)up_rank * (ptrdiff_t)total_count * rext; + + if (MPI_IN_PLACE == t->sbuf) { + char *my_data = ((char*)t->rbuf) + + (ptrdiff_t)t->w_rank * (ptrdiff_t)t->rcount * rext; + ompi_datatype_copy_content_same_ddt(t->rdtype, t->rcount, + my_slot, my_data); + t->low_comm->c_coll->coll_gather(MPI_IN_PLACE, t->scount, t->sdtype, + my_slot, t->rcount, t->rdtype, + t->root_low_rank, t->low_comm, + t->low_comm->c_coll->coll_gather_module); + } else { + t->low_comm->c_coll->coll_gather((char *)t->sbuf, t->scount, t->sdtype, + my_slot, t->rcount, t->rdtype, + t->root_low_rank, t->low_comm, + t->low_comm->c_coll->coll_gather_module); + } + t->sbuf = my_slot; + t->sbuf_inter_free = NULL; + t->inter_frag = NULL; + + /* Create uag task */ + mca_coll_task_t *uag = t->cur_task; + init_task(uag, mca_coll_han_allgather_uag_task, (void *) t); + issue_task(uag); + return OMPI_SUCCESS; + } + rsize = opal_datatype_span(&t->rdtype->super, (int64_t) t->rcount * low_size, &rgap); - tmp_buf = (char *) malloc(rsize); + + t->inter_frag = NULL; + if (mca_coll_han_component.han_use_persist_buffers && t->han_module != NULL) { + if ((size_t)rsize <= mca_coll_han_component.han_fragment_size + && mca_coll_han_component.han_fragment_size > 0) { + tmp_buf = han_alloc_frag(&t->han_module->fragment_freelist, + mca_coll_han_component.han_fragment_size, + (size_t)rsize, &t->inter_frag); + } else { + /* Too large for freelist — use shared scratch buffer */ + tmp_buf = han_scratch_alloc(&t->han_module->scratch_buf[0], + &t->han_module->scratch_buf_size[0], + (size_t)rsize); + if (NULL == tmp_buf) return OMPI_ERR_OUT_OF_RESOURCE; + } + } else { + tmp_buf = (char *) malloc(rsize); + if (NULL == tmp_buf) return OMPI_ERR_OUT_OF_RESOURCE; + } tmp_rbuf = tmp_buf - rgap; + if (MPI_IN_PLACE == t->sbuf) { tmp_send = ((char*)t->rbuf) + (ptrdiff_t)t->w_rank * (ptrdiff_t)t->rcount * rext; ompi_datatype_copy_content_same_ddt(t->rdtype, t->rcount, tmp_rbuf, tmp_send); @@ -180,6 +322,12 @@ int mca_coll_han_allgather_lg_task(void *task_args) t->sbuf = tmp_rbuf; t->sbuf_inter_free = tmp_buf; + /* When using persist gather buffer, don't free it in uag_task */ + if (mca_coll_han_component.han_use_persist_buffers + && t->inter_frag == NULL && t->han_module != NULL + && tmp_buf == t->han_module->scratch_buf[0]) { + t->sbuf_inter_free = NULL; + } /* Create uag (upper level all-gather) task */ mca_coll_task_t *uag = t->cur_task; @@ -207,13 +355,30 @@ int mca_coll_han_allgather_uag_task(void *task_args) OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, "[%d]: HAN Allgather is bycore: ", t->w_rank)); reorder_rbuf = (char *) t->rbuf; + + /* When persist buffers gathered directly into rbuf, use in-place */ + if (mca_coll_han_component.han_use_persist_buffers + && t->sbuf_inter_free == NULL) { + t->up_comm->c_coll->coll_allgather(MPI_IN_PLACE, + t->scount * low_size, t->sdtype, + reorder_rbuf, t->rcount * low_size, t->rdtype, + t->up_comm, t->up_comm->c_coll->coll_allgather_module); + goto allgather_done; + } } else { ptrdiff_t rsize, rgap = 0; rsize = opal_datatype_span(&t->rdtype->super, (int64_t) t->rcount * low_size * up_size, &rgap); - reorder_buf = (char *) malloc(rsize); + if (mca_coll_han_component.han_use_persist_buffers && t->han_module != NULL) { + reorder_buf = han_scratch_alloc(&t->han_module->scratch_buf[1], + &t->han_module->scratch_buf_size[1], + (size_t)rsize); + if (NULL == reorder_buf) return OMPI_ERR_OUT_OF_RESOURCE; + } else { + reorder_buf = (char *) malloc(rsize); + } reorder_rbuf = reorder_buf - rgap; } @@ -223,7 +388,9 @@ int mca_coll_han_allgather_uag_task(void *task_args) t->up_comm, t->up_comm->c_coll->coll_allgather_module); if (t->sbuf_inter_free != NULL) { - free(t->sbuf_inter_free); + han_free_frag(&t->han_module->fragment_freelist, + t->inter_frag, t->sbuf_inter_free); + t->inter_frag = NULL; t->sbuf_inter_free = NULL; } @@ -253,12 +420,15 @@ int mca_coll_han_allgather_uag_task(void *task_args) (ptrdiff_t) t->rcount); } } - free(reorder_buf); + if (!mca_coll_han_component.han_use_persist_buffers) { + free(reorder_buf); + } reorder_buf = NULL; } } - +allgather_done: + ; /* empty statement required after label before declaration */ /* Create lb (low level broadcast) task */ mca_coll_task_t *lb = t->cur_task; /* Init and issue lb task */ @@ -288,6 +458,340 @@ int mca_coll_han_allgather_lb_task(void *task_args) } +/** + * Reorder a fragment from gathered layout into rbuf at the correct offset. + * Used by the pipeline and single-fragment paths. + */ +static inline void +han_reorder_frag(char *rbuf, const char *src_buf, + struct ompi_datatype_t *rdtype, ptrdiff_t rextent, + size_t frag_count, size_t frag_offset, size_t rcount, + int up_size, int low_size, const int *topo) +{ + for (int i = 0; i < up_size; i++) { + for (int j = 0; j < low_size; j++) { + int global_idx = i * low_size + j; + int dest_rank = topo[global_idx * 2 + 1]; + ompi_datatype_copy_content_same_ddt(rdtype, + (ptrdiff_t)frag_count, + rbuf + rextent * ((ptrdiff_t)dest_rank * (ptrdiff_t)rcount + + (ptrdiff_t)frag_offset), + (char *)src_buf + rextent * (ptrdiff_t)global_idx + * (ptrdiff_t)frag_count); + } + } +} + +/** + * Mapbycore fast path: gather directly into rbuf, in-place allgather, + * single bcast. No temporary buffers needed. + */ +static int +han_allgather_mapbycore(const void *sbuf, size_t scount, + struct ompi_datatype_t *sdtype, + void *rbuf, size_t rcount, + struct ompi_datatype_t *rdtype, + struct ompi_communicator_t *up_comm, + struct ompi_communicator_t *low_comm, + int w_rank, int low_rank, int up_rank, + int low_size, int up_size, int root_low_rank) +{ + ptrdiff_t rlb, rext; + ompi_datatype_get_extent(rdtype, &rlb, &rext); + size_t total_count = rcount * low_size; + char *my_slot = (char *)rbuf + (ptrdiff_t)up_rank * (ptrdiff_t)total_count * rext; + + if (MPI_IN_PLACE == sbuf) { + if (low_rank == root_low_rank) { + low_comm->c_coll->coll_gather(MPI_IN_PLACE, scount, sdtype, + my_slot, rcount, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } else { + char *my_data = ((char*)rbuf) + (ptrdiff_t)w_rank * (ptrdiff_t)rcount * rext; + low_comm->c_coll->coll_gather(my_data, rcount, rdtype, + NULL, rcount, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } + } else { + low_comm->c_coll->coll_gather((char *)sbuf, scount, sdtype, + my_slot, rcount, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } + + if (low_rank == root_low_rank) { + up_comm->c_coll->coll_allgather(MPI_IN_PLACE, total_count, rdtype, + rbuf, total_count, rdtype, + up_comm, up_comm->c_coll->coll_allgather_module); + } + + low_comm->c_coll->coll_bcast(rbuf, rcount*low_size*up_size, rdtype, + root_low_rank, low_comm, + low_comm->c_coll->coll_bcast_module); + return OMPI_SUCCESS; +} + +/** + * Single-fragment freelist path: gather into reorder_buf, in-place + * allgather, reorder into rbuf, bcast. + */ +static int +han_allgather_single_frag(const void *sbuf, size_t scount, + struct ompi_datatype_t *sdtype, + void *rbuf, size_t rcount, + struct ompi_datatype_t *rdtype, + mca_coll_han_module_t *han_module, + struct ompi_communicator_t *up_comm, + struct ompi_communicator_t *low_comm, + struct ompi_communicator_t *comm, + int w_rank, int low_rank, int up_rank, + int low_size, int up_size, int root_low_rank, + size_t frag_size, const int *topo) +{ + ptrdiff_t rlb, rext, rextent; + ompi_datatype_get_extent(rdtype, &rlb, &rext); + ompi_datatype_type_extent(rdtype, &rextent); + size_t total_count = rcount * low_size; + char *reorder_buf = NULL; + char *reorder_buf_start = NULL; + char *my_slot = NULL; + opal_free_list_item_t *fl_item = NULL; + + if (low_rank == root_low_rank) { + ptrdiff_t rsize, rgap = 0; + rsize = opal_datatype_span(&rdtype->super, + (int64_t)rcount * low_size * up_size, &rgap); + + reorder_buf = han_alloc_frag(&han_module->fragment_freelist, + frag_size, (size_t)rsize, &fl_item); + if (NULL == reorder_buf) { + return OMPI_ERR_OUT_OF_RESOURCE; + } + reorder_buf_start = reorder_buf - rgap; + my_slot = reorder_buf_start + + rextent * (ptrdiff_t)up_rank * (ptrdiff_t)total_count; + if (MPI_IN_PLACE == sbuf) { + char *my_data = ((char*)rbuf) + (ptrdiff_t)w_rank * (ptrdiff_t)rcount * rext; + ompi_datatype_copy_content_same_ddt(rdtype, rcount, my_slot, my_data); + } + } + + if (MPI_IN_PLACE == sbuf) { + if (low_rank == root_low_rank) { + low_comm->c_coll->coll_gather(MPI_IN_PLACE, scount, sdtype, + my_slot, rcount, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } else { + char *my_data = ((char*)rbuf) + (ptrdiff_t)w_rank * (ptrdiff_t)rcount * rext; + low_comm->c_coll->coll_gather(my_data, rcount, rdtype, + NULL, rcount, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } + } else { + low_comm->c_coll->coll_gather((char *)sbuf, scount, sdtype, + my_slot, rcount, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } + + if (low_rank == root_low_rank) { + up_comm->c_coll->coll_allgather(MPI_IN_PLACE, total_count, rdtype, + reorder_buf_start, total_count, rdtype, + up_comm, up_comm->c_coll->coll_allgather_module); + + ompi_coll_han_reorder_gather(reorder_buf_start, rbuf, rcount, rdtype, comm, topo); + han_free_frag(&han_module->fragment_freelist, fl_item, reorder_buf); + } + + low_comm->c_coll->coll_bcast(rbuf, rcount * low_size * up_size, rdtype, + root_low_rank, low_comm, low_comm->c_coll->coll_bcast_module); + return OMPI_SUCCESS; +} + +/** + * Pipeline path: double-buffered igather+ibcast with blocking low_comm + * gather as the sync point. + */ +static int +han_allgather_pipeline(const void *sbuf, size_t scount, + struct ompi_datatype_t *sdtype, + void *rbuf, size_t rcount, + struct ompi_datatype_t *rdtype, + mca_coll_han_module_t *han_module, + struct ompi_communicator_t *up_comm, + struct ompi_communicator_t *low_comm, + int w_rank, int low_rank, + int low_size, int up_size, int root_low_rank, + size_t frag_size, size_t frag_count, size_t num_frags, + const int *topo) +{ + ptrdiff_t rlb, rext, rextent; + int root_up_rank = 0; + ompi_datatype_get_extent(rdtype, &rlb, &rext); + ompi_datatype_type_extent(rdtype, &rextent); + + /* Pipeline requires non-blocking collectives on up_comm */ + if (NULL == up_comm->c_coll->coll_ibcast + || NULL == up_comm->c_coll->coll_igather) { + return OMPI_ERR_NOT_SUPPORTED; + } + + /* Allocate double-buffered reorder buffers */ + char *frag_reorder[2] = {NULL, NULL}; + opal_free_list_item_t *frag_reorder_item[2] = {NULL, NULL}; + int frag_reorder_src[2] = {HAN_ALLOC_MALLOC, HAN_ALLOC_MALLOC}; + if (low_rank == root_low_rank) { + size_t frag_reorder_size = (size_t)frag_count * low_size * up_size * rextent; + size_t large_frag_size = mca_coll_han_component.han_large_fragment_size; + for (int b = 0; b < 2; b++) { + frag_reorder[b] = han_alloc_tiered( + &han_module->large_fragment_freelist, large_frag_size, + &han_module->fragment_freelist, frag_size, + frag_reorder_size, &frag_reorder_item[b], + &frag_reorder_src[b]); + if (NULL == frag_reorder[b]) { + for (int i = 0; i < b; i++) { + han_free_tiered(&han_module->large_fragment_freelist, + &han_module->fragment_freelist, + frag_reorder_item[i], frag_reorder[i], + frag_reorder_src[i]); + } + return OMPI_ERR_OUT_OF_RESOURCE; + } + } + } + + opal_free_list_item_t *inter_frag_item = NULL; + char *gather_buf = NULL; + ompi_request_t *igather_req = NULL; + ompi_request_t *ibcast_req = NULL; + size_t prev_frag_count = 0; + size_t prev_frag_offset = 0; + int cur_buf = 0; + + for (size_t frag = 0; frag < num_frags; frag++) { + size_t frag_offset = frag * frag_count; + size_t this_count = frag_count; + if (frag_offset + this_count > rcount) + this_count = rcount - frag_offset; + + int prev_buf = 1 - cur_buf; + + if (frag > 0 && low_rank == root_low_rank) { + ompi_request_wait(&igather_req, MPI_STATUS_IGNORE); + igather_req = NULL; + + han_free_frag(&han_module->fragment_freelist, + inter_frag_item, gather_buf); + inter_frag_item = NULL; + gather_buf = NULL; + + size_t prev_ag = prev_frag_count * low_size * up_size; + up_comm->c_coll->coll_ibcast(frag_reorder[prev_buf], prev_ag, rdtype, + root_up_rank, up_comm, &ibcast_req, + up_comm->c_coll->coll_ibcast_module); + } + + if (low_rank == root_low_rank) { + gather_buf = han_alloc_frag(&han_module->fragment_freelist, + frag_size, + (size_t)this_count * low_size * rextent, + &inter_frag_item); + if (NULL == gather_buf) { + /* Clean up any outstanding ibcast and double buffers */ + if (ibcast_req != NULL) { + ompi_request_wait(&ibcast_req, MPI_STATUS_IGNORE); + } + for (int b = 0; b < 2; b++) { + han_free_tiered(&han_module->large_fragment_freelist, + &han_module->fragment_freelist, + frag_reorder_item[b], frag_reorder[b], + frag_reorder_src[b]); + } + return OMPI_ERR_OUT_OF_RESOURCE; + } + if (MPI_IN_PLACE == sbuf) { + char *my_data = ((char*)rbuf) + + ((ptrdiff_t)w_rank * (ptrdiff_t)rcount + (ptrdiff_t)frag_offset) * rext; + ompi_datatype_copy_content_same_ddt(rdtype, this_count, + gather_buf, my_data); + } + } + + /* ALL ranks: blocking low_comm gather — SYNC POINT */ + if (MPI_IN_PLACE == sbuf) { + if (low_rank == root_low_rank) { + low_comm->c_coll->coll_gather(MPI_IN_PLACE, this_count, rdtype, + gather_buf, this_count, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } else { + char *my_data = ((char*)rbuf) + + ((ptrdiff_t)w_rank * (ptrdiff_t)rcount + (ptrdiff_t)frag_offset) * rext; + low_comm->c_coll->coll_gather(my_data, this_count, rdtype, + NULL, this_count, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } + } else { + low_comm->c_coll->coll_gather( + (char *)sbuf + (ptrdiff_t)frag_offset * rext, this_count, sdtype, + gather_buf, this_count, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } + + if (low_rank == root_low_rank) { + size_t ag_count = this_count * low_size; + up_comm->c_coll->coll_igather(gather_buf, ag_count, rdtype, + frag_reorder[cur_buf], ag_count, rdtype, root_up_rank, + up_comm, &igather_req, up_comm->c_coll->coll_igather_module); + } + + if (frag > 0 && low_rank == root_low_rank) { + ompi_request_wait(&ibcast_req, MPI_STATUS_IGNORE); + ibcast_req = NULL; + + han_reorder_frag(rbuf, frag_reorder[prev_buf], rdtype, rextent, + prev_frag_count, prev_frag_offset, rcount, + up_size, low_size, topo); + } + + prev_frag_count = this_count; + prev_frag_offset = frag_offset; + cur_buf = 1 - cur_buf; + } + + /* Epilogue: last frag */ + if (low_rank == root_low_rank) { + int last_buf = 1 - cur_buf; + + if (igather_req != NULL) { + ompi_request_wait(&igather_req, MPI_STATUS_IGNORE); + } + + han_free_frag(&han_module->fragment_freelist, + inter_frag_item, gather_buf); + + size_t last_ag = prev_frag_count * low_size * up_size; + up_comm->c_coll->coll_ibcast(frag_reorder[last_buf], last_ag, rdtype, + root_up_rank, up_comm, &ibcast_req, + up_comm->c_coll->coll_ibcast_module); + ompi_request_wait(&ibcast_req, MPI_STATUS_IGNORE); + + han_reorder_frag(rbuf, frag_reorder[last_buf], rdtype, rextent, + prev_frag_count, prev_frag_offset, rcount, + up_size, low_size, topo); + + for (int b = 0; b < 2; b++) { + han_free_tiered(&han_module->large_fragment_freelist, + &han_module->fragment_freelist, + frag_reorder_item[b], frag_reorder[b], + frag_reorder_src[b]); + } + } + + low_comm->c_coll->coll_bcast(rbuf, rcount * low_size * up_size, rdtype, + root_low_rank, low_comm, + low_comm->c_coll->coll_bcast_module); + return OMPI_SUCCESS; +} + /** * Short implementation of allgather that only does hierarchical * communications without tasks. @@ -300,6 +804,7 @@ mca_coll_han_allgather_intra_simple(const void *sbuf, size_t scount, struct ompi_communicator_t *comm, mca_coll_base_module_t *module){ + /* create the subcommunicators */ mca_coll_han_module_t *han_module = (mca_coll_han_module_t *)module; @@ -336,101 +841,172 @@ mca_coll_han_allgather_intra_simple(const void *sbuf, size_t scount, int up_size = ompi_comm_size(up_comm); int root_low_rank = 0; // node leader will be 0 on each rank - /* allocate the intermediary buffer - * to gather on leaders on the low sub communicator */ - ptrdiff_t rlb, rext; - ompi_datatype_get_extent (rdtype, &rlb, &rext); - char *tmp_buf = NULL; - char *tmp_buf_start = NULL; - char *tmp_send = NULL; - if (MPI_IN_PLACE == sbuf) { - scount = rcount; - sdtype = rdtype; - } - if (low_rank == root_low_rank) { - ptrdiff_t rsize, rgap = 0; - /* Compute the size to receive all the local data, including datatypes empty gaps */ - rsize = opal_datatype_span(&rdtype->super, (int64_t)rcount * low_size, &rgap); - /* intermediary buffer on node leaders to gather on low comm */ - tmp_buf = (char *) malloc(rsize); - tmp_buf_start = tmp_buf - rgap; + /* Check if freelist-based optimization is enabled. + * Only use optimized path when the gathered data per node is large + * enough that pipeline/mapbycore benefit outweighs setup overhead. + * With MIN_PIPELINE_FRAGS=4, the threshold ensures at least 4 + * fragments worth of data, so the pipeline has enough stages to + * overlap igather and ibcast effectively. */ + size_t frag_size = mca_coll_han_component.han_fragment_size; + ptrdiff_t rextent_check; + ompi_datatype_type_extent(rdtype, &rextent_check); + size_t gathered_size = (size_t)rcount * (size_t)low_size * (size_t)rextent_check; + if (frag_size == 0 || gathered_size <= (HAN_MIN_PIPELINE_FRAGS * frag_size)) { + /* + * Simple path: gather to tmp_buf, allgather between leaders, + * reorder if needed, bcast to all ranks. + */ + ptrdiff_t rlb, rext; + ompi_datatype_get_extent (rdtype, &rlb, &rext); + char *tmp_buf = NULL; + char *tmp_buf_start = NULL; + char *tmp_send = NULL; if (MPI_IN_PLACE == sbuf) { - tmp_send = ((char*)rbuf) + (ptrdiff_t)w_rank * (ptrdiff_t)rcount * rext; - ompi_datatype_copy_content_same_ddt(rdtype, rcount, tmp_buf_start, tmp_send); + scount = rcount; + sdtype = rdtype; } - } - - /* 1. low gather on node leaders into tmp_buf */ - if (MPI_IN_PLACE == sbuf) { if (low_rank == root_low_rank) { - low_comm->c_coll->coll_gather(MPI_IN_PLACE, scount, sdtype, - tmp_buf_start, rcount, rdtype, root_low_rank, - low_comm, low_comm->c_coll->coll_gather_module); + ptrdiff_t rsize, rgap = 0; + /* Compute the size to receive all the local data, including datatypes empty gaps */ + rsize = opal_datatype_span(&rdtype->super, (int64_t)rcount * low_size, &rgap); + /* intermediary buffer on node leaders to gather on low comm */ + tmp_buf = han_scratch_or_malloc(&han_module->scratch_buf[1], + &han_module->scratch_buf_size[1], + rsize, mca_coll_han_component.han_use_persist_buffers); + if (NULL == tmp_buf) { + return OMPI_ERR_OUT_OF_RESOURCE; + } + tmp_buf_start = tmp_buf - rgap; + if (MPI_IN_PLACE == sbuf) { + tmp_send = ((char*)rbuf) + (ptrdiff_t)w_rank * (ptrdiff_t)rcount * rext; + ompi_datatype_copy_content_same_ddt(rdtype, rcount, tmp_buf_start, tmp_send); + } + } + + /* 1. low gather on node leaders into tmp_buf */ + if (MPI_IN_PLACE == sbuf) { + if (low_rank == root_low_rank) { + low_comm->c_coll->coll_gather(MPI_IN_PLACE, scount, sdtype, + tmp_buf_start, rcount, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } + else { + tmp_send = ((char*)rbuf) + (ptrdiff_t)w_rank * (ptrdiff_t)rcount * rext; + low_comm->c_coll->coll_gather(tmp_send, rcount, rdtype, + NULL, rcount, rdtype, root_low_rank, + low_comm, low_comm->c_coll->coll_gather_module); + } } else { - tmp_send = ((char*)rbuf) + (ptrdiff_t)w_rank * (ptrdiff_t)rcount * rext; - low_comm->c_coll->coll_gather(tmp_send, rcount, rdtype, - NULL, rcount, rdtype, root_low_rank, + low_comm->c_coll->coll_gather((char *)sbuf, scount, sdtype, + tmp_buf_start, rcount, rdtype, root_low_rank, low_comm, low_comm->c_coll->coll_gather_module); } - } - else { - low_comm->c_coll->coll_gather((char *)sbuf, scount, sdtype, - tmp_buf_start, rcount, rdtype, root_low_rank, - low_comm, low_comm->c_coll->coll_gather_module); - } - /* 2. allgather between node leaders, from tmp_buf to reorder_buf */ - if (low_rank == root_low_rank) { - /* allocate buffer to store unordered result on node leaders - * if the processes are mapped-by core, no need to reorder: - * distribution of ranks on core first and node next, - * in a increasing order for both patterns. - */ - char *reorder_buf = NULL; - char *reorder_buf_start = NULL; - if (han_module->is_mapbycore) { - reorder_buf_start = rbuf; - } else { - if (0 == low_rank && 0 == up_rank) { // first rank displays message - OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, - "[%d]: Future Allgather needs reordering: ", up_rank)); + /* 2. allgather between node leaders, from tmp_buf to reorder_buf */ + if (low_rank == root_low_rank) { + /* allocate buffer to store unordered result on node leaders + * if the processes are mapped-by core, no need to reorder: + * distribution of ranks on core first and node next, + * in a increasing order for both patterns. + */ + char *reorder_buf = NULL; + char *reorder_buf_start = NULL; + if (han_module->is_mapbycore) { + reorder_buf_start = rbuf; + } else { + if (0 == low_rank && 0 == up_rank) { // first rank displays message + OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, + "[%d]: Future Allgather needs reordering: ", up_rank)); + } + ptrdiff_t rsize, rgap = 0; + rsize = opal_datatype_span(&rdtype->super, (int64_t)rcount * low_size * up_size, &rgap); + reorder_buf = han_scratch_or_malloc(&han_module->scratch_buf[0], + &han_module->scratch_buf_size[0], + rsize, mca_coll_han_component.han_use_persist_buffers); + reorder_buf_start = reorder_buf - rgap; } - ptrdiff_t rsize, rgap = 0; - rsize = opal_datatype_span(&rdtype->super, (int64_t)rcount * low_size * up_size, &rgap); - reorder_buf = (char *) malloc(rsize); - reorder_buf_start = reorder_buf - rgap; - } - /* 2a. inter node allgather */ - up_comm->c_coll->coll_allgather(tmp_buf_start, scount*low_size, sdtype, - reorder_buf_start, rcount*low_size, rdtype, - up_comm, up_comm->c_coll->coll_allgather_module); + /* 2a. inter node allgather */ + up_comm->c_coll->coll_allgather(tmp_buf_start, scount*low_size, sdtype, + reorder_buf_start, rcount*low_size, rdtype, + up_comm, up_comm->c_coll->coll_allgather_module); + + if (tmp_buf != NULL && !mca_coll_han_component.han_use_persist_buffers) { + free(tmp_buf); + tmp_buf = NULL; + tmp_buf_start = NULL; + } + + /* 2b. reorder the node leader's into rbuf. + * if ranks are not mapped in topological order, data needs to be reordered + * (see reorder_gather) + */ + if (!han_module->is_mapbycore) { + ompi_coll_han_reorder_gather(reorder_buf_start, + rbuf, rcount, rdtype, + comm, topo); + if (!mca_coll_han_component.han_use_persist_buffers) { + free(reorder_buf); + } + reorder_buf = NULL; + } - if (tmp_buf != NULL) { - free(tmp_buf); - tmp_buf = NULL; - tmp_buf_start = NULL; } - /* 2b. reorder the node leader's into rbuf. - * if ranks are not mapped in topological order, data needs to be reordered - * (see reorder_gather) + /* 3. up broadcast: leaders broadcast on their nodes */ + low_comm->c_coll->coll_bcast(rbuf, rcount*low_size*up_size, rdtype, + root_low_rank, low_comm, + low_comm->c_coll->coll_bcast_module); + + } else { + /* + * Freelist path: uses pre-allocated buffers and mapbycore/pipeline + * optimizations for large messages. */ - if (!han_module->is_mapbycore) { - ompi_coll_han_reorder_gather(reorder_buf_start, - rbuf, rcount, rdtype, - comm, topo); - free(reorder_buf); - reorder_buf = NULL; - } + ptrdiff_t rextent; + size_t frag_count; + size_t num_frags; + size_t max_elems; - } + ompi_datatype_type_extent(rdtype, &rextent); - /* 3. up broadcast: leaders broadcast on their nodes */ - low_comm->c_coll->coll_bcast(rbuf, rcount*low_size*up_size, rdtype, - root_low_rank, low_comm, - low_comm->c_coll->coll_bcast_module); + if (MPI_IN_PLACE == sbuf) { + scount = rcount; + sdtype = rdtype; + } + + /* Compute per-fragment element count */ + frag_count = rcount; + if (frag_size > 0 && rextent > 0) { + max_elems = frag_size / ((size_t)low_size * (size_t)rextent); + if (max_elems < 1) max_elems = 1; + if (max_elems < rcount) frag_count = max_elems; + } + num_frags = (rcount + frag_count - 1) / frag_count; + if (han_module->is_mapbycore) { + return han_allgather_mapbycore(sbuf, scount, sdtype, rbuf, rcount, + rdtype, up_comm, low_comm, w_rank, low_rank, up_rank, + low_size, up_size, root_low_rank); + } else if (num_frags == 1) { + return han_allgather_single_frag(sbuf, scount, sdtype, rbuf, rcount, + rdtype, han_module, up_comm, low_comm, comm, + w_rank, low_rank, up_rank, low_size, up_size, + root_low_rank, frag_size, topo); + } else { + int rc = han_allgather_pipeline(sbuf, scount, sdtype, rbuf, rcount, + rdtype, han_module, up_comm, low_comm, + w_rank, low_rank, low_size, up_size, + root_low_rank, frag_size, frag_count, num_frags, topo); + if (OMPI_ERR_NOT_SUPPORTED == rc) { + return han_allgather_single_frag(sbuf, scount, sdtype, rbuf, rcount, + rdtype, han_module, up_comm, low_comm, comm, + w_rank, low_rank, up_rank, low_size, up_size, + root_low_rank, frag_size, topo); + } + return rc; + } + } return OMPI_SUCCESS; -} +} \ No newline at end of file diff --git a/ompi/mca/coll/han/coll_han_alltoall.c b/ompi/mca/coll/han/coll_han_alltoall.c index 489dd41e35a..af4e1e7ac68 100644 --- a/ompi/mca/coll/han/coll_han_alltoall.c +++ b/ompi/mca/coll/han/coll_han_alltoall.c @@ -60,6 +60,62 @@ static inline int ring_partner(int rank, int round, int comm_size) { return ring_partner_no_skip(rank, round+1, comm_size); } + +/** + * Set up or reuse cached SMSC arrays for alltoall. + * Returns 1 if cache was valid (allgather + map can be skipped), + * 0 on cache miss (arrays allocated, caller must populate), + * or -1 on allocation failure. + */ +static int alltoall_cache_setup( + mca_coll_han_module_t *han_module, + const void *sbuf, size_t scount, int low_size, + char ***low_bufs_out, void ***map_ctx_out, void ***gather_buf_out, + int *send_needs_bounce_out, int *ii_push_data_out) +{ + struct han_alltoall_cache *c = &han_module->a2a_cache; + const int nptrs_gather = 3; + + if (c->cached_sbuf == sbuf + && c->cached_scount == scount + && c->cached_low_size == low_size + && c->low_bufs != NULL) { + *low_bufs_out = c->low_bufs; + *map_ctx_out = c->map_ctx; + *gather_buf_out = c->gather_buf; + *send_needs_bounce_out = c->cached_send_needs_bounce; + *ii_push_data_out = c->cached_ii_push_data; + return 1; /* cache valid */ + } + + /* Invalidate old cache — unmap old SMSC regions */ + if (c->map_ctx) { + for (int i = 0; i < c->cached_low_size; i++) { + if (c->map_ctx[i]) + mca_smsc->unmap_peer_region(c->map_ctx[i]); + } + } + /* Allocate/reuse persistent arrays */ + if (c->cached_low_size < low_size) { + free(c->low_bufs); + free(c->map_ctx); + free(c->gather_buf); + c->cached_low_size = 0; + c->low_bufs = malloc(low_size * sizeof(char*)); + c->map_ctx = malloc(low_size * sizeof(void*)); + c->gather_buf = calloc(low_size * nptrs_gather, sizeof(void*)); + if (NULL == c->low_bufs || NULL == c->map_ctx || NULL == c->gather_buf) { + return -1; /* allocation failure */ + } + } + *low_bufs_out = c->low_bufs; + *map_ctx_out = c->map_ctx; + *gather_buf_out = c->gather_buf; + memset(c->map_ctx, 0, low_size * sizeof(void*)); + memset(c->gather_buf, 0, low_size * nptrs_gather * sizeof(void*)); + return 0; /* cache miss */ +} + int mca_coll_han_alltoall_using_smsc( const void *sbuf, size_t scount, struct ompi_datatype_t *sdtype, @@ -218,14 +274,31 @@ int mca_coll_han_alltoall_using_smsc( int64_t send_bytes_per_fan = low_size * packed_size; inter_send_reqs = malloc(sizeof(*inter_send_reqs) * fanout); inter_recv_reqs = malloc(sizeof(*inter_recv_reqs) * up_size ); - char **low_bufs = malloc(low_size * sizeof(*low_bufs)); - void **sbuf_map_ctx = malloc(low_size * sizeof(&sbuf_map_ctx)); - opal_free_list_item_t *send_fl_item = NULL; + /* Check if cached SMSC mappings are still valid */ + int a2a_cache_valid; + char **low_bufs = NULL; + void **sbuf_map_ctx = NULL; + opal_free_list_item_t *send_fl_item = NULL; const int nptrs_gather = 3; - void **gather_buf_out = calloc(low_size*nptrs_gather, sizeof(void*)); + void **gather_buf_out = NULL; int send_bounce_status = BOUNCE_NOT_INITIALIZED; + if (mca_coll_han_component.han_use_persist_buffers) { + int cache_rc = alltoall_cache_setup( + han_module, sbuf, scount, low_size, + &low_bufs, &sbuf_map_ctx, &gather_buf_out, + &send_needs_bounce, &ii_push_data); + if (cache_rc < 0) { rc = OMPI_ERR_OUT_OF_RESOURCE; goto cleanup; } + a2a_cache_valid = (cache_rc == 1); + } else { + /* Original upstream path — fresh allocations per call */ + a2a_cache_valid = 0; + low_bufs = malloc(low_size * sizeof(*low_bufs)); + sbuf_map_ctx = malloc(low_size * sizeof(*sbuf_map_ctx)); + gather_buf_out = calloc(low_size * nptrs_gather, sizeof(void*)); + } + do { start_allgather: if ( 0 == send_needs_bounce ) { @@ -233,70 +306,96 @@ int mca_coll_han_alltoall_using_smsc( send_bounce_status = BOUNCE_IS_FROM_RBUF; } else { if (send_bounce_status == BOUNCE_NOT_INITIALIZED || send_bounce_status == BOUNCE_IS_FROM_RBUF) { - if (send_bytes_per_fan * fanout < mca_coll_han_component.han_packbuf_bytes) { - send_fl_item = opal_free_list_get(&mca_coll_han_component.pack_buffers); - if (send_fl_item) { - send_bounce_status = BOUNCE_IS_FROM_FREELIST; - send_bounce = send_fl_item->ptr; + if (mca_coll_han_component.han_use_persist_buffers) { + /* Persistent bounce: realloc-to-HWM avoids munmap on free + * which would invalidate NIC memory registration cache entries. */ + size_t needed = send_bytes_per_fan * fanout; + if (han_module->a2a_cache.bounce_size < needed) { + char *p = realloc(han_module->a2a_cache.bounce, needed); + if (NULL == p) { rc = OMPI_ERR_OUT_OF_RESOURCE; goto cleanup; } + han_module->a2a_cache.bounce = p; + han_module->a2a_cache.bounce_size = needed; } - } - if (!send_fl_item) { - send_bounce = malloc(send_bytes_per_fan * fanout); + send_bounce = han_module->a2a_cache.bounce; send_bounce_status = BOUNCE_IS_FROM_MALLOC; + } else { + if (send_bytes_per_fan * fanout < mca_coll_han_component.han_packbuf_bytes) { + send_fl_item = opal_free_list_get(&mca_coll_han_component.pack_buffers); + if (send_fl_item) { + send_bounce_status = BOUNCE_IS_FROM_FREELIST; + send_bounce = send_fl_item->ptr; + } + } + if (!send_fl_item) { + send_bounce = malloc(send_bytes_per_fan * fanout); + send_bounce_status = BOUNCE_IS_FROM_MALLOC; + } } } } - if (ii_push_data) { - /* all ranks will push to the other ranks' bounce buffer */ - gather_buf_in[0] = send_bounce; - } else { - /* all ranks will pull from the other ranks' sbuf */ - gather_buf_in[0] = (void*)sbuf; - } - gather_buf_in[1] = (void*)(intptr_t)send_needs_bounce; - gather_buf_in[2] = (void*)(intptr_t)ii_push_data; + if (!a2a_cache_valid) { + if (ii_push_data) { + /* all ranks will push to the other ranks' bounce buffer */ + gather_buf_in[0] = send_bounce; + } else { + /* all ranks will pull from the other ranks' sbuf */ + gather_buf_in[0] = (void*)sbuf; + } + gather_buf_in[1] = (void*)(intptr_t)send_needs_bounce; + gather_buf_in[2] = (void*)(intptr_t)ii_push_data; - rc = low_comm->c_coll->coll_allgather(gather_buf_in, nptrs_gather, MPI_AINT, - gather_buf_out, nptrs_gather, MPI_AINT, low_comm, - low_comm->c_coll->coll_allgather_module); + rc = low_comm->c_coll->coll_allgather(gather_buf_in, nptrs_gather, MPI_AINT, + gather_buf_out, nptrs_gather, MPI_AINT, low_comm, + low_comm->c_coll->coll_allgather_module); - if (rc != 0) { - OPAL_OUTPUT_VERBOSE((40, mca_coll_han_component.han_output, - "Allgather failed with %d\n",rc)); - goto cleanup; - } + if (rc != 0) { + OPAL_OUTPUT_VERBOSE((40, mca_coll_han_component.han_output, + "Allgather failed with %d\n",rc)); + goto cleanup; + } - for (int jother=0; jother 1 || ii_push_data; - for (int jother=0; jothermap_peer_region( - smsc_ep, - MCA_RCACHE_FLAGS_PERSIST, - low_bufs[jother], - sextent*w_size*scount, - (void**) &low_bufs[jother] ); + if (!a2a_cache_valid) { + for (int jother=0; jothermap_peer_region( + smsc_ep, + MCA_RCACHE_FLAGS_PERSIST, + low_bufs[jother], + sextent*w_size*scount, + (void**) &low_bufs[jother] ); + } } - } + /* Update cache (only when persist buffers enabled) */ + if (mca_coll_han_component.han_use_persist_buffers) { + han_module->a2a_cache.cached_sbuf = sbuf; + han_module->a2a_cache.cached_scount = scount; + han_module->a2a_cache.cached_low_size = low_size; + han_module->a2a_cache.cached_send_needs_bounce = send_needs_bounce; + han_module->a2a_cache.cached_ii_push_data = ii_push_data; + } + } /* !a2a_cache_valid */ for (int jslot=0; jslot < fanout; jslot++) { inter_send_reqs[jslot] = MPI_REQUEST_NULL; @@ -305,12 +404,25 @@ int mca_coll_han_alltoall_using_smsc( /* pre-post all our receives. We will be ready to receive all data regardless of fan-out. (This is not an in-place algorithm)*/ + size_t recv_chunk_bytes = rextent * rcount * low_size; + size_t recv_total = recv_chunk_bytes * up_size; + if (mca_coll_han_component.han_use_persist_buffers) { + /* Use persistent recv buffer to keep MR addresses stable */ + if (han_module->a2a_cache.recv_buf_size < recv_total) { + char *p = realloc(han_module->a2a_cache.recv_buf, recv_total); + if (NULL == p) { rc = OMPI_ERR_OUT_OF_RESOURCE; goto cleanup; } + han_module->a2a_cache.recv_buf = p; + han_module->a2a_cache.recv_buf_size = recv_total; + } + } + int inter_recv_count = 0; for (int jround=0; jrounda2a_cache.recv_buf + recv_chunk_bytes * jround + : ((char*)rbuf) + rextent*rcount*first_remote_wrank; MCA_PML_CALL(irecv (recv_chunk, rcount*low_size, rdtype, first_remote_wrank+low_rank, @@ -415,6 +527,17 @@ int mca_coll_han_alltoall_using_smsc( /* wait for all irecv to complete */ ompi_request_wait_all(inter_recv_count, inter_recv_reqs, MPI_STATUS_IGNORE); + /* Copy from persistent recv buffer to application rbuf */ + if (mca_coll_han_component.han_use_persist_buffers) { + for (int jround=0; jrounda2a_cache.recv_buf + recv_chunk_bytes * jround, + recv_chunk_bytes); + } + } + cleanup: /* we may still have neighbors reading directly from our buffer, so we must ensure it is not modified */ @@ -423,22 +546,29 @@ int mca_coll_han_alltoall_using_smsc( low_comm->c_coll->coll_barrier(low_comm, low_comm->c_coll->coll_barrier_module); } - for (int jlow=0; jlowunmap_peer_region(sbuf_map_ctx[jlow]); + if (mca_coll_han_component.han_use_persist_buffers) { + /* SMSC mappings, bounce, and arrays are cached — do not free/unmap */ + } else { + for (int jlow=0; jlowunmap_peer_region(sbuf_map_ctx[jlow]); + } } } OBJ_DESTRUCT(&convertor); if (send_bounce_status == BOUNCE_IS_FROM_FREELIST) { opal_free_list_return(&mca_coll_han_component.pack_buffers, send_fl_item); - } else if (send_bounce_status == BOUNCE_IS_FROM_MALLOC) { + } else if (send_bounce_status == BOUNCE_IS_FROM_MALLOC + && !mca_coll_han_component.han_use_persist_buffers) { free(send_bounce); } free(inter_send_reqs); free(inter_recv_reqs); - free(sbuf_map_ctx); - free(low_bufs); - free(gather_buf_out); + if (!mca_coll_han_component.han_use_persist_buffers) { + free(sbuf_map_ctx); + free(low_bufs); + free(gather_buf_out); + } OPAL_OUTPUT_VERBOSE((40, mca_coll_han_component.han_output, "Alltoall Complete with %d\n",rc)); diff --git a/ompi/mca/coll/han/coll_han_alltoallv.c b/ompi/mca/coll/han/coll_han_alltoallv.c index b3c25014b2f..ad9c92892e9 100644 --- a/ompi/mca/coll/han/coll_han_alltoallv.c +++ b/ompi/mca/coll/han/coll_han_alltoallv.c @@ -582,6 +582,52 @@ static int alltoallv_sendrecv_w( return 0; } + +/** + * Set up persistent allocations for alltoallv. + * Grows arrays to high-water mark to avoid per-call malloc/free. + * Returns OMPI_SUCCESS on success, OMPI_ERR_OUT_OF_RESOURCE on failure. + */ +static int alltoallv_cache_setup( + struct han_alltoallv_cache *c, + size_t serialization_buf_length, int low_size) +{ + if (c->serial_buf_size < serialization_buf_length) { + free(c->serial_buf); + c->serial_buf = malloc(serialization_buf_length); + if (NULL == c->serial_buf) { + c->serial_buf_size = 0; + return OMPI_ERR_OUT_OF_RESOURCE; + } + c->serial_buf_size = serialization_buf_length; + } + if (c->low_size < low_size) { + free(c->gather_out); free(c->peers); free(c->peer_types); + free(c->send_from); free(c->recv_to); + free(c->send_counts); free(c->recv_counts); + free(c->send_types); free(c->recv_types); + c->low_size = 0; + c->gather_out = malloc(sizeof(struct gathered_data) * low_size); + c->peers = malloc(sizeof(struct peer_data) * low_size); + c->peer_types = malloc(sizeof(opal_datatype_t) * low_size); + c->send_from = malloc(sizeof(void*) * low_size); + c->recv_to = malloc(sizeof(void*) * low_size); + c->send_counts = malloc(sizeof(size_t) * low_size); + c->recv_counts = malloc(sizeof(size_t) * low_size); + c->send_types = malloc(sizeof(opal_datatype_t*) * low_size); + c->recv_types = malloc(sizeof(opal_datatype_t*) * low_size); + if (NULL == c->gather_out || NULL == c->peers || + NULL == c->peer_types || NULL == c->send_from || + NULL == c->recv_to || NULL == c->send_counts || + NULL == c->recv_counts|| NULL == c->send_types || + NULL == c->recv_types) { + return OMPI_ERR_OUT_OF_RESOURCE; + } + c->low_size = low_size; + } + return OMPI_SUCCESS; +} + static int decide_to_use_smsc_alg( int *use_smsc, const void *sbuf, @@ -769,11 +815,30 @@ int mca_coll_han_alltoallv_using_smsc( int w_size = ompi_comm_size(comm); int use_smsc; - rc = decide_to_use_smsc_alg(&use_smsc, - sbuf, scounts, sdispls, sdtype, rbuf, rcounts, rdispls, rdtype, comm); - if (rc != 0) { - opal_output_verbose(1, mca_coll_han_component.han_output, - "decide_to_use_smsc_alg failed during execution! rc=%d\n", rc); + if (sbuf == MPI_IN_PLACE) { + return han_module->previous_alltoallv(sbuf, scounts, sdispls, sdtype, rbuf, rcounts, rdispls, rdtype, + comm, han_module->previous_alltoallv_module); + } + + /* Cache the decide_to_use_smsc_alg result to avoid per-call allreduce. + * The first call runs the allreduce (all ranks participate). Every + * subsequent call reuses the cached result. This is safe because the + * decision depends on buffer types (GPU, contiguous) which don't change + * between calls, and the MCA parameter is globally consistent. */ + if (mca_coll_han_component.han_use_persist_buffers + && han_module->a2av_cache.smsc_decided) { + use_smsc = han_module->a2av_cache.use_smsc; + } else { + rc = decide_to_use_smsc_alg(&use_smsc, + sbuf, scounts, sdispls, sdtype, rbuf, rcounts, rdispls, rdtype, comm); + if (rc != 0) { + opal_output_verbose(1, mca_coll_han_component.han_output, + "decide_to_use_smsc_alg failed during execution! rc=%d\n", rc); + } + if (mca_coll_han_component.han_use_persist_buffers) { + han_module->a2av_cache.smsc_decided = true; + han_module->a2av_cache.use_smsc = use_smsc; + } } if (!use_smsc) { return han_module->previous_alltoallv(sbuf, scounts, sdispls, sdtype, rbuf, rcounts, rdispls, rdtype, @@ -790,22 +855,36 @@ int mca_coll_han_alltoallv_using_smsc( int up_rank = ompi_comm_rank(up_comm); struct gathered_data low_gather_in; - struct gathered_data *low_gather_out; + struct gathered_data *low_gather_out = NULL; low_gather_in.stype_serialized_length = ddt_pack_datatype(&sdtype->super, NULL); - uint8_t *serialization_buf; + uint8_t *serialization_buf = NULL; size_t serialization_buf_length = low_gather_in.stype_serialized_length + sizeof(struct peer_counts)*w_size; - /* allocate data */ - serialization_buf = malloc(serialization_buf_length); - low_gather_out = malloc(sizeof(*low_gather_out) * low_size); - struct peer_data *peers = malloc(sizeof(*peers) * low_size); - opal_datatype_t *peer_send_types = malloc(sizeof(*peer_send_types) * low_size); + struct peer_data *peers = NULL; + opal_datatype_t *peer_send_types = NULL; bool have_bufs_and_types = false; + if (mca_coll_han_component.han_use_persist_buffers) { + /* Persistent allocations (realloc-to-HWM) */ + rc = alltoallv_cache_setup(&han_module->a2av_cache, + serialization_buf_length, low_size); + if (rc != OMPI_SUCCESS) { goto cleanup; } + serialization_buf = han_module->a2av_cache.serial_buf; + low_gather_out = han_module->a2av_cache.gather_out; + peers = han_module->a2av_cache.peers; + peer_send_types = han_module->a2av_cache.peer_types; + } else { + /* Original upstream path — fresh allocations per call */ + serialization_buf = malloc(serialization_buf_length); + low_gather_out = malloc(sizeof(*low_gather_out) * low_size); + peers = malloc(sizeof(*peers) * low_size); + peer_send_types = malloc(sizeof(*peer_send_types) * low_size); + } + low_gather_in.serialization_buffer = serialization_buf; low_gather_in.sbuf = (void*)sbuf; // cast to discard the const @@ -831,6 +910,7 @@ int mca_coll_han_alltoallv_using_smsc( buf_packed += ddt_pack_datatype(&sdtype->super, serialization_buf + buf_packed); assert(buf_packed == serialization_buf_length); + /* Always run allgather — all ranks must participate (collective) */ rc = low_comm->c_coll->coll_allgather(&low_gather_in, sizeof(low_gather_in), MPI_BYTE, low_gather_out, sizeof(low_gather_in), MPI_BYTE, low_comm, low_comm->c_coll->coll_allgather_module); @@ -898,12 +978,21 @@ int mca_coll_han_alltoallv_using_smsc( } have_bufs_and_types = true; - send_from_addrs = malloc(sizeof(*send_from_addrs)*low_size); - recv_to_addrs = malloc(sizeof(*recv_to_addrs)*low_size); - send_counts = malloc(sizeof(*send_counts)*low_size); - recv_counts = malloc(sizeof(*recv_counts)*low_size); - send_types = malloc(sizeof(*send_types)*low_size); - recv_types = malloc(sizeof(*recv_types)*low_size); + if (mca_coll_han_component.han_use_persist_buffers) { + send_from_addrs = han_module->a2av_cache.send_from; + recv_to_addrs = han_module->a2av_cache.recv_to; + send_counts = han_module->a2av_cache.send_counts; + recv_counts = han_module->a2av_cache.recv_counts; + send_types = (opal_datatype_t **)han_module->a2av_cache.send_types; + recv_types = (opal_datatype_t **)han_module->a2av_cache.recv_types; + } else { + send_from_addrs = malloc(sizeof(*send_from_addrs)*low_size); + recv_to_addrs = malloc(sizeof(*recv_to_addrs)*low_size); + send_counts = malloc(sizeof(*send_counts)*low_size); + recv_counts = malloc(sizeof(*recv_counts)*low_size); + send_types = malloc(sizeof(*send_types)*low_size); + recv_types = malloc(sizeof(*recv_types)*low_size); + } /**** * Main exchange loop @@ -957,7 +1046,7 @@ int mca_coll_han_alltoallv_using_smsc( cleanup: low_comm->c_coll->coll_barrier(low_comm, low_comm->c_coll->coll_barrier_module); - if (send_from_addrs) { + if (send_from_addrs && !mca_coll_han_component.han_use_persist_buffers) { free(send_from_addrs); free(recv_to_addrs); free(send_counts); @@ -971,7 +1060,6 @@ int mca_coll_han_alltoallv_using_smsc( if (jlow != low_rank) { OBJ_DESTRUCT(&peer_send_types[jlow]); } - for (int jbuf=0; jbuf<2; jbuf++) { if (peers[jlow].map_ctx[jbuf]) { mca_smsc->unmap_peer_region(peers[jlow].map_ctx[jbuf]); @@ -979,10 +1067,12 @@ int mca_coll_han_alltoallv_using_smsc( } } } - free(serialization_buf); - free(low_gather_out); - free(peers); - free(peer_send_types); + if (!mca_coll_han_component.han_use_persist_buffers) { + free(serialization_buf); + free(low_gather_out); + free(peers); + free(peer_send_types); + } OPAL_OUTPUT_VERBOSE((40, mca_coll_han_component.han_output, "Alltoall Complete with %d\n",rc)); diff --git a/ompi/mca/coll/han/coll_han_component.c b/ompi/mca/coll/han/coll_han_component.c index 7ae17b9e4f8..9e7f1259feb 100644 --- a/ompi/mca/coll/han/coll_han_component.c +++ b/ompi/mca/coll/han/coll_han_component.c @@ -640,5 +640,32 @@ static int han_register(void) &(cs->max_dynamic_errors)); + cs->han_use_persist_buffers = true; + (void) mca_base_component_var_register(&mca_coll_han_component.super.collm_version, + "use_persist_buffers", + "Use persistent/freelist buffers to avoid malloc/free in collectives (0 = disabled)", + MCA_BASE_VAR_TYPE_BOOL, NULL, 0, MCA_BASE_VAR_FLAG_SETTABLE, + OPAL_INFO_LVL_6, + MCA_BASE_VAR_SCOPE_ALL, + &(cs->han_use_persist_buffers)); + + cs->han_fragment_size = 65536; + (void) mca_base_component_var_register(&mca_coll_han_component.super.collm_version, + "fragment_size", + "Size of freelist fragment buffers for collective operations (0 = disabled)", + MCA_BASE_VAR_TYPE_UNSIGNED_LONG, NULL, 0, MCA_BASE_VAR_FLAG_SETTABLE, + OPAL_INFO_LVL_6, + MCA_BASE_VAR_SCOPE_ALL, + &(cs->han_fragment_size)); + + cs->han_large_fragment_size = 1048576; + (void) mca_base_component_var_register(&mca_coll_han_component.super.collm_version, + "large_fragment_size", + "Size of large freelist buffers for pipeline reorder (0 = use small fragments or malloc)", + MCA_BASE_VAR_TYPE_UNSIGNED_LONG, NULL, 0, MCA_BASE_VAR_FLAG_SETTABLE, + OPAL_INFO_LVL_6, + MCA_BASE_VAR_SCOPE_ALL, + &(cs->han_large_fragment_size)); + return OMPI_SUCCESS; } diff --git a/ompi/mca/coll/han/coll_han_gather.c b/ompi/mca/coll/han/coll_han_gather.c index e3259267b9a..63f98776b19 100644 --- a/ompi/mca/coll/han/coll_han_gather.c +++ b/ompi/mca/coll/han/coll_han_gather.c @@ -46,7 +46,8 @@ mca_coll_han_set_gather_args(mca_coll_han_gather_args_t * args, int root_low_rank, struct ompi_communicator_t *up_comm, struct ompi_communicator_t *low_comm, - int w_rank, bool noop, bool is_mapbycore, ompi_request_t * req) + int w_rank, bool noop, bool is_mapbycore, ompi_request_t * req, + mca_coll_han_module_t *han_module) { args->cur_task = cur_task; args->sbuf = sbuf; @@ -65,6 +66,7 @@ mca_coll_han_set_gather_args(mca_coll_han_gather_args_t * args, args->noop = noop; args->is_mapbycore = is_mapbycore; args->req = req; + args->han_module = han_module; } @@ -156,7 +158,11 @@ mca_coll_han_gather_intra(const void *sbuf, size_t scount, rsize = opal_datatype_span(&rdtype->super, (int64_t)rcount * w_size, &rgap); - reorder_buf = (char *)malloc(rsize); //TODO:free + reorder_buf = han_scratch_or_malloc(&han_module->scratch_buf[0], + &han_module->scratch_buf_size[0], + (size_t)rsize, + mca_coll_han_component.han_use_persist_buffers); + if (NULL == reorder_buf) return OMPI_ERR_OUT_OF_RESOURCE; /* rgap is the size of unused space at the start of the datatype */ reorder_rbuf = reorder_buf - rgap; @@ -180,7 +186,8 @@ mca_coll_han_gather_intra(const void *sbuf, size_t scount, mca_coll_han_gather_args_t *lg_args = malloc(sizeof(mca_coll_han_gather_args_t)); mca_coll_han_set_gather_args(lg_args, lg, (char *) sbuf, NULL, scount, sdtype, reorder_rbuf, rcount, rdtype, root, root_up_rank, root_low_rank, up_comm, - low_comm, w_rank, low_rank != root_low_rank, han_module->is_mapbycore, temp_request); + low_comm, w_rank, low_rank != root_low_rank, han_module->is_mapbycore, temp_request, + han_module); /* Init lg task */ init_task(lg, mca_coll_han_gather_lg_task, (void *) (lg_args)); /* Issure lg task */ @@ -193,7 +200,9 @@ mca_coll_han_gather_intra(const void *sbuf, size_t scount, ompi_coll_han_reorder_gather(reorder_buf, rbuf, rcount, rdtype, comm, topo); - free(reorder_buf); + if (!mca_coll_han_component.han_use_persist_buffers) { + free(reorder_buf); + } } return OMPI_SUCCESS; @@ -212,15 +221,18 @@ int mca_coll_han_gather_lg_task(void *task_args) char *tmp_buf = NULL; char *tmp_rbuf = NULL; if (!t->noop) { - /* if the process is one of the node leader, allocate the intermediary - * buffer to gather on the low sub communicator */ + /* Intra-node gather buffer: persistent realloc-to-HWM or malloc */ int low_size = ompi_comm_size(t->low_comm); int low_rank = ompi_comm_rank(t->low_comm); ptrdiff_t rsize, rgap = 0; rsize = opal_datatype_span(&dtype->super, count * low_size, &rgap); - tmp_buf = (char *) malloc(rsize); + tmp_buf = han_scratch_or_malloc(&t->han_module->scratch_buf[1], + &t->han_module->scratch_buf_size[1], + (size_t)rsize, + mca_coll_han_component.han_use_persist_buffers); + if (NULL == tmp_buf) return OMPI_ERR_OUT_OF_RESOURCE; tmp_rbuf = tmp_buf - rgap; if (t->w_rank == t->root && MPI_IN_PLACE == t->sbuf) { ptrdiff_t rextent; @@ -286,9 +298,12 @@ int mca_coll_han_gather_ug_task(void *task_args) t->up_comm, t->up_comm->c_coll->coll_gather_module); - if (t->sbuf_inter_free != NULL) { - free(t->sbuf_inter_free); - t->sbuf_inter_free = NULL; + /* Free intra-node buffer when not using persist buffers */ + if (!mca_coll_han_component.han_use_persist_buffers) { + if (t->sbuf_inter_free != NULL) { + free(t->sbuf_inter_free); + t->sbuf_inter_free = NULL; + } } OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, "[%d] Han Gather: ug gather finish\n", t->w_rank)); @@ -371,23 +386,30 @@ mca_coll_han_gather_intra_simple(const void *sbuf, size_t scount, ptrdiff_t rsize = opal_datatype_span(&rdtype->super, (int64_t)rcount * w_size, &rgap); - reorder_buf = (char *)malloc(rsize); + reorder_buf = han_scratch_or_malloc(&han_module->scratch_buf[0], + &han_module->scratch_buf_size[0], + (size_t)rsize, + mca_coll_han_component.han_use_persist_buffers); + if (NULL == reorder_buf) return OMPI_ERR_OUT_OF_RESOURCE; /* rgap is the size of unused space at the start of the datatype */ reorder_buf_start = reorder_buf - rgap; } } - /* allocate the intermediary buffer - * to gather on leaders on the low sub communicator */ - char *tmp_buf = NULL; // allocated memory - char *tmp_buf_start = NULL; // start of the data + /* Intra-node gather buffer: persistent realloc-to-HWM or malloc */ + char *tmp_buf = NULL; + char *tmp_buf_start = NULL; if (low_rank == root_low_rank) { ptrdiff_t rsize, rgap = 0; rsize = opal_datatype_span(&dtype->super, count * low_size, &rgap); - tmp_buf = (char *) malloc(rsize); + tmp_buf = han_scratch_or_malloc(&han_module->scratch_buf[1], + &han_module->scratch_buf_size[1], + (size_t)rsize, + mca_coll_han_component.han_use_persist_buffers); + if (NULL == tmp_buf) return OMPI_ERR_OUT_OF_RESOURCE; tmp_buf_start = tmp_buf - rgap; } @@ -414,10 +436,10 @@ mca_coll_han_gather_intra_simple(const void *sbuf, size_t scount, up_comm, up_comm->c_coll->coll_gather_module); - if (tmp_buf != NULL) { + /* Free intra-node buffer when not using persist buffers */ + if (!mca_coll_han_component.han_use_persist_buffers) { free(tmp_buf); tmp_buf = NULL; - tmp_buf_start = NULL; } OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, "[%d] Future Gather: ug gather finish\n", w_rank)); @@ -431,7 +453,9 @@ mca_coll_han_gather_intra_simple(const void *sbuf, size_t scount, ompi_coll_han_reorder_gather(reorder_buf_start, rbuf, rcount, rdtype, comm, topo); - free(reorder_buf); + if (!mca_coll_han_component.han_use_persist_buffers) { + free(reorder_buf); + } } return OMPI_SUCCESS; @@ -452,7 +476,7 @@ ompi_coll_han_reorder_gather(const void *sbuf, void *rbuf, size_t count, struct ompi_datatype_t *dtype, struct ompi_communicator_t *comm, - int * topo) + const int * topo) { int i, topolevel = 2; // always 2 levels in topo #if OPAL_ENABLE_DEBUG diff --git a/ompi/mca/coll/han/coll_han_gatherv.c b/ompi/mca/coll/han/coll_han_gatherv.c index fd600b9d1cd..cf62870bcf0 100644 --- a/ompi/mca/coll/han/coll_han_gatherv.c +++ b/ompi/mca/coll/han/coll_han_gatherv.c @@ -219,7 +219,10 @@ int mca_coll_han_gatherv_intra(const void *sbuf, size_t scount, struct ompi_data if (need_bounce_buf) { ptrdiff_t rsize, rgap; rsize = opal_datatype_span(&rdtype->super, total_up_rcounts, &rgap); - bounce_buf = malloc(rsize); + bounce_buf = han_scratch_or_malloc( + &han_module->scratch_buf[0], + &han_module->scratch_buf_size[0], + rsize, mca_coll_han_component.han_use_persist_buffers); if (!bounce_buf) { err = OMPI_ERR_OUT_OF_RESOURCE; goto root_out; @@ -276,7 +279,7 @@ int mca_coll_han_gatherv_intra(const void *sbuf, size_t scount, struct ompi_data if (up_peer_ub) { free(up_peer_ub); } - if (bounce_buf) { + if (bounce_buf && !mca_coll_han_component.han_use_persist_buffers) { free(bounce_buf); } @@ -338,7 +341,10 @@ int mca_coll_han_gatherv_intra(const void *sbuf, size_t scount, struct ompi_data total_rsize += low_rcounts[i]; } - tmp_buf = (char *) malloc(total_rsize); /* tmp_buf is still valid if total_rsize is 0 */ + tmp_buf = han_scratch_or_malloc( + &han_module->scratch_buf[1], + &han_module->scratch_buf_size[1], + total_rsize, mca_coll_han_component.han_use_persist_buffers); if (!tmp_buf) { err = OMPI_ERR_OUT_OF_RESOURCE; goto node_leader_out; @@ -363,7 +369,7 @@ int mca_coll_han_gatherv_intra(const void *sbuf, size_t scount, struct ompi_data if (low_displs) { free(low_displs); } - if (tmp_buf) { + if (tmp_buf && !mca_coll_han_component.han_use_persist_buffers) { free(tmp_buf); } diff --git a/ompi/mca/coll/han/coll_han_module.c b/ompi/mca/coll/han/coll_han_module.c index 28338439e39..2ea27325728 100644 --- a/ompi/mca/coll/han/coll_han_module.c +++ b/ompi/mca/coll/han/coll_han_module.c @@ -7,7 +7,7 @@ * Copyright (c) 2021 Triad National Security, LLC. All rights * reserved. * Copyright (c) 2022 IBM Corporation. All rights reserved - * Copyright (c) 2024 NVIDIA Corporation. All rights reserved. + * Copyright (c) 2024-2026 NVIDIA Corporation. All rights reserved. * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * $COPYRIGHT$ * @@ -22,6 +22,131 @@ #include "coll_han.h" #include "coll_han_dynamic.h" +/* + * Fragment item class for freelist-based buffer pool (64KB) + */ +static void fragment_item_constructor(fragment_item_t *item) +{ + item->buffer = NULL; + if (mca_coll_han_component.han_fragment_size > 0) { + if (posix_memalign(&item->buffer, 4096, mca_coll_han_component.han_fragment_size) != 0) { + item->buffer = NULL; + } + } +} + +static void fragment_item_destructor(fragment_item_t *item) +{ + if (item->buffer) { + free(item->buffer); + item->buffer = NULL; + } +} + +OBJ_CLASS_INSTANCE(fragment_item_t, + opal_free_list_item_t, + fragment_item_constructor, + fragment_item_destructor); + +/* + * Large fragment item class for freelist-based buffer pool (1MB) + */ +static void large_fragment_item_constructor(large_fragment_item_t *item) +{ + item->buffer = NULL; + if (mca_coll_han_component.han_large_fragment_size > 0) { + if (posix_memalign(&item->buffer, 4096, mca_coll_han_component.han_large_fragment_size) != 0) { + item->buffer = NULL; + } + } +} + +static void large_fragment_item_destructor(large_fragment_item_t *item) +{ + if (item->buffer) { + free(item->buffer); + item->buffer = NULL; + } +} + +OBJ_CLASS_INSTANCE(large_fragment_item_t, + opal_free_list_item_t, + large_fragment_item_constructor, + large_fragment_item_destructor); + +/** + * Initialize fragment freelists on a HAN module. + */ +#define HAN_FRAG_INITIAL_COUNT 32 +#define HAN_FRAG_MAX_COUNT (-1) /* unlimited */ +#define HAN_FRAG_GROWTH_BATCH 64 +#define HAN_LARGE_FRAG_INITIAL 4 +#define HAN_LARGE_FRAG_MAX 20 +#define HAN_LARGE_FRAG_GROWTH 4 + +static void han_init_freelists(mca_coll_han_module_t *han_module) +{ + int rc; + if (!mca_coll_han_component.han_use_persist_buffers) { + return; + } + if (mca_coll_han_component.han_fragment_size > 0) { + OBJ_CONSTRUCT(&han_module->fragment_freelist, opal_free_list_t); + rc = opal_free_list_init(&han_module->fragment_freelist, + sizeof(fragment_item_t), + opal_cache_line_size, + OBJ_CLASS(fragment_item_t), + 0, opal_cache_line_size, + HAN_FRAG_INITIAL_COUNT, + HAN_FRAG_MAX_COUNT, + HAN_FRAG_GROWTH_BATCH, + NULL, 0, NULL, NULL, NULL); + if (OPAL_SUCCESS != rc) { + OBJ_DESTRUCT(&han_module->fragment_freelist); + opal_output_verbose(0, mca_coll_han_component.han_output, + "coll:han: fragment freelist init failed, disabling persist buffers\n"); + mca_coll_han_component.han_use_persist_buffers = false; + return; + } + } + OBJ_CONSTRUCT(&han_module->large_fragment_freelist, opal_free_list_t); + if (mca_coll_han_component.han_large_fragment_size > 0) { + rc = opal_free_list_init(&han_module->large_fragment_freelist, + sizeof(large_fragment_item_t), + opal_cache_line_size, + OBJ_CLASS(large_fragment_item_t), + 0, opal_cache_line_size, + HAN_LARGE_FRAG_INITIAL, + HAN_LARGE_FRAG_MAX, + HAN_LARGE_FRAG_GROWTH, + NULL, 0, NULL, NULL, NULL); + if (OPAL_SUCCESS != rc) { + OBJ_DESTRUCT(&han_module->large_fragment_freelist); + if (mca_coll_han_component.han_fragment_size > 0) { + OBJ_DESTRUCT(&han_module->fragment_freelist); + } + opal_output_verbose(0, mca_coll_han_component.han_output, + "coll:han: large fragment freelist init failed, disabling persist buffers\n"); + mca_coll_han_component.han_use_persist_buffers = false; + return; + } + } +} + +/** + * Destroy fragment freelists on a HAN module. + */ +static void han_destroy_freelists(mca_coll_han_module_t *han_module) +{ + if (!mca_coll_han_component.han_use_persist_buffers) { + return; + } + if (mca_coll_han_component.han_fragment_size > 0) { + OBJ_DESTRUCT(&han_module->fragment_freelist); + } + OBJ_DESTRUCT(&han_module->large_fragment_freelist); +} + /* *@file @@ -80,6 +205,12 @@ static void mca_coll_han_module_construct(mca_coll_han_module_t * module) module->cached_up_comms = NULL; module->cached_vranks = NULL; module->cached_topo = NULL; + module->scratch_buf[0] = NULL; + module->scratch_buf_size[0] = 0; + module->scratch_buf[1] = NULL; + module->scratch_buf_size[1] = 0; + memset(&module->a2a_cache, 0, sizeof(module->a2a_cache)); + memset(&module->a2av_cache, 0, sizeof(module->a2av_cache)); module->is_mapbycore = false; module->storage_initialized = false; for( i = 0; i < NB_TOPO_LVL; i++ ) { @@ -116,7 +247,12 @@ mca_coll_han_module_destruct(mca_coll_han_module_t * module) if (module->cached_low_comms != NULL) { for (i = 0; i < COLL_HAN_LOW_MODULES; i++) { + int cid = module->cached_low_comms[i]->c_index; ompi_comm_free(&(module->cached_low_comms[i])); + ompi_communicator_t *tmp = ompi_comm_lookup(cid); + if (NULL != tmp) { + OBJ_RELEASE(tmp); + } module->cached_low_comms[i] = NULL; } free(module->cached_low_comms); @@ -124,7 +260,12 @@ mca_coll_han_module_destruct(mca_coll_han_module_t * module) } if (module->cached_up_comms != NULL) { for (i = 0; i < COLL_HAN_UP_MODULES; i++) { + int cid = module->cached_up_comms[i]->c_index; ompi_comm_free(&(module->cached_up_comms[i])); + ompi_communicator_t *tmp = ompi_comm_lookup(cid); + if (NULL != tmp) { + OBJ_RELEASE(tmp); + } module->cached_up_comms[i] = NULL; } free(module->cached_up_comms); @@ -138,9 +279,48 @@ mca_coll_han_module_destruct(mca_coll_han_module_t * module) free(module->cached_topo); module->cached_topo = NULL; } + free(module->scratch_buf[0]); + module->scratch_buf[0] = NULL; + module->scratch_buf_size[0] = 0; + free(module->scratch_buf[1]); + module->scratch_buf[1] = NULL; + module->scratch_buf_size[1] = 0; + + /* Alltoall cache cleanup */ + free(module->a2a_cache.bounce); + if (module->a2a_cache.map_ctx) { + if (mca_smsc) { + for (i = 0; i < module->a2a_cache.cached_low_size; i++) { + if (module->a2a_cache.map_ctx[i]) + mca_smsc->unmap_peer_region(module->a2a_cache.map_ctx[i]); + } + } + free(module->a2a_cache.map_ctx); + } + free(module->a2a_cache.low_bufs); + free(module->a2a_cache.gather_buf); + free(module->a2a_cache.recv_buf); + + /* Alltoallv cache cleanup */ + free(module->a2av_cache.serial_buf); + free(module->a2av_cache.gather_out); + free(module->a2av_cache.peers); + free(module->a2av_cache.peer_types); + free(module->a2av_cache.send_from); + free(module->a2av_cache.recv_to); + free(module->a2av_cache.send_counts); + free(module->a2av_cache.recv_counts); + free(module->a2av_cache.send_types); + free(module->a2av_cache.recv_types); + for(i=0 ; isub_comm[i]) { + int cid = module->sub_comm[i]->c_index; ompi_comm_free(&(module->sub_comm[i])); + ompi_communicator_t *tmp = ompi_comm_lookup(cid); + if (NULL != tmp) { + OBJ_RELEASE(tmp); + } } } @@ -300,6 +480,8 @@ mca_coll_han_module_enable(mca_coll_base_module_t * module, { mca_coll_han_module_t * han_module = (mca_coll_han_module_t*) module; + han_init_freelists(han_module); + HAN_INSTALL_COLL_API(comm, han_module, alltoall); HAN_INSTALL_COLL_API(comm, han_module, alltoallv); HAN_INSTALL_COLL_API(comm, han_module, allgather); @@ -329,6 +511,8 @@ mca_coll_han_module_disable(mca_coll_base_module_t * module, { mca_coll_han_module_t * han_module = (mca_coll_han_module_t *) module; + han_destroy_freelists(han_module); + HAN_UNINSTALL_COLL_API(comm, han_module, alltoall); HAN_UNINSTALL_COLL_API(comm, han_module, alltoallv); HAN_UNINSTALL_COLL_API(comm, han_module, allgather); diff --git a/ompi/mca/coll/han/coll_han_reduce.c b/ompi/mca/coll/han/coll_han_reduce.c index 097da6e7662..40e038fcd02 100644 --- a/ompi/mca/coll/han/coll_han_reduce.c +++ b/ompi/mca/coll/han/coll_han_reduce.c @@ -143,17 +143,16 @@ mca_coll_han_reduce_intra(const void *sbuf, /* node leaders require a buffer to store intermediate results */ void *tmp_rbuf = NULL; - void *tmp_rbuf_to_free = NULL; + bool is_tmp_rbuf = false; if (w_rank == root) { /* the global root already has one */ tmp_rbuf = rbuf; } else if (low_rank == root_low_rank) { /* allocate 2 temporary segments on node leaders that are not the global root */ - tmp_rbuf = malloc(2*extent*seg_count); - if (NULL == tmp_rbuf) { - return OMPI_ERR_OUT_OF_RESOURCE; - } - tmp_rbuf_to_free = tmp_rbuf; + size_t needed = 2*extent*seg_count; + tmp_rbuf = malloc(needed); + if (NULL == tmp_rbuf) return OMPI_ERR_OUT_OF_RESOURCE; + is_tmp_rbuf = true; } /* Create t0 tasks for the first segment */ @@ -163,7 +162,7 @@ mca_coll_han_reduce_intra(const void *sbuf, mca_coll_han_set_reduce_args(t, t0, (char *) sbuf, (char *) tmp_rbuf, seg_count, dtype, op, root_up_rank, root_low_rank, up_comm, low_comm, num_segments, 0, w_rank, count - (num_segments - 1) * seg_count, - low_rank != root_low_rank, (NULL != tmp_rbuf_to_free)); + low_rank != root_low_rank, is_tmp_rbuf); /* Init the first task */ init_task(t0, mca_coll_han_reduce_t0_task, (void *) t); issue_task(t0); @@ -195,7 +194,9 @@ mca_coll_han_reduce_intra(const void *sbuf, } free(t); - free(tmp_rbuf_to_free); + if (is_tmp_rbuf) { + free(tmp_rbuf); + } return OMPI_SUCCESS; @@ -296,7 +297,8 @@ mca_coll_han_reduce_intra_simple(const void *sbuf, int ret; int *vranks, low_rank, low_size; ptrdiff_t rsize, rgap = 0; - void * tmp_buf; + void * tmp_buf = NULL; + opal_free_list_item_t *tmp_fl_item = NULL; mca_coll_han_module_t *han_module = (mca_coll_han_module_t *)module; @@ -345,11 +347,25 @@ mca_coll_han_reduce_intra_simple(const void *sbuf, /* Get root ranks for low and up comms */ mca_coll_han_get_ranks(vranks, root, low_size, &root_low_rank, &root_up_rank); + /* Freelist-backed simple reduce: low_comm reduce → up_comm reduce */ if (root_low_rank == low_rank && w_rank != root) { rsize = opal_datatype_span(&dtype->super, (int64_t)count, &rgap); - tmp_buf = malloc(rsize); - if (NULL == tmp_buf) { - return OMPI_ERROR; + if (mca_coll_han_component.han_use_persist_buffers) { + size_t frag_size = mca_coll_han_component.han_fragment_size; + if (frag_size > 0 && (size_t)rsize <= frag_size) { + fragment_item_t *fi = (fragment_item_t*)opal_free_list_get( + &han_module->fragment_freelist); + if (fi != NULL) { + tmp_buf = (char *)fi->buffer; + tmp_fl_item = (opal_free_list_item_t*)fi; + } + } + } + if (tmp_buf == NULL) { + tmp_buf = malloc(rsize); + if (NULL == tmp_buf) { + return OMPI_ERROR; + } } } else { /* global root rbuf is valid, local non-root do not need buffers */ @@ -364,7 +380,11 @@ mca_coll_han_reduce_intra_simple(const void *sbuf, low_comm, low_comm->c_coll->coll_reduce_module); if (OPAL_UNLIKELY(OMPI_SUCCESS != ret)){ if (root_low_rank == low_rank && w_rank != root){ - free(tmp_buf); + if (tmp_fl_item != NULL) { + opal_free_list_return(&han_module->fragment_freelist, tmp_fl_item); + } else { + free(tmp_buf); + } } OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, "HAN/REDUCE: low comm reduce failed. " @@ -378,7 +398,11 @@ mca_coll_han_reduce_intra_simple(const void *sbuf, ret = up_comm->c_coll->coll_reduce((char *)tmp_buf, NULL, count, dtype, op, root_up_rank, up_comm, up_comm->c_coll->coll_reduce_module); - free(tmp_buf); + if (tmp_fl_item != NULL) { + opal_free_list_return(&han_module->fragment_freelist, tmp_fl_item); + } else { + free(tmp_buf); + } } else { /* Take advantage of any optimisation made for IN_PLACE * communications */ diff --git a/ompi/mca/coll/han/coll_han_scatter.c b/ompi/mca/coll/han/coll_han_scatter.c index 2122103a5dd..65e5fae349e 100644 --- a/ompi/mca/coll/han/coll_han_scatter.c +++ b/ompi/mca/coll/han/coll_han_scatter.c @@ -4,6 +4,8 @@ * reserved. * Copyright (c) 2022 IBM Corporation. All rights reserved * Copyright (c) 2024 NVIDIA Corporation. All rights reserved. + * Copyright (c) 2026 Amazon.com, Inc. or its affiliates. + * All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -26,6 +28,51 @@ static int mca_coll_han_scatter_us_task(void *task_args); static int mca_coll_han_scatter_ls_task(void *task_args); +/** + * Allocate from tiered freelists (large then small), falling back to malloc. + */ +static char *scatter_alloc_tiered(opal_free_list_t *large_fl, size_t large_size, + opal_free_list_t *small_fl, size_t small_size, + size_t needed, opal_free_list_item_t **item, + int *src) +{ + *item = NULL; + *src = HAN_ALLOC_MALLOC; + if (large_size > 0 && needed <= large_size) { + large_fragment_item_t *lfi = (large_fragment_item_t *)opal_free_list_get(large_fl); + if (lfi != NULL) { + *item = (opal_free_list_item_t *)lfi; + *src = HAN_ALLOC_LARGE; + return (char *)lfi->buffer; + } + } + if (small_size > 0 && needed <= small_size) { + fragment_item_t *fi = (fragment_item_t *)opal_free_list_get(small_fl); + if (fi != NULL) { + *item = (opal_free_list_item_t *)fi; + *src = HAN_ALLOC_SMALL; + return (char *)fi->buffer; + } + } + return (char *)malloc(needed); +} + +/** + * Free a tiered allocation based on src tag. + */ +static void scatter_free_tiered(opal_free_list_t *large_fl, + opal_free_list_t *small_fl, + opal_free_list_item_t *item, char *buf, int src) +{ + if (src == HAN_ALLOC_LARGE) { + opal_free_list_return(large_fl, item); + } else if (src == HAN_ALLOC_SMALL) { + opal_free_list_return(small_fl, item); + } else { + free(buf); + } +} + /* Only work with regular situation (each node has equal number of processes) */ static inline void @@ -44,7 +91,8 @@ mca_coll_han_set_scatter_args(mca_coll_han_scatter_args_t * args, int root_low_rank, struct ompi_communicator_t *up_comm, struct ompi_communicator_t *low_comm, - int w_rank, bool noop, ompi_request_t * req) + int w_rank, bool noop, ompi_request_t * req, + mca_coll_han_module_t *han_module) { args->cur_task = cur_task; args->sbuf = sbuf; @@ -63,6 +111,9 @@ mca_coll_han_set_scatter_args(mca_coll_han_scatter_args_t * args, args->w_rank = w_rank; args->noop = noop; args->req = req; + args->han_module = han_module; + args->reorder_fl_item = NULL; + args->reorder_fl_src = HAN_ALLOC_MALLOC; } /* @@ -138,6 +189,8 @@ mca_coll_han_scatter_intra(const void *sbuf, size_t scount, */ char *reorder_buf = NULL; char *reorder_sbuf = NULL; + opal_free_list_item_t *reorder_fl_item = NULL; + int reorder_fl_src = HAN_ALLOC_MALLOC; if (w_rank == root) { /* If the processes are mapped-by core, no need to reorder */ @@ -149,7 +202,19 @@ mca_coll_han_scatter_intra(const void *sbuf, size_t scount, ptrdiff_t ssize, sgap = 0, sextent; ompi_datatype_type_extent(sdtype, &sextent); ssize = opal_datatype_span(&sdtype->super, (int64_t) scount * w_size, &sgap); - reorder_buf = (char *) malloc(ssize); + if (mca_coll_han_component.han_use_persist_buffers) { + reorder_buf = scatter_alloc_tiered( + &han_module->large_fragment_freelist, + mca_coll_han_component.han_large_fragment_size, + &han_module->fragment_freelist, + mca_coll_han_component.han_fragment_size, + ssize, &reorder_fl_item, &reorder_fl_src); + } else { + reorder_buf = (char *)malloc(ssize); + } + if (NULL == reorder_buf) { + return OMPI_ERR_OUT_OF_RESOURCE; + } reorder_sbuf = reorder_buf - sgap; for (int i = 0; i < up_size; i++) { for (int j = 0; j < low_size; j++) { @@ -177,7 +242,9 @@ mca_coll_han_scatter_intra(const void *sbuf, size_t scount, mca_coll_han_set_scatter_args(us_args, us, reorder_sbuf, NULL, reorder_buf, scount, sdtype, (char *) rbuf, rcount, rdtype, root, root_up_rank, root_low_rank, up_comm, low_comm, w_rank, low_rank != root_low_rank, - temp_request); + temp_request, han_module); + us_args->reorder_fl_item = reorder_fl_item; + us_args->reorder_fl_src = reorder_fl_src; /* Init us task */ init_task(us, mca_coll_han_scatter_us_task, (void *) (us_args)); /* Issure us task */ @@ -209,8 +276,16 @@ int mca_coll_han_scatter_us_task(void *task_args) int low_size = ompi_comm_size(t->low_comm); ptrdiff_t rsize, rgap = 0; rsize = opal_datatype_span(&dtype->super, (int64_t) count * low_size, &rgap); - char *tmp_buf = (char *) malloc(rsize); + + /* Inter-node receive buffer: persistent realloc-to-HWM or malloc */ + char *tmp_buf; + tmp_buf = han_scratch_or_malloc(&t->han_module->scratch_buf[1], + &t->han_module->scratch_buf_size[1], + (size_t)rsize, + mca_coll_han_component.han_use_persist_buffers); + if (NULL == tmp_buf) return OMPI_ERR_OUT_OF_RESOURCE; char *tmp_rbuf = tmp_buf - rgap; + OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, "[%d] Han Scatter: us scatter\n", t->w_rank)); /* Inter node scatter */ @@ -223,9 +298,18 @@ int mca_coll_han_scatter_us_task(void *task_args) t->scount = count; } + /* Free reorder buffer (root only) */ if (t->sbuf_reorder_free != NULL && t->root == t->w_rank) { - free(t->sbuf_reorder_free); + if (mca_coll_han_component.han_use_persist_buffers) { + scatter_free_tiered(&t->han_module->large_fragment_freelist, + &t->han_module->fragment_freelist, + t->reorder_fl_item, t->sbuf_reorder_free, + t->reorder_fl_src); + } else { + free(t->sbuf_reorder_free); + } t->sbuf_reorder_free = NULL; + t->reorder_fl_item = NULL; } /* Create ls tasks for the current union segment */ mca_coll_task_t *ls = t->cur_task; @@ -249,9 +333,12 @@ int mca_coll_han_scatter_ls_task(void *task_args) t->rcount, t->rdtype, t->root_low_rank, t->low_comm, t->low_comm->c_coll->coll_scatter_module); - if (t->sbuf_inter_free != NULL && t->noop != true) { - free(t->sbuf_inter_free); - t->sbuf_inter_free = NULL; + /* Free inter-node buffer when not using persist buffers */ + if (!mca_coll_han_component.han_use_persist_buffers) { + if (t->sbuf_inter_free != NULL && !t->noop) { + free(t->sbuf_inter_free); + t->sbuf_inter_free = NULL; + } } OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, "[%d] Han Scatter: ls finish\n", t->w_rank)); @@ -308,7 +395,7 @@ mca_coll_han_scatter_intra_simple(const void *sbuf, size_t scount, int low_rank = ompi_comm_rank(low_comm); int low_size = ompi_comm_size(low_comm); /* Get root ranks for low and up comms */ - int root_low_rank, root_up_rank; /* root ranks for both sub-communicators */ + int root_low_rank, root_up_rank; mca_coll_han_get_ranks(vranks, root, low_size, &root_low_rank, &root_up_rank); if (w_rank == root) { @@ -323,7 +410,8 @@ mca_coll_han_scatter_intra_simple(const void *sbuf, size_t scount, * if the processes are mapped-by core, no need to reorder: * distribution of ranks on core first and node next, * in a increasing order for both patterns */ - char *reorder_buf = NULL; // allocated memory + char *reorder_buf = NULL; + bool reorder_is_sbuf = false; size_t block_size; ompi_datatype_type_size(dtype, &block_size); @@ -337,23 +425,19 @@ mca_coll_han_scatter_intra_simple(const void *sbuf, size_t scount, OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, "[%d]: Han scatter: no need to reorder: ", w_rank)); reorder_buf = (char *)sbuf; + reorder_is_sbuf = true; } else { /* Data must be copied, let's be efficient packing it */ OPAL_OUTPUT_VERBOSE((30, mca_coll_han_component.han_output, "[%d]: Han scatter: needs reordering or compacting: ", w_rank)); - reorder_buf = malloc(block_size * w_size); - if ( NULL == reorder_buf){ - return OMPI_ERROR; - } + size_t reorder_size = (size_t)block_size * w_size; + reorder_buf = han_scratch_or_malloc(&han_module->scratch_buf[0], + &han_module->scratch_buf_size[0], + reorder_size, + mca_coll_han_component.han_use_persist_buffers); + if (NULL == reorder_buf) return OMPI_ERROR; - /** Reorder and packing: - * Suppose, the message is 0 1 2 3 4 5 6 7 but the processes are - * mapped on 2 nodes, for example |0 2 4 6| |1 3 5 7|. The messages to - * leaders must be 0 2 4 6 and 1 3 5 7. - * So the upper scatter must send 0 2 4 6 1 3 5 7. - * In general, the topo[i*topolevel +1] must be taken. - */ ptrdiff_t extent, block_extent; ompi_datatype_type_extent(dtype, &extent); block_extent = extent * (ptrdiff_t)count; @@ -367,43 +451,74 @@ mca_coll_han_scatter_intra_simple(const void *sbuf, size_t scount, } } - /* allocate the intermediary buffer - * to scatter from leaders on the low sub communicators */ - char *tmp_buf = NULL; // allocated memory + /* + * Persistent inter-node receive buffer. + * Grows to high-water mark via realloc so the virtual address + * stabilises after the first large call, keeping the NIC MR cache + * entry valid across iterations. + * + * When the total fits in a freelist item, use the freelist instead + * (the item address is also stable across get/return cycles). + */ + size_t tmp_total = block_size * low_size; + char *tmp_buf = NULL; + opal_free_list_item_t *tmp_fl_item = NULL; + int tmp_fl_src = HAN_ALLOC_MALLOC; + if (low_rank == root_low_rank) { - tmp_buf = (char *) malloc(block_size * low_size); - - /* 1. up scatter (internode) between node leaders */ - up_comm->c_coll->coll_scatter((char*) reorder_buf, - count * low_size, - dtype, - (char *)tmp_buf, - block_size * low_size, - MPI_BYTE, - root_up_rank, - up_comm, + if (mca_coll_han_component.han_use_persist_buffers) { + tmp_buf = scatter_alloc_tiered( + &han_module->large_fragment_freelist, + mca_coll_han_component.han_large_fragment_size, + &han_module->fragment_freelist, + mca_coll_han_component.han_fragment_size, + tmp_total, &tmp_fl_item, &tmp_fl_src); + /* If tiered alloc fell back to malloc (src==0), use persist instead */ + if (tmp_fl_src == HAN_ALLOC_MALLOC && tmp_buf != NULL) { + free(tmp_buf); + tmp_buf = NULL; + } + if (tmp_fl_src == HAN_ALLOC_MALLOC) { + tmp_buf = han_scratch_alloc(&han_module->scratch_buf[1], + &han_module->scratch_buf_size[1], + tmp_total); + if (NULL == tmp_buf) return OMPI_ERR_OUT_OF_RESOURCE; + } + } else { + tmp_buf = (char *)malloc(tmp_total); + if (NULL == tmp_buf) return OMPI_ERR_OUT_OF_RESOURCE; + } + + up_comm->c_coll->coll_scatter((char *)reorder_buf, + count * low_size, dtype, + tmp_buf, + block_size * low_size, MPI_BYTE, + root_up_rank, up_comm, up_comm->c_coll->coll_scatter_module); } - /* 2. low scatter on nodes leaders */ - low_comm->c_coll->coll_scatter((char *)tmp_buf, - block_size, - MPI_BYTE, - (char*)rbuf, - rcount, - rdtype, - root_low_rank, - low_comm, + low_comm->c_coll->coll_scatter(tmp_buf, + block_size, MPI_BYTE, + (char *)rbuf, rcount, rdtype, + root_low_rank, low_comm, low_comm->c_coll->coll_scatter_module); if (low_rank == root_low_rank) { - free(tmp_buf); - tmp_buf = NULL; + if (mca_coll_han_component.han_use_persist_buffers) { + if (tmp_fl_src != HAN_ALLOC_MALLOC) { + scatter_free_tiered(&han_module->large_fragment_freelist, + &han_module->fragment_freelist, + tmp_fl_item, tmp_buf, tmp_fl_src); + } + /* persist buffer (src==0) is not freed */ + } else { + free(tmp_buf); + } } - if (reorder_buf != sbuf) { + + if (!mca_coll_han_component.han_use_persist_buffers && !reorder_is_sbuf) { free(reorder_buf); } return OMPI_SUCCESS; - } diff --git a/ompi/mca/coll/han/coll_han_scatterv.c b/ompi/mca/coll/han/coll_han_scatterv.c index 18c67baff91..dfc23175ff5 100644 --- a/ompi/mca/coll/han/coll_han_scatterv.c +++ b/ompi/mca/coll/han/coll_han_scatterv.c @@ -226,7 +226,10 @@ int mca_coll_han_scatterv_intra(const void *sbuf, ompi_count_array_t scounts, om if (need_bounce_buf) { ptrdiff_t ssize, sgap; ssize = opal_datatype_span(&rdtype->super, total_up_scounts, &sgap); - bounce_buf = malloc(ssize); + bounce_buf = han_scratch_or_malloc( + &han_module->scratch_buf[0], + &han_module->scratch_buf_size[0], + ssize, mca_coll_han_component.han_use_persist_buffers); if (!bounce_buf) { err = OMPI_ERR_OUT_OF_RESOURCE; goto root_out; @@ -293,7 +296,7 @@ int mca_coll_han_scatterv_intra(const void *sbuf, ompi_count_array_t scounts, om if (up_peer_ub) { free(up_peer_ub); } - if (bounce_buf) { + if (bounce_buf && !mca_coll_han_component.han_use_persist_buffers) { free(bounce_buf); } @@ -355,7 +358,10 @@ int mca_coll_han_scatterv_intra(const void *sbuf, ompi_count_array_t scounts, om total_rsize += low_scounts[i]; } - tmp_buf = (char *) malloc(total_rsize); /* tmp_buf is still valid if total_rsize is 0 */ + tmp_buf = han_scratch_or_malloc( + &han_module->scratch_buf[1], + &han_module->scratch_buf_size[1], + total_rsize, mca_coll_han_component.han_use_persist_buffers); if (!tmp_buf) { err = OMPI_ERR_OUT_OF_RESOURCE; goto node_leader_out; @@ -382,7 +388,7 @@ int mca_coll_han_scatterv_intra(const void *sbuf, ompi_count_array_t scounts, om if (low_displs) { free(low_displs); } - if (tmp_buf) { + if (tmp_buf && !mca_coll_han_component.han_use_persist_buffers) { free(tmp_buf); } diff --git a/ompi/mca/coll/han/coll_han_subcomms.c b/ompi/mca/coll/han/coll_han_subcomms.c index 9fcd65dad9b..ccee0392238 100644 --- a/ompi/mca/coll/han/coll_han_subcomms.c +++ b/ompi/mca/coll/han/coll_han_subcomms.c @@ -7,7 +7,8 @@ * Laboratory, ICS Forth. All rights reserved. * Copyright (c) 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved. * - * Copyright (c) 2024 NVIDIA Corporation. All rights reserved. + * Copyright (c) 2024-2026 NVIDIA Corporation. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -46,15 +47,6 @@ (COMM)->c_coll->coll_##COLL##_module = (FALLBACKS).COLL.module; \ } while (0) -#define HAN_SUBCOM_EXTRA_RETAIN(COMM, PARENT_COMM) \ - do \ - { \ - if (OMPI_COMM_CID_IS_LOWER(COMM, PARENT_COMM)) { \ - OMPI_COMM_SET_EXTRA_RETAIN(COMM); \ - OBJ_RETAIN(COMM); \ - } \ - } while (0) - /* * Routine that creates the local hierarchical sub-communicators * Called each time a collective is called. @@ -215,10 +207,10 @@ int mca_coll_han_comm_create_new(struct ompi_communicator_t *comm, HAN_SUBCOM_RESTORE_COLLECTIVE(fallbacks, comm, han_module, scatterv); OBJ_DESTRUCT(&comm_info); - - /* Ensure these communicators aren't released before the parent comm */ - HAN_SUBCOM_EXTRA_RETAIN(*low_comm, comm); - HAN_SUBCOM_EXTRA_RETAIN(*up_comm, comm); + + /* Retain sub-communicators so they survive finalize ordering */ + OBJ_RETAIN(*low_comm); + OBJ_RETAIN(*up_comm); return OMPI_SUCCESS; @@ -245,9 +237,9 @@ int mca_coll_han_comm_create(struct ompi_communicator_t *comm, { int low_rank, low_size, up_rank, w_rank, w_size; mca_coll_han_collectives_fallback_t fallbacks; - ompi_communicator_t **low_comms; - ompi_communicator_t **up_comms; + ompi_communicator_t **low_comms = NULL, **up_comms = NULL; int vrank, *vranks; + int rc; opal_info_t comm_info; /* use cached communicators if possible */ @@ -257,6 +249,8 @@ int mca_coll_han_comm_create(struct ompi_communicator_t *comm, return OMPI_SUCCESS; } + OBJ_CONSTRUCT(&comm_info, opal_info_t); + /* * We cannot use han allreduce and allgather without sub-communicators, * but we are in the creation of the data structures for the HAN, and @@ -290,10 +284,14 @@ int mca_coll_han_comm_create(struct ompi_communicator_t *comm, * all participants. */ int local_procs = ompi_group_count_local_peers(comm->c_local_group); - comm->c_coll->coll_allreduce(MPI_IN_PLACE, &local_procs, 1, MPI_INT, - MPI_MAX, comm, - comm->c_coll->coll_allreduce_module); + rc = comm->c_coll->coll_allreduce(MPI_IN_PLACE, &local_procs, 1, MPI_INT, + MPI_MAX, comm, + comm->c_coll->coll_allreduce_module); + if (OMPI_SUCCESS != rc) { + goto final_agree; + } if( local_procs == 1 ) { + OBJ_DESTRUCT(&comm_info); /* restore saved collectives */ HAN_SUBCOM_RESTORE_COLLECTIVE(fallbacks, comm, han_module, alltoall); HAN_SUBCOM_RESTORE_COLLECTIVE(fallbacks, comm, han_module, alltoallv); @@ -313,20 +311,21 @@ int mca_coll_han_comm_create(struct ompi_communicator_t *comm, /* create communicators if there is no cached communicator */ w_rank = ompi_comm_rank(comm); w_size = ompi_comm_size(comm); - low_comms = (struct ompi_communicator_t **)malloc(COLL_HAN_LOW_MODULES * + low_comms = (struct ompi_communicator_t **)calloc(COLL_HAN_LOW_MODULES, sizeof(struct ompi_communicator_t *)); - up_comms = (struct ompi_communicator_t **)malloc(COLL_HAN_UP_MODULES * + up_comms = (struct ompi_communicator_t **)calloc(COLL_HAN_UP_MODULES, sizeof(struct ompi_communicator_t *)); - OBJ_CONSTRUCT(&comm_info, opal_info_t); - /* * Upgrade sm module priority to set up low_comms[0] with sm module * This sub-communicator contains the ranks that share my node. */ opal_info_set(&comm_info, "ompi_comm_coll_preference", "tuned,^han"); - ompi_comm_split_type(comm, MPI_COMM_TYPE_SHARED, 0, - &comm_info, &(low_comms[0])); + rc = ompi_comm_split_type(comm, MPI_COMM_TYPE_SHARED, 0, + &comm_info, &(low_comms[0])); + if (OMPI_SUCCESS != rc) { + goto final_agree; + } assert(OMPI_COMM_IS_DISJOINT_SET(low_comms[0]) && !OMPI_COMM_IS_DISJOINT(low_comms[0])); /* @@ -340,21 +339,30 @@ int mca_coll_han_comm_create(struct ompi_communicator_t *comm, * This sub-communicator contains the ranks that share my node. */ opal_info_set(&comm_info, "ompi_comm_coll_preference", "sm,^han"); - ompi_comm_split_type(comm, MPI_COMM_TYPE_SHARED, 0, + rc = ompi_comm_split_type(comm, MPI_COMM_TYPE_SHARED, 0, &comm_info, &(low_comms[1])); + if (OMPI_SUCCESS != rc) { + goto final_agree; + } assert(OMPI_COMM_IS_DISJOINT_SET(low_comms[1]) && !OMPI_COMM_IS_DISJOINT(low_comms[1])); opal_info_set(&comm_info, "ompi_comm_coll_preference", "xhc,^han"); - ompi_comm_split_type(comm, MPI_COMM_TYPE_SHARED, 0, + rc = ompi_comm_split_type(comm, MPI_COMM_TYPE_SHARED, 0, &comm_info, &(low_comms[2])); - + if (OMPI_SUCCESS != rc) { + goto final_agree; + } + assert(OMPI_COMM_IS_DISJOINT_SET(low_comms[2]) && !OMPI_COMM_IS_DISJOINT(low_comms[2])); /* * Upgrade libnbc module priority to set up up_comms[0] with libnbc module * This sub-communicator contains one process per node: processes with the * same intra-node rank id share such a sub-communicator */ opal_info_set(&comm_info, "ompi_comm_coll_preference", "libnbc,^han"); - ompi_comm_split_with_info(comm, low_rank, w_rank, &comm_info, &(up_comms[0]), false); + rc = ompi_comm_split_with_info(comm, low_rank, w_rank, &comm_info, &(up_comms[0]), false); + if (OMPI_SUCCESS != rc) { + goto final_agree; + } up_rank = ompi_comm_rank(up_comms[0]); assert(OMPI_COMM_IS_DISJOINT_SET(up_comms[0]) && OMPI_COMM_IS_DISJOINT(up_comms[0])); @@ -363,7 +371,10 @@ int mca_coll_han_comm_create(struct ompi_communicator_t *comm, * This sub-communicator contains one process per node. */ opal_info_set(&comm_info, "ompi_comm_coll_preference", "adapt,^han"); - ompi_comm_split_with_info(comm, low_rank, w_rank, &comm_info, &(up_comms[1]), false); + rc = ompi_comm_split_with_info(comm, low_rank, w_rank, &comm_info, &(up_comms[1]), false); + if (OMPI_SUCCESS != rc) { + goto final_agree; + } assert(OMPI_COMM_IS_DISJOINT_SET(up_comms[1]) && OMPI_COMM_IS_DISJOINT(up_comms[1])); /* @@ -380,9 +391,11 @@ int mca_coll_han_comm_create(struct ompi_communicator_t *comm, * gather vrank from each process so every process will know other processes * vrank */ - comm->c_coll->coll_allgather(&vrank, 1, MPI_INT, vranks, 1, MPI_INT, comm, + rc = comm->c_coll->coll_allgather(&vrank, 1, MPI_INT, vranks, 1, MPI_INT, comm, comm->c_coll->coll_allgather_module); - + if (OMPI_SUCCESS != rc) { + goto final_agree; + } /* * Set the cached info */ @@ -390,12 +403,66 @@ int mca_coll_han_comm_create(struct ompi_communicator_t *comm, han_module->cached_up_comms = up_comms; han_module->cached_vranks = vranks; - /* Ensure these communicators aren't released before the parent comm */ + /* Retain sub-communicators so they survive finalize ordering */ for(int i = 0; i < COLL_HAN_LOW_MODULES; i++) { - HAN_SUBCOM_EXTRA_RETAIN(low_comms[i], comm); + OBJ_RETAIN(low_comms[i]); } for(int i = 0; i < COLL_HAN_UP_MODULES; i++) { - HAN_SUBCOM_EXTRA_RETAIN(up_comms[i], comm); + OBJ_RETAIN(up_comms[i]); + } + + +final_agree: + + OBJ_DESTRUCT(&comm_info); + + if (OMPI_SUCCESS != rc) { + /** + * Revoke the input communicator to ensure no process is stuck. + */ + ompi_comm_revoke_internal(comm); + } + + /** + * Agree that everyone has successfully created the sub-communicators. + */ + + int agree_flag = (OMPI_SUCCESS == rc) ? 1 : 0; + ompi_group_t *failed_group = &ompi_mpi_group_empty.group; + int agree_rc = comm->c_coll->coll_agree( &agree_flag, + 1, + &ompi_mpi_int.dt, + &ompi_mpi_op_band.op, + &failed_group, false, + comm, + comm->c_coll->coll_agree_module); + + if (OMPI_SUCCESS != agree_rc) { + agree_flag = 0; /* agree failed so make sure to tear everything down */ + rc = agree_rc; + } + + if (!agree_flag) { + han_module->enabled = false; /* entire module set to pass-through from now on */ + if (low_comms != NULL) { + for(int i = 0; i < COLL_HAN_LOW_MODULES; i++) { + if (NULL != low_comms[i]) { + ompi_comm_revoke_internal(low_comms[i]); + ompi_comm_free(&low_comms[i]); + } + } + free(low_comms); + } + if (up_comms != NULL) { + for(int i = 0; i < COLL_HAN_UP_MODULES; i++) { + if (NULL != up_comms[i]) { + ompi_comm_revoke_internal(up_comms[i]); + ompi_comm_free(&up_comms[i]); + } + } + free(up_comms); + } + return rc; /* sub-communicator creation failed on at least one process */ } /* Reset the saved collectives to point back to HAN */ @@ -411,7 +478,6 @@ int mca_coll_han_comm_create(struct ompi_communicator_t *comm, HAN_SUBCOM_RESTORE_COLLECTIVE(fallbacks, comm, han_module, scatter); HAN_SUBCOM_RESTORE_COLLECTIVE(fallbacks, comm, han_module, scatterv); - OBJ_DESTRUCT(&comm_info); return OMPI_SUCCESS; } diff --git a/ompi/mca/coll/inter/coll_inter_allgatherv.c b/ompi/mca/coll/inter/coll_inter_allgatherv.c index fa7c9e14301..a0c7881bcff 100644 --- a/ompi/mca/coll/inter/coll_inter_allgatherv.c +++ b/ompi/mca/coll/inter/coll_inter_allgatherv.c @@ -13,6 +13,7 @@ * Copyright (c) 2015-2017 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2022 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -107,21 +108,8 @@ mca_coll_inter_allgatherv_inter(const void *sbuf, size_t scount, goto exit; } - /* TODO:BIGCOUNT: Remove tehese temporaries once ompi_datatype is updated for bigcount */ - int *tmp_rcounts = malloc(sizeof(int) * size); - int *tmp_disps = malloc(sizeof(int) * size); - if (NULL == tmp_rcounts || NULL == tmp_disps) { - err = OMPI_ERR_OUT_OF_RESOURCE; - goto exit; - } - for (i = 0; i < size; ++i) { - tmp_rcounts[i] = (int) ompi_count_array_get(rcounts, i); - tmp_disps[i] = (int) ompi_disp_array_get(disps, i); - } - ompi_datatype_create_indexed(size,tmp_rcounts,tmp_disps,rdtype,&ndtype); + ompi_datatype_create_indexed(size,rcounts,disps,rdtype,&ndtype); ompi_datatype_commit(&ndtype); - free(tmp_rcounts); - free(tmp_disps); if (0 == rank) { /* Exchange data between roots */ diff --git a/ompi/mca/coll/inter/coll_inter_gatherv.c b/ompi/mca/coll/inter/coll_inter_gatherv.c index 1e1d8840a44..73d3716ca8e 100644 --- a/ompi/mca/coll/inter/coll_inter_gatherv.c +++ b/ompi/mca/coll/inter/coll_inter_gatherv.c @@ -13,6 +13,7 @@ * Copyright (c) 2015-2016 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2022 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -62,20 +63,8 @@ mca_coll_inter_gatherv_inter(const void *sbuf, size_t scount, size_local = ompi_comm_size(comm); if (MPI_ROOT == root) { /* I am the root, receiving the data from zero. */ - /* TODO:BIGCOUNT: Remove these temporaries once ompi_datatype is updated for bigcount */ - int *tmp_rcounts = malloc(sizeof(int) * size); - int *tmp_disps = malloc(sizeof(int) * size); - if (NULL == tmp_rcounts || NULL == tmp_disps) { - return OMPI_ERR_OUT_OF_RESOURCE; - } - for (i = 0; i < size; ++i) { - tmp_rcounts[i] = ompi_count_array_get(rcounts, i); - tmp_disps[i] = ompi_disp_array_get(disps, i); - } - ompi_datatype_create_indexed(size, tmp_rcounts, tmp_disps, rdtype, &ndtype); + ompi_datatype_create_indexed(size, rcounts, disps, rdtype, &ndtype); ompi_datatype_commit(&ndtype); - free(tmp_rcounts); - free(tmp_disps); err = MCA_PML_CALL(recv(rbuf, 1, ndtype, 0, MCA_COLL_BASE_TAG_GATHERV, diff --git a/ompi/mca/coll/inter/coll_inter_scatterv.c b/ompi/mca/coll/inter/coll_inter_scatterv.c index 5d98e1ea099..9f97af68a45 100644 --- a/ompi/mca/coll/inter/coll_inter_scatterv.c +++ b/ompi/mca/coll/inter/coll_inter_scatterv.c @@ -13,6 +13,7 @@ * Copyright (c) 2015-2016 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2022 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -100,9 +101,9 @@ mca_coll_inter_scatterv_inter(const void *sbuf, ompi_count_array_t scounts, displace[i] = displace[i-1] + counts[i-1]; } } - /* perform the scatterv locally */ OMPI_COUNT_ARRAY_INIT(&counts_arg, counts); OMPI_DISP_ARRAY_INIT(&displace_arg, displace); + /* perform the scatterv locally */ err = comm->c_local_comm->c_coll->coll_scatterv(ptmp, counts_arg, displace_arg, rdtype, rbuf, rcount, rdtype, 0, comm->c_local_comm, @@ -139,20 +140,8 @@ mca_coll_inter_scatterv_inter(const void *sbuf, ompi_count_array_t scounts, return err; } - /* TODO:BIGCOUNT: Remove these temporaries once ompi_datatype is updated for bigcount */ - int *tmp_scounts = malloc(sizeof(int) * size); - int *tmp_disps = malloc(sizeof(int) * size); - if (NULL == tmp_scounts || NULL == tmp_disps) { - return OMPI_ERR_OUT_OF_RESOURCE; - } - for (i = 0; i < size; ++i) { - tmp_scounts[i] = (int) ompi_count_array_get(scounts, i); - tmp_disps[i] = (int) ompi_disp_array_get(disps, i); - } - ompi_datatype_create_indexed(size,tmp_scounts,tmp_disps,sdtype,&ndtype); + ompi_datatype_create_indexed(size,scounts,disps,sdtype,&ndtype); ompi_datatype_commit(&ndtype); - free(tmp_scounts); - free(tmp_disps); err = MCA_PML_CALL(send(sbuf, 1, ndtype, 0, MCA_COLL_BASE_TAG_SCATTERV, diff --git a/ompi/mca/coll/tuned/coll_tuned.h b/ompi/mca/coll/tuned/coll_tuned.h index 47634caec25..53bb8705aa0 100644 --- a/ompi/mca/coll/tuned/coll_tuned.h +++ b/ompi/mca/coll/tuned/coll_tuned.h @@ -113,6 +113,7 @@ int ompi_coll_tuned_allgatherv_intra_check_forced_init(coll_tuned_force_algorith /* All Reduce */ int ompi_coll_tuned_allreduce_intra_dec_fixed(ALLREDUCE_ARGS); +int ompi_coll_tuned_allreduce_intra_disjoint_dec_fixed(ALLREDUCE_ARGS); int ompi_coll_tuned_allreduce_intra_dec_dynamic(ALLREDUCE_ARGS); int ompi_coll_tuned_allreduce_intra_do_this(ALLREDUCE_ARGS, int algorithm, int faninout, int segsize); int ompi_coll_tuned_allreduce_intra_check_forced_init (coll_tuned_force_algorithm_mca_param_indices_t *mca_param_indices); diff --git a/ompi/mca/coll/tuned/coll_tuned_decision_fixed.c b/ompi/mca/coll/tuned/coll_tuned_decision_fixed.c index e97993ffe10..3b0077c9bcc 100644 --- a/ompi/mca/coll/tuned/coll_tuned_decision_fixed.c +++ b/ompi/mca/coll/tuned/coll_tuned_decision_fixed.c @@ -218,6 +218,194 @@ ompi_coll_tuned_allreduce_intra_dec_fixed(const void *sbuf, void *rbuf, size_t c comm, module, alg, 0, 0); } + +/* + * allreduce_intra_disjoint + * + * Function: - selects allreduce algorithm to use for disjoint (inter-node) + * communicators, whose communication patterns differ from intra-node. + * This function implements a decision tree that selects the most + * efficient allreduce algorithm based on communicator size, message + * size, and operation commutativity. + * Accepts: - same as MPI_Allreduce() + * Returns: - MPI_SUCCESS or error code + */ +int +ompi_coll_tuned_allreduce_intra_disjoint_dec_fixed(const void *sbuf, void *rbuf, size_t count, + struct ompi_datatype_t *dtype, + struct ompi_op_t *op, + struct ompi_communicator_t *comm, + mca_coll_base_module_t *module) +{ + + size_t dsize, total_dsize; + int communicator_size, alg; + communicator_size = ompi_comm_size(comm); + OPAL_OUTPUT_VERBOSE((COLL_TUNED_TRACING_VERBOSE, ompi_coll_tuned_stream, + "ompi_coll_tuned_allreduce_intra_disjoint_dec_fixed")); + + ompi_datatype_type_size(dtype, &dsize); + total_dsize = dsize * (ptrdiff_t)count; + + /** Algorithms: + * {1, "basic_linear"}, + * {2, "nonoverlapping"}, + * {3, "recursive_doubling"}, + * {4, "ring"}, + * {5, "segmented_ring"}, + * {6, "rabenseifner"}, + * {7, "allgather_reduce"} + * + * Currently, ring, segmented ring, and rabenseifner do not support + * non-commutative operations. + */ + if( !ompi_op_is_commute(op) ) { + if (communicator_size == 2) { + alg = 3; + } else if (communicator_size < 4) { + alg = 7; + } else if (communicator_size < 8) { + if (total_dsize < 1048576) { + alg = 7; + } else { + alg = 3; + } + } else if (communicator_size < 16) { + if (total_dsize < 262144) { + alg = 7; + } else { + alg = 3; + } + } else if (communicator_size < 32) { + if (total_dsize < 32768) { + alg = 7; + } else if (total_dsize < 131072) { + alg = 2; + } else { + alg = 3; + } + } else if (communicator_size <= 64) { + if (total_dsize < 32768) { + alg = 7; + } else { + alg = 3; + } + } else if (communicator_size < 128) { + alg = 3; + } else if (communicator_size < 256) { + if (total_dsize < 131072) { + alg = 2; + } else if (total_dsize < 524288) { + alg = 3; + } else { + alg = 2; + } + } else if (communicator_size < 512) { + if (total_dsize < 4096) { + alg = 2; + } else if (total_dsize < 524288) { + alg = 3; + } else { + alg = 2; + } + } else { + if (total_dsize < 2048) { + alg = 2; + } else { + alg = 3; + } + } + } else { + if (communicator_size == 2) { + alg = 3; + } else if (communicator_size < 4) { + alg = 7; + } else if (communicator_size < 8) { + if (total_dsize < 1048576) { + alg = 7; + } else { + alg = 3; + } + } else if (communicator_size < 16) { + if (total_dsize < 262144) { + alg = 7; + } else if (total_dsize < 1048576) { + alg = 6; + } else { + alg = 3; + } + } else if (communicator_size < 32) { + if (total_dsize < 32768) { + alg = 7; + } else if (total_dsize < 131072) { + alg = 2; + } else { + alg = 6; + } + } else if (communicator_size <= 64) { + if (total_dsize < 32768) { + alg = 7; + } else if (total_dsize < 131072) { + alg = 3; + } else { + alg = 6; + } + } else if (communicator_size < 128) { + if (total_dsize < 262144) { + alg = 3; + } else { + alg = 6; + } + } else if (communicator_size < 256) { + if (total_dsize < 131072) { + alg = 2; + } else if (total_dsize < 262144) { + alg = 3; + } else { + alg = 6; + } + } else if (communicator_size < 512) { + if (total_dsize < 4096) { + alg = 2; + } else { + alg = 6; + } + } else if (communicator_size < 2048) { + if (total_dsize < 2048) { + alg = 2; + } else if (total_dsize < 16384) { + alg = 3; + } else { + alg = 6; + } + } else if (communicator_size < 4096) { + if (total_dsize < 2048) { + alg = 2; + } else if (total_dsize < 4096) { + alg = 5; + } else if (total_dsize < 16384) { + alg = 3; + } else { + alg = 6; + } + } else { + if (total_dsize < 2048) { + alg = 2; + } else if (total_dsize < 16384) { + alg = 5; + } else if (total_dsize < 32768) { + alg = 3; + } else { + alg = 6; + } + } + } + + return ompi_coll_tuned_allreduce_intra_do_this (sbuf, rbuf, count, dtype, op, + comm, module, alg, 0, 0); +} + + /* * alltoall_intra_dec * diff --git a/ompi/mca/coll/tuned/coll_tuned_module.c b/ompi/mca/coll/tuned/coll_tuned_module.c index 20bb4c4a49b..f82bcf27951 100644 --- a/ompi/mca/coll/tuned/coll_tuned_module.c +++ b/ompi/mca/coll/tuned/coll_tuned_module.c @@ -105,12 +105,13 @@ ompi_coll_tuned_comm_query(struct ompi_communicator_t *comm, int *priority) */ if (OMPI_COMM_IS_DISJOINT_SET(comm) && OMPI_COMM_IS_DISJOINT(comm)) { tuned_module->super.coll_bcast = ompi_coll_tuned_bcast_intra_disjoint_dec_fixed; + tuned_module->super.coll_allreduce = ompi_coll_tuned_allreduce_intra_disjoint_dec_fixed; } else { tuned_module->super.coll_bcast = ompi_coll_tuned_bcast_intra_dec_fixed; + tuned_module->super.coll_allreduce = ompi_coll_tuned_allreduce_intra_dec_fixed; } tuned_module->super.coll_allgather = ompi_coll_tuned_allgather_intra_dec_fixed; tuned_module->super.coll_allgatherv = ompi_coll_tuned_allgatherv_intra_dec_fixed; - tuned_module->super.coll_allreduce = ompi_coll_tuned_allreduce_intra_dec_fixed; tuned_module->super.coll_alltoall = ompi_coll_tuned_alltoall_intra_dec_fixed; tuned_module->super.coll_alltoallv = ompi_coll_tuned_alltoallv_intra_dec_fixed; tuned_module->super.coll_barrier = ompi_coll_tuned_barrier_intra_dec_fixed; diff --git a/ompi/mca/common/monitoring/monitoring_prof_f.c b/ompi/mca/common/monitoring/monitoring_prof_f.c index 4e193a9b510..622ab3274a5 100644 --- a/ompi/mca/common/monitoring/monitoring_prof_f.c +++ b/ompi/mca/common/monitoring/monitoring_prof_f.c @@ -48,7 +48,7 @@ void monitoring_prof_mpi_finalize_f2c( MPI_Fint *ierr ) { if (NULL != ierr) *ierr = OMPI_INT_2_FINT(c_ierr); } -#if OPAL_HAVE_WEAK_SYMBOLS +#if OPAL_HAVE_WEAK_ALIASES #pragma weak MPI_INIT = monitoring_prof_mpi_init_f2c #pragma weak mpi_init = monitoring_prof_mpi_init_f2c #pragma weak mpi_init_ = monitoring_prof_mpi_init_f2c diff --git a/ompi/mca/common/ompio/common_ompio.h b/ompi/mca/common/ompio/common_ompio.h index 8b12f7b8cc5..09e43cea071 100644 --- a/ompi/mca/common/ompio/common_ompio.h +++ b/ompi/mca/common/ompio/common_ompio.h @@ -17,6 +17,7 @@ * Copyright (c) 2022-2023 Advanced Micro Devices, Inc. All rights reserved. * Copyright (c) 2024 Triad National Security, LLC. All rights * reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -115,6 +116,14 @@ enum ompio_fs_type GPFS = 6 }; +enum mca_common_ompio_info_phase_t +{ + MCA_COMMON_OMPIO_INFO_PHASE_OPEN, + MCA_COMMON_OMPIO_INFO_PHASE_SET_INFO, + MCA_COMMON_OMPIO_INFO_PHASE_SET_VIEW +}; +typedef enum mca_common_ompio_info_phase_t mca_common_ompio_info_phase_t; + typedef struct mca_common_ompio_io_array_t { void *memory_address; /* we need that of type OMPI_MPI_OFFSET_TYPE */ @@ -180,6 +189,7 @@ struct ompio_file_t { opal_convertor_t *f_mem_convertor; opal_convertor_t *f_file_convertor; opal_info_t *f_info; + mca_common_ompio_info_phase_t f_info_phase; void *f_fs_ptr; int f_fs_block_size; int f_atomicity; @@ -327,6 +337,18 @@ OMPI_DECLSPEC int mca_common_ompio_file_delete (const char *filename, struct opal_info_t *info); OMPI_DECLSPEC int mca_common_ompio_create_incomplete_file_handle (const char *filename, ompio_file_t **fh); +OMPI_DECLSPEC int mca_common_ompio_info_subscribe (ompio_file_t *fh, + const char *key, + const char *value, + opal_key_interest_callback_t *callback); +OMPI_DECLSPEC int mca_common_ompio_info_apply (ompio_file_t *fh, + opal_info_t *info); +OMPI_DECLSPEC int mca_common_ompio_info_set (ompio_file_t *fh, + const char *key, + const char *value); +OMPI_DECLSPEC int mca_common_ompio_info_dup (ompio_file_t *fh, + ompi_info_t **info_used); +OMPI_DECLSPEC int mca_common_ompio_info_register (ompio_file_t *fh); OMPI_DECLSPEC int mca_common_ompio_file_close (ompio_file_t *ompio_fh); OMPI_DECLSPEC int mca_common_ompio_file_get_size (ompio_file_t *ompio_fh, OMPI_MPI_OFFSET_TYPE *size); diff --git a/ompi/mca/common/ompio/common_ompio_file_open.c b/ompi/mca/common/ompio/common_ompio_file_open.c index 9104b175e70..1b42546a12d 100644 --- a/ompi/mca/common/ompio/common_ompio_file_open.c +++ b/ompi/mca/common/ompio/common_ompio_file_open.c @@ -17,6 +17,8 @@ * Copyright (c) 2018 DataDirect Networks. All rights reserved. * Copyright (c) 2024 Triad National Security, LLC. All rights * reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -39,6 +41,8 @@ #include "ompi/mca/sharedfp/sharedfp.h" #include "ompi/mca/sharedfp/base/base.h" +#include +#include #include #include #include "common_ompio.h" @@ -50,6 +54,238 @@ static mca_common_ompio_generate_current_file_view_fn_t generate_current_file_view_fn; static mca_common_ompio_get_mca_parameter_value_fn_t get_mca_parameter_value_fn; +/* + * The info subscriber callback both decides whether a key is public in + * MPI_File_get_info and updates the internal field that older OMPIO code + * reads. Keeping that side effect here prevents a second parser from + * drifting away from the value returned to the application. + */ +static const char *mca_common_ompio_cb_buffer_size_cb (opal_infosubscriber_t *object, + const char *key, + const char *value) +{ + ompi_file_t *file; + mca_common_ompio_data_t *data; + ompio_file_t *fh; + int bytes_per_agg; + + if (NULL == object || NULL == value) { + return NULL; + } + + file = (ompi_file_t *) object; + data = (mca_common_ompio_data_t *) file->f_io_selected_data; + if (NULL == data) { + return NULL; + } + fh = &data->ompio_fh; + + if (1 == sscanf(value, "%d", &bytes_per_agg)) { + fh->f_bytes_per_agg = bytes_per_agg; + } + + OMPIO_MCA_PRINT_INFO(fh, key, value, ""); + return value; +} + +static const char *mca_common_ompio_keep_info_value_cb (opal_infosubscriber_t *object, + const char *key, + const char *value) +{ + if (NULL == object || NULL == key || NULL == value) { + return NULL; + } + + return value; +} + +static const char *mca_common_ompio_cb_nodes_cb (opal_infosubscriber_t *object, + const char *key, + const char *value) +{ + if (NULL == object || NULL == key || NULL == value) { + return NULL; + } + + /* + * The num_aggregators MCA default is -1, which means "let OMPIO + * choose automatically." That sentinel is useful internally but is + * not a meaningful MPI_Info value to report back to users. + */ + if (0 == strcmp(value, "-1")) { + return NULL; + } + + return value; +} + +int mca_common_ompio_info_subscribe (ompio_file_t *fh, + const char *key, + const char *value, + opal_key_interest_callback_t *callback) +{ + int ret; + + if (NULL == fh) { + return OMPI_ERR_BAD_PARAM; + } + + /* + * Some nested OMPIO handles, such as private sharedfp handles, do not + * have a backing ompi_file_t. They cannot participate in the public + * MPI_Info subscription mechanism, and treating registration as a + * no-op keeps those internal opens on the old behavior path. + */ + if (NULL == fh->f_fh) { + return OMPI_SUCCESS; + } + + if (NULL == key || NULL == callback) { + return OMPI_ERR_BAD_PARAM; + } + + ret = opal_infosubscribe_subscribe(&fh->f_fh->super, key, value, callback); + if (OPAL_SUCCESS == ret) { + /* + * f_info is intentionally borrowed. The actual storage belongs to + * ompi_file_t::super.s_info so MPI_File_get_info and + * MPI_File_set_info see the same object as the lower OMPIO code. + */ + fh->f_info = fh->f_fh->super.s_info; + } + + return ret; +} + +int mca_common_ompio_info_apply (ompio_file_t *fh, + opal_info_t *info) +{ + int ret; + + if (NULL == fh || NULL == fh->f_fh) { + return OMPI_ERR_BAD_PARAM; + } + + ret = opal_infosubscribe_change_info(&fh->f_fh->super, info); + if (OPAL_SUCCESS == ret) { + fh->f_info = fh->f_fh->super.s_info; + } + + return ret; +} + +int mca_common_ompio_info_set (ompio_file_t *fh, + const char *key, + const char *value) +{ + if (NULL == fh || NULL == key || NULL == value) { + return OMPI_ERR_BAD_PARAM; + } + + /* + * Internal nested OMPIO handles do not have a backing ompi_file_t and + * therefore cannot publish user-visible MPI_Info values. Treat this + * like info_subscribe(): a successful no-op instead of writing into the + * borrowed info object passed to the internal open path. + */ + if (NULL == fh->f_fh) { + return OMPI_SUCCESS; + } + + if (NULL == fh->f_info) { + if (NULL == fh->f_fh->super.s_info) { + return OMPI_ERR_BAD_PARAM; + } + fh->f_info = fh->f_fh->super.s_info; + } + + return opal_info_set(fh->f_info, key, value); +} + +int mca_common_ompio_info_dup (ompio_file_t *fh, + ompi_info_t **info_used) +{ + int ret; + opal_info_t *opal_info_used; + + if (NULL == fh || NULL == info_used) { + return OMPI_ERR_BAD_PARAM; + } + + *info_used = ompi_info_allocate(); + if (NULL == *info_used) { + return OMPI_ERR_OUT_OF_RESOURCE; + } + + if (NULL == fh->f_info) { + if (NULL == fh->f_fh || NULL == fh->f_fh->super.s_info) { + ompi_info_free(info_used); + return OMPI_ERR_BAD_PARAM; + } + fh->f_info = fh->f_fh->super.s_info; + } + + /* + * MPI requires MPI_File_get_info to return a new caller-owned + * MPI_Info handle. Duplicate only public entries; rejected user hints + * remain internal in s_info so components can ignore them without + * exposing them as accepted hints. + */ + opal_info_used = &(*info_used)->super; + ret = opal_info_dup_public(fh->f_info, &opal_info_used); + if (OPAL_SUCCESS != ret) { + ompi_info_free(info_used); + return ret; + } + + return OMPI_SUCCESS; +} + +int mca_common_ompio_info_register (ompio_file_t *fh) +{ + char default_value[32]; + int num_aggregators; + int ret; + int size; + + if (NULL == fh) { + return OMPI_ERR_BAD_PARAM; + } + + if (NULL == fh->f_fh) { + return OMPI_SUCCESS; + } + + size = snprintf(default_value, sizeof(default_value), "%d", fh->f_bytes_per_agg); + if (0 > size || sizeof(default_value) <= (size_t) size) { + return OMPI_ERR_VALUE_OUT_OF_BOUNDS; + } + + ret = mca_common_ompio_info_subscribe (fh, "cb_buffer_size", + default_value, + mca_common_ompio_cb_buffer_size_cb); + if (OMPI_SUCCESS != ret) { + return ret; + } + + num_aggregators = OMPIO_MCA_GET(fh, num_aggregators); + size = snprintf(default_value, sizeof(default_value), "%d", num_aggregators); + if (0 > size || sizeof(default_value) <= (size_t) size) { + return OMPI_ERR_VALUE_OUT_OF_BOUNDS; + } + + ret = mca_common_ompio_info_subscribe (fh, "cb_nodes", + default_value, + mca_common_ompio_cb_nodes_cb); + if (OMPI_SUCCESS != ret) { + return ret; + } + + return mca_common_ompio_info_subscribe (fh, "collective_buffering", + "true", + mca_common_ompio_keep_info_value_cb); +} + int mca_common_ompio_file_open (ompi_communicator_t *comm, const char *filename, int amode, @@ -99,6 +335,7 @@ int mca_common_ompio_file_open (ompi_communicator_t *comm, ompio_fh->f_fstype = NONE; ompio_fh->f_amode = amode; ompio_fh->f_info = info; + ompio_fh->f_info_phase = MCA_COMMON_OMPIO_INFO_PHASE_OPEN; /* set some function pointers required for fcoll, fbtls and sharedfp modules*/ ompio_fh->f_generate_current_file_view = generate_current_file_view_fn; @@ -122,6 +359,10 @@ int mca_common_ompio_file_open (ompi_communicator_t *comm, } mca_common_ompio_set_file_defaults (ompio_fh); + ret = mca_common_ompio_info_register (ompio_fh); + if (OMPI_SUCCESS != ret) { + goto fn_fail; + } ompio_fh->f_split_coll_req = NULL; ompio_fh->f_split_coll_in_use = false; @@ -441,24 +682,16 @@ int mca_common_ompio_set_file_defaults (ompio_file_t *fh) { if (NULL != fh) { - opal_cstring_t *stripe_str; ompi_datatype_t *types[2]; int blocklen[2] = {1, 1}; ptrdiff_t d[2], base; - int i, flag; + int i; fh->f_flags = 0; fh->f_perm = OMPIO_PERM_NULL; fh->f_io_array = NULL; fh->f_bytes_per_agg = OMPIO_MCA_GET(fh, bytes_per_agg); - opal_info_get (fh->f_info, "cb_buffer_size", &stripe_str, &flag); - if ( flag ) { - /* Info object trumps mca parameter value */ - sscanf ( stripe_str->string, "%d", &fh->f_bytes_per_agg ); - OMPIO_MCA_PRINT_INFO(fh, "cb_buffer_size", stripe_str->string, ""); - OBJ_RELEASE(stripe_str); - } fh->f_fs_block_size = 4096; fh->f_atomicity = 0; @@ -502,8 +735,8 @@ int mca_common_ompio_set_file_defaults (ompio_file_t *fh) } ompi_datatype_create_struct (2, - blocklen, - d, + OMPI_COUNT_ARRAY_CREATE(blocklen), + OMPI_DISP_ARRAY_CREATE(d), types, &fh->f_iov_type); ompi_datatype_commit (&fh->f_iov_type); diff --git a/ompi/mca/common/ompio/common_ompio_file_read_all.c b/ompi/mca/common/ompio/common_ompio_file_read_all.c index 1b2f8d6c474..862da9ebeb5 100644 --- a/ompi/mca/common/ompio/common_ompio_file_read_all.c +++ b/ompi/mca/common/ompio/common_ompio_file_read_all.c @@ -16,6 +16,7 @@ * Copyright (c) 2024 Triad National Security, LLC. All rights * reserved. * Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -502,6 +503,9 @@ mca_common_ompio_base_file_read_all (struct ompio_file_t *fh, *****************************************************************/ bytes_received = 0; + /** + * TODO: replace with big count? + */ while (bytes_to_read_in_cycle) { /* This next block identifies which process is the holder ** of the sorted[current_index] element; @@ -774,8 +778,8 @@ mca_common_ompio_base_file_read_all (struct ompio_file_t *fh, send_req[i] = MPI_REQUEST_NULL; if ( 0 < disp_index[i] ) { ompi_datatype_create_hindexed(disp_index[i], - blocklen_per_process[i], - displs_per_process[i], + OMPI_COUNT_ARRAY_CREATE(blocklen_per_process[i]), + OMPI_DISP_ARRAY_CREATE(displs_per_process[i]), MPI_BYTE, &sendtype[i]); ompi_datatype_commit(&sendtype[i]); @@ -854,8 +858,8 @@ mca_common_ompio_base_file_read_all (struct ompio_file_t *fh, } ompi_datatype_create_hindexed(block_index+1, - blocklength_proc, - displs_proc, + OMPI_COUNT_ARRAY_CREATE(blocklength_proc), + OMPI_DISP_ARRAY_CREATE(displs_proc), MPI_BYTE, &newType); ompi_datatype_commit(&newType); diff --git a/ompi/mca/common/ompio/common_ompio_file_view.c b/ompi/mca/common/ompio/common_ompio_file_view.c index 8ea15de14d6..1d5dd557e72 100644 --- a/ompi/mca/common/ompio/common_ompio_file_view.c +++ b/ompi/mca/common/ompio/common_ompio_file_view.c @@ -14,6 +14,8 @@ * and Technology (RIST). All rights reserved. * Copyright (c) 2017 IBM Corporation. All rights reserved. * Copyright (c) 2023 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -34,6 +36,27 @@ static OMPI_MPI_OFFSET_TYPE get_contiguous_chunk_size (ompio_file_t *, int flag); static int datatype_duplicate (ompi_datatype_t *oldtype, ompi_datatype_t **newtype ); +static int get_current_info_value (ompio_file_t *fh, const char *key, + opal_cstring_t **value, int *flag); + +static int get_current_info_value (ompio_file_t *fh, const char *key, + opal_cstring_t **value, int *flag) +{ + int ret; + + *flag = 0; + *value = NULL; + + if (NULL != fh->f_info) { + ret = opal_info_get(fh->f_info, key, value, flag); + if (OPAL_SUCCESS != ret) { + return ret; + } + } + + return OMPI_SUCCESS; +} + static int datatype_duplicate (ompi_datatype_t *oldtype, ompi_datatype_t **newtype ) { ompi_datatype_t *type; @@ -47,8 +70,8 @@ static int datatype_duplicate (ompi_datatype_t *oldtype, ompi_datatype_t **newt ompi_datatype_destroy (&type); return MPI_ERR_INTERN; } - - ompi_datatype_set_args( type, 0, NULL, 0, NULL, 1, &oldtype, MPI_COMBINER_DUP ); + + ompi_datatype_set_args( type, 0, 0, NULL, 0, OMPI_DISP_ARRAY_NULL, 1, &oldtype, MPI_COMBINER_DUP ); *newtype = type; return OMPI_SUCCESS; @@ -70,7 +93,9 @@ int mca_common_ompio_set_view (ompio_file_t *fh, ompi_datatype_t *etype, ompi_datatype_t *filetype, const char *datarep, - opal_info_t *info) + /* Info hints are applied by the caller through the + * subscription path before this helper runs. */ + opal_info_t *info __opal_attribute_unused__) { int ret=OMPI_SUCCESS; size_t max_data = 0; @@ -220,24 +245,15 @@ int mca_common_ompio_set_view (ompio_file_t *fh, } opal_cstring_t *stripe_str; - /* Check the info object set during File_open */ - opal_info_get (info, "cb_nodes", &stripe_str, &flag); + ret = get_current_info_value(fh, "cb_nodes", &stripe_str, &flag); + if (OMPI_SUCCESS != ret) { + goto exit; + } if ( flag ) { sscanf ( stripe_str->string, "%d", &num_cb_nodes ); OMPIO_MCA_PRINT_INFO(fh, "cb_nodes", stripe_str->string, ""); - /* add the key/value to the file's info object */ - opal_info_set_cstring(fh->f_info, "cb_nodes", stripe_str); OBJ_RELEASE(stripe_str); } - else { - /* Check the info object set during file_set_view */ - opal_info_get (fh->f_info, "cb_nodes", &stripe_str, &flag); - if ( flag ) { - sscanf ( stripe_str->string, "%d", &num_cb_nodes ); - OMPIO_MCA_PRINT_INFO(fh, "cb_nodes", stripe_str->string, ""); - OBJ_RELEASE(stripe_str); - } - } if ( -1 != OMPIO_MCA_GET(fh, num_aggregators) || -1 != num_cb_nodes) { @@ -326,7 +342,10 @@ int mca_common_ompio_set_view (ompio_file_t *fh, } bool info_is_set=false; - opal_info_get (info, "collective_buffering", &stripe_str, &flag); + ret = get_current_info_value(fh, "collective_buffering", &stripe_str, &flag); + if (OMPI_SUCCESS != ret) { + goto exit; + } if ( flag ) { if ( 0 == strncasecmp(stripe_str->string, "false", 5) ){ info_is_set = true; @@ -334,20 +353,7 @@ int mca_common_ompio_set_view (ompio_file_t *fh, } else { OMPIO_MCA_PRINT_INFO(fh, "collective_buffering", stripe_str->string, ""); } - /* add the key/value to the file's info object */ - opal_info_set_cstring(fh->f_info, "collective_buffering", stripe_str); OBJ_RELEASE(stripe_str); - } else { - opal_info_get (fh->f_info, "collective_buffering", &stripe_str, &flag); - if ( flag ) { - if ( 0 == strncasecmp(stripe_str->string, "false", 5) ){ - info_is_set = true; - OMPIO_MCA_PRINT_INFO(fh, "collective_buffering", stripe_str->string, "enforcing using individual fcoll component"); - } else { - OMPIO_MCA_PRINT_INFO(fh, "collective_buffering", stripe_str->string, ""); - } - OBJ_RELEASE(stripe_str); - } } mca_fcoll_base_component_t *preferred =NULL; diff --git a/ompi/mca/common/ubcl/Makefile.am b/ompi/mca/common/ubcl/Makefile.am index 0cd4eb083ef..8e97d16ccdc 100644 --- a/ompi/mca/common/ubcl/Makefile.am +++ b/ompi/mca/common/ubcl/Makefile.am @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Bull SAS. All rights reserved. +# Copyright (c) 2025-2026 Bull SAS. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -8,7 +8,7 @@ #AM_CPPFLAGS = $(common_ubcl_CPPFLAGS) -common_ubcl_sources = \ +ompi_common_ubcl_sources = \ common_ubcl.c \ common_ubcl.h @@ -27,14 +27,16 @@ else noinst_LTLIBRARIES += $(comp_noinst) endif -libmca_common_ubcl_la_SOURCES = $(common_ubcl_sources) -libmca_common_ubcl_la_CFLAGS = $(common_ubcl_CFLAGS) -libmca_common_ubcl_la_CPPFLAGS = $(common_ubcl_CPPFLAGS) -libmca_common_ubcl_la_LDFLAGS = $(common_ubcl_LDFLAGS) -libmca_common_ubcl_la_LIBADD = $(common_ubcl_LIBS) \ +libmca_common_ubcl_la_SOURCES = $(ompi_common_ubcl_sources) +libmca_common_ubcl_la_CFLAGS = $(ompi_common_ubcl_CFLAGS) +libmca_common_ubcl_la_CPPFLAGS = $(ompi_common_ubcl_CPPFLAGS) +libmca_common_ubcl_la_LDFLAGS = $(ompi_common_ubcl_LDFLAGS) +libmca_common_ubcl_la_LIBADD = $(ompi_common_ubcl_LIBS) \ $(OPAL_TOP_BUILDDIR)/opal/mca/common/ubcl/lib@OPAL_LIB_NAME@mca_common_ubcl.la -libmca_common_ubcl_noinst_la_SOURCES = $(common_ubcl_sources) +libmca_common_ubcl_noinst_la_SOURCES = $(ompi_common_ubcl_sources) +libmca_common_ubcl_noinst_la_CPPFLAGS = $(ompi_common_ubcl_CPPFLAGS) +libmca_common_ubcl_noinst_la_LDFLAGS = $(ompi_common_ubcl_LDFLAGS) # Conditionally install the header files diff --git a/ompi/mca/common/ubcl/configure.m4 b/ompi/mca/common/ubcl/configure.m4 index 42ba29cf67a..76000dbc7e3 100644 --- a/ompi/mca/common/ubcl/configure.m4 +++ b/ompi/mca/common/ubcl/configure.m4 @@ -1,6 +1,6 @@ # -*- shell-script -*- # -# Copyright (c) 2025 Bull S.A.S. All rights reserved. +# Copyright (c) 2025-2026 Bull S.A.S. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -15,6 +15,24 @@ AC_DEFUN([MCA_ompi_common_ubcl_CONFIG],[ [common_ubcl_happy="yes"], [common_ubcl_happy="no"]) + # By default the tarball build goes through a 'make dist check' step. + # It runs first a configure without any options and OMPI_CHECK_UBCL may find UBCL. + # In that case, UBCL symbols will be included inside binaries such as ompi_info, + # therefore UBCL linking flags are needed in static mode to resolve symbols. + # + # In dynamic mode, UBCL symbols are resolved at runtime: ompi components + # are dlopen-ed lazily and UBCL components initialization starts with a dlopen + # of libubcl.so to load symbols. If it fails UBCL components init returns an error and + # Open MPI will search for other components. UBCL linking flags become optional. + # + # In dynamic mode, linking to the UBCL library is delayed. So common/ubcl can let + # endusers provide a more recent version of UBCL. + # This mode should be preferably selected for UBCL components. + # An mca parameter is exposed to select a specific UBCL path that is dlopen + # at runtime. + AS_IF([test "$compile_mode" = "dso"], + [common_ubcl_LDFLAGS=""], + [AC_MSG_WARN([Only DSO mode of common/ubcl is tested (see --enable-mca-dso)])]) AC_REQUIRE([MCA_opal_common_ubcl_CONFIG]) @@ -23,7 +41,7 @@ AC_DEFUN([MCA_ompi_common_ubcl_CONFIG],[ [$2]) # substitute in the things needed to build ubcl - AC_SUBST([common_ubcl_CPPFLAGS]) - AC_SUBST([common_ubcl_LDFLAGS]) - AC_SUBST([common_ubcl_LIBS]) + AC_SUBST([ompi_common_ubcl_CPPFLAGS]) + AC_SUBST([ompi_common_ubcl_LDFLAGS]) + AC_SUBST([ompi_common_ubcl_LIBS]) ])dnl diff --git a/ompi/mca/fcoll/base/fcoll_base_coll_array.c b/ompi/mca/fcoll/base/fcoll_base_coll_array.c index 68f25ace6fb..6f888c1dd29 100644 --- a/ompi/mca/fcoll/base/fcoll_base_coll_array.c +++ b/ompi/mca/fcoll/base/fcoll_base_coll_array.c @@ -14,6 +14,7 @@ * Copyright (c) 2017 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2017 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -50,7 +51,6 @@ int ompi_fcoll_base_coll_allgatherv_array (void *sbuf, int err = OMPI_SUCCESS; ptrdiff_t extent, lb; int i, rank, j; - int *tmp_rcounts = NULL, *tmp_disps = NULL; char *send_buf = NULL; struct ompi_datatype_t *newtype, *send_type; @@ -93,24 +93,11 @@ int ompi_fcoll_base_coll_allgatherv_array (void *sbuf, return err; } - /* TODO:BIGCOUNT: remove tmp_rcounts and tmp_disps once the ompi_datatype - * interface is udpated to use size_t/ptrdiff_t - */ - tmp_rcounts = (int *)malloc(2 * procs_per_group * sizeof(int)); - if (NULL == tmp_rcounts) { - return OMPI_ERR_OUT_OF_RESOURCE; - } - tmp_disps = tmp_rcounts + procs_per_group; - for (i = 0; i < procs_per_group; i++) { - tmp_rcounts[i] = (int) rcounts[i]; - tmp_disps[i] = (int) disps[i]; - } err = ompi_datatype_create_indexed (procs_per_group, - tmp_rcounts, - tmp_disps, + OMPI_COUNT_ARRAY_CREATE(rcounts), + OMPI_DISP_ARRAY_CREATE(disps), rdtype, &newtype); - free(tmp_rcounts); if (MPI_SUCCESS != err) { return err; } diff --git a/ompi/mca/fcoll/dynamic/fcoll_dynamic_file_write_all.c b/ompi/mca/fcoll/dynamic/fcoll_dynamic_file_write_all.c index 2ce3ef5d27f..0560bdbe2c4 100644 --- a/ompi/mca/fcoll/dynamic/fcoll_dynamic_file_write_all.c +++ b/ompi/mca/fcoll/dynamic/fcoll_dynamic_file_write_all.c @@ -16,6 +16,7 @@ * Copyright (c) 2023 Jeffrey M. Squyres. All rights reserved. * Copyright (c) 2024 Triad National Security, LLC. All rights * reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -784,8 +785,8 @@ mca_fcoll_dynamic_file_write_all (struct ompio_file_t *fh, recv_req[i] = MPI_REQUEST_NULL; if ( 0 < disp_index[i] ) { ompi_datatype_create_hindexed(disp_index[i], - blocklen_per_process[i], - displs_per_process[i], + OMPI_COUNT_ARRAY_CREATE(blocklen_per_process[i]), + OMPI_DISP_ARRAY_CREATE(displs_per_process[i]), MPI_BYTE, &recvtype[i]); ompi_datatype_commit(&recvtype[i]); diff --git a/ompi/mca/fcoll/dynamic_gen2/fcoll_dynamic_gen2_file_write_all.c b/ompi/mca/fcoll/dynamic_gen2/fcoll_dynamic_gen2_file_write_all.c index 1f9b5f8bef7..bcd5195148b 100644 --- a/ompi/mca/fcoll/dynamic_gen2/fcoll_dynamic_gen2_file_write_all.c +++ b/ompi/mca/fcoll/dynamic_gen2/fcoll_dynamic_gen2_file_write_all.c @@ -17,6 +17,7 @@ * Copyright (c) 2023 Jeffrey M. Squyres. All rights reserved. * Copyright (c) 2024 Triad National Security, LLC. All rights * reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -1160,13 +1161,13 @@ static int shuffle_init ( int index, int cycles, int aggregator, int rank, mca_i reqs[i] = MPI_REQUEST_NULL; if ( 0 < data->disp_index[i] ) { ompi_datatype_create_hindexed(data->disp_index[i], - data->blocklen_per_process[i], - data->displs_per_process[i], + OMPI_COUNT_ARRAY_CREATE(data->blocklen_per_process[i]), + OMPI_DISP_ARRAY_CREATE(data->displs_per_process[i]), MPI_BYTE, &data->recvtype[i]); ompi_datatype_commit(&data->recvtype[i]); opal_datatype_type_size(&data->recvtype[i]->super, &datatype_size); - + if (datatype_size){ ret = MCA_PML_CALL(irecv(data->global_buf, 1, @@ -1240,8 +1241,8 @@ static int shuffle_init ( int index, int cycles, int aggregator, int rank, mca_i if ( 0 <= block_index ) { ompi_datatype_create_hindexed(block_index+1, - blocklength_proc, - displs_proc, + OMPI_COUNT_ARRAY_CREATE(blocklength_proc), + OMPI_DISP_ARRAY_CREATE(displs_proc), MPI_BYTE, &newType); ompi_datatype_commit(&newType); diff --git a/ompi/mca/fcoll/vulcan/fcoll_vulcan_file_read_all.c b/ompi/mca/fcoll/vulcan/fcoll_vulcan_file_read_all.c index f6a492e621c..ebef7b3cb92 100644 --- a/ompi/mca/fcoll/vulcan/fcoll_vulcan_file_read_all.c +++ b/ompi/mca/fcoll/vulcan/fcoll_vulcan_file_read_all.c @@ -16,6 +16,7 @@ * Copyright (c) 2024 Triad National Security, LLC. All rights * reserved. * Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -840,8 +841,8 @@ static int shuffle_init (int index, int cycles, int aggregator, int rank, mca_io reqs[i] = MPI_REQUEST_NULL; if (0 < data->disp_index[i]) { ompi_datatype_create_hindexed (data->disp_index[i], - data->blocklen_per_process[i], - data->displs_per_process[i], + OMPI_COUNT_ARRAY_CREATE(data->blocklen_per_process[i]), + OMPI_DISP_ARRAY_CREATE(data->displs_per_process[i]), MPI_BYTE, &data->recvtype[i]); ompi_datatype_commit (&data->recvtype[i]); @@ -918,8 +919,8 @@ static int shuffle_init (int index, int cycles, int aggregator, int rank, mca_io if (0 <= block_index) { ompi_datatype_create_hindexed (block_index+1, - blocklength_proc, - displs_proc, + OMPI_COUNT_ARRAY_CREATE(blocklength_proc), + OMPI_DISP_ARRAY_CREATE(displs_proc), MPI_BYTE, &newType); ompi_datatype_commit (&newType); diff --git a/ompi/mca/fcoll/vulcan/fcoll_vulcan_file_write_all.c b/ompi/mca/fcoll/vulcan/fcoll_vulcan_file_write_all.c index b6e9be6d2ca..61aa8b32920 100644 --- a/ompi/mca/fcoll/vulcan/fcoll_vulcan_file_write_all.c +++ b/ompi/mca/fcoll/vulcan/fcoll_vulcan_file_write_all.c @@ -17,6 +17,7 @@ * Copyright (c) 2024 Triad National Security, LLC. All rights * reserved. * Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -833,8 +834,8 @@ static int shuffle_init (int index, int num_cycles, int aggregator, int rank, reqs[i] = MPI_REQUEST_NULL; if (0 < data->disp_index[i]) { ompi_datatype_create_hindexed(data->disp_index[i], - data->blocklen_per_process[i], - data->displs_per_process[i], + OMPI_COUNT_ARRAY_CREATE(data->blocklen_per_process[i]), + OMPI_DISP_ARRAY_CREATE(data->displs_per_process[i]), MPI_BYTE, &data->recvtype[i]); ompi_datatype_commit(&data->recvtype[i]); @@ -909,8 +910,8 @@ static int shuffle_init (int index, int num_cycles, int aggregator, int rank, if ( 0 <= block_index ) { ompi_datatype_create_hindexed(block_index+1, - blocklength_proc, - displs_proc, + OMPI_COUNT_ARRAY_CREATE(blocklength_proc), + OMPI_DISP_ARRAY_CREATE(displs_proc), MPI_BYTE, &newType); ompi_datatype_commit(&newType); diff --git a/ompi/mca/fs/gpfs/fs_gpfs.c b/ompi/mca/fs/gpfs/fs_gpfs.c index a1bf194f3e3..46c42b56e58 100644 --- a/ompi/mca/fs/gpfs/fs_gpfs.c +++ b/ompi/mca/fs/gpfs/fs_gpfs.c @@ -12,6 +12,7 @@ * Copyright (c) 2008-2018 University of Houston. All rights reserved. * Copyright (c) 2018 Research Organization for Information Science * and Technology (RIST). All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -138,5 +139,6 @@ int mca_fs_gpfs_module_init (ompio_file_t *file) int mca_fs_gpfs_module_finalize (ompio_file_t *file) { + mca_fs_gpfs_info_cache_free(file); return OMPI_SUCCESS; } diff --git a/ompi/mca/fs/gpfs/fs_gpfs.h b/ompi/mca/fs/gpfs/fs_gpfs.h index f34de540a7b..2d5bdf11a68 100644 --- a/ompi/mca/fs/gpfs/fs_gpfs.h +++ b/ompi/mca/fs/gpfs/fs_gpfs.h @@ -11,6 +11,7 @@ * Copyright (c) 2004-2005 The Regents of the University of California. * All rights reserved. * Copyright (c) 2008-2012 University of Houston. All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -40,6 +41,7 @@ int mca_fs_gpfs_component_file_unquery(ompio_file_t *file); int mca_fs_gpfs_module_init(ompio_file_t *file); int mca_fs_gpfs_module_finalize(ompio_file_t *file); +void mca_fs_gpfs_info_cache_free(ompio_file_t *file); OMPI_DECLSPEC extern mca_fs_base_component_2_0_0_t mca_fs_gpfs_component; /* diff --git a/ompi/mca/fs/gpfs/fs_gpfs_file_open.c b/ompi/mca/fs/gpfs/fs_gpfs_file_open.c index 497bc402f7a..78f80921e8f 100644 --- a/ompi/mca/fs/gpfs/fs_gpfs_file_open.c +++ b/ompi/mca/fs/gpfs/fs_gpfs_file_open.c @@ -10,6 +10,7 @@ * Copyright (c) 2004-2005 The Regents of the University of California. * All rights reserved. * Copyright (c) 2008-2012 University of Houston. All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -38,6 +39,332 @@ #include #include #include +#include +#include + +struct mca_fs_gpfs_info_cache_t { + char *use_siox_lib; + char *gpfs_access_range; + char *gpfs_free_range; + char *gpfs_clear_file_cache; + char *gpfs_cancel_hints; + char *gpfs_set_replication; + char *gpfs_byte_range; + char *gpfs_restripe_data; +#ifdef HAVE_C_SIOX_H + char *siox_access_range; + char *siox_free_range; + char *siox_clear_file_cache; + char *siox_cancel_hints; + char *siox_data_ship_start; + char *siox_data_ship_stop; + char *siox_set_replication; + char *siox_byte_range; + char *siox_restripe_data; +#endif +}; + +static const char *const mca_fs_gpfs_hints[] = { + "useSIOXLib", + "gpfsAccessRange", + "gpfsFreeRange", + "gpfsClearFileCache", + "gpfsCancelHints", + "gpfsSetReplication", + "gpfsByteRange", + "gpfsRestripeData", + NULL +}; + +#ifdef HAVE_C_SIOX_H +static const char *const mca_fs_gpfs_siox_hints[] = { + "sioxAccessRange", + "sioxFreeRange", + "sioxClearFileCache", + "sioxCancelHints", + "sioxDataShipStart", + "sioxDataShipStop", + "sioxSetReplication", + "sioxByteRange", + "sioxRestripeData", + NULL +}; +#endif + +/* + * GPFS hint names are intentionally registered only in the GPFS component. + * Keep the cache keyed by the public hint strings so callback registration, + * open-time remember logic, and final cleanup all agree on ownership without + * teaching OMPIO common code about GPFS-specific names. + */ +static char **mca_fs_gpfs_info_cache_slot(ompio_file_t *fh, const char *key) +{ + struct mca_fs_gpfs_info_cache_t *cache; + + if (NULL == fh || NULL == fh->f_fs_ptr || NULL == key) { + return NULL; + } + + cache = (struct mca_fs_gpfs_info_cache_t *) fh->f_fs_ptr; + if (0 == strcmp(key, "useSIOXLib")) { + return &cache->use_siox_lib; + } + if (0 == strcmp(key, "gpfsAccessRange")) { + return &cache->gpfs_access_range; + } + if (0 == strcmp(key, "gpfsFreeRange")) { + return &cache->gpfs_free_range; + } + if (0 == strcmp(key, "gpfsClearFileCache")) { + return &cache->gpfs_clear_file_cache; + } + if (0 == strcmp(key, "gpfsCancelHints")) { + return &cache->gpfs_cancel_hints; + } + if (0 == strcmp(key, "gpfsSetReplication")) { + return &cache->gpfs_set_replication; + } + if (0 == strcmp(key, "gpfsByteRange")) { + return &cache->gpfs_byte_range; + } + if (0 == strcmp(key, "gpfsRestripeData")) { + return &cache->gpfs_restripe_data; + } +#ifdef HAVE_C_SIOX_H + if (0 == strcmp(key, "sioxAccessRange")) { + return &cache->siox_access_range; + } + if (0 == strcmp(key, "sioxFreeRange")) { + return &cache->siox_free_range; + } + if (0 == strcmp(key, "sioxClearFileCache")) { + return &cache->siox_clear_file_cache; + } + if (0 == strcmp(key, "sioxCancelHints")) { + return &cache->siox_cancel_hints; + } + if (0 == strcmp(key, "sioxDataShipStart")) { + return &cache->siox_data_ship_start; + } + if (0 == strcmp(key, "sioxDataShipStop")) { + return &cache->siox_data_ship_stop; + } + if (0 == strcmp(key, "sioxSetReplication")) { + return &cache->siox_set_replication; + } + if (0 == strcmp(key, "sioxByteRange")) { + return &cache->siox_byte_range; + } + if (0 == strcmp(key, "sioxRestripeData")) { + return &cache->siox_restripe_data; + } +#endif + + return NULL; +} + +static const char *mca_fs_gpfs_get_cached_info(ompio_file_t *fh, const char *key) +{ + char **slot; + + slot = mca_fs_gpfs_info_cache_slot(fh, key); + if (NULL == slot) { + return NULL; + } + + return *slot; +} + +static int mca_fs_gpfs_info_cache_alloc(ompio_file_t *fh) +{ + if (NULL == fh) { + return OMPI_ERR_BAD_PARAM; + } + + if (NULL != fh->f_fs_ptr) { + return OMPI_SUCCESS; + } + + fh->f_fs_ptr = calloc(1, sizeof(struct mca_fs_gpfs_info_cache_t)); + if (NULL == fh->f_fs_ptr) { + return OMPI_ERR_OUT_OF_RESOURCE; + } + + return OMPI_SUCCESS; +} + +static int mca_fs_gpfs_remember_info(ompio_file_t *fh, const char *key) +{ + char **slot; + char *copy; + int flag; + opal_cstring_t *value_str; + + slot = mca_fs_gpfs_info_cache_slot(fh, key); + if (NULL == slot) { + return OMPI_ERR_BAD_PARAM; + } + + opal_info_get(fh->f_info, key, &value_str, &flag); + if (!flag) { + return OMPI_SUCCESS; + } + + /* + * The subscriber callback can only return borrowed/static storage. + * Store a per-file copy of each accepted open-time value so callbacks + * during later phases can return the actual value that was applied. + */ + copy = strdup(value_str->string); + OBJ_RELEASE(value_str); + if (NULL == copy) { + return OMPI_ERR_OUT_OF_RESOURCE; + } + + free(*slot); + *slot = copy; + return OMPI_SUCCESS; +} + +void mca_fs_gpfs_info_cache_free(ompio_file_t *file) +{ + struct mca_fs_gpfs_info_cache_t *cache; + + if (NULL == file || NULL == file->f_fs_ptr) { + return; + } + + cache = (struct mca_fs_gpfs_info_cache_t *) file->f_fs_ptr; + free(cache->use_siox_lib); + free(cache->gpfs_access_range); + free(cache->gpfs_free_range); + free(cache->gpfs_clear_file_cache); + free(cache->gpfs_cancel_hints); + free(cache->gpfs_set_replication); + free(cache->gpfs_byte_range); + free(cache->gpfs_restripe_data); +#ifdef HAVE_C_SIOX_H + free(cache->siox_access_range); + free(cache->siox_free_range); + free(cache->siox_clear_file_cache); + free(cache->siox_cancel_hints); + free(cache->siox_data_ship_start); + free(cache->siox_data_ship_stop); + free(cache->siox_set_replication); + free(cache->siox_byte_range); + free(cache->siox_restripe_data); +#endif + free(cache); + file->f_fs_ptr = NULL; +} + +static const char *mca_fs_gpfs_hint_cb(opal_infosubscriber_t *object, + const char *key, + const char *value) +{ + ompi_file_t *file; + mca_common_ompio_data_t *data; + ompio_file_t *fh; + + if (NULL == object || NULL == key || NULL == value) { + return NULL; + } + + file = (ompi_file_t *) object; + data = (mca_common_ompio_data_t *) file->f_io_selected_data; + if (NULL == data) { + return NULL; + } + fh = &data->ompio_fh; + + /* + * The existing GPFS code applies these hints only during file open. + * Later MPI_File_set_info or MPI_File_set_view calls should not make a + * new value public unless GPFS actually consumed it, so report the + * cached open-time value outside the OPEN phase. + */ + if (MCA_COMMON_OMPIO_INFO_PHASE_OPEN != fh->f_info_phase) { + return mca_fs_gpfs_get_cached_info(fh, key); + } + + if ('\0' == value[0]) { + return NULL; + } + + return value; +} + +static int mca_fs_gpfs_subscribe_hints(ompio_file_t *fh, const char *const *hints) +{ + int ret; + + for (int i = 0; NULL != hints[i]; ++i) { + ret = mca_common_ompio_info_subscribe(fh, hints[i], NULL, + mca_fs_gpfs_hint_cb); + if (OMPI_SUCCESS != ret) { + return ret; + } + } + + return OMPI_SUCCESS; +} + +static int mca_fs_gpfs_remember_hints(ompio_file_t *fh, const char *const *hints) +{ + int ret; + + for (int i = 0; NULL != hints[i]; ++i) { + ret = mca_fs_gpfs_remember_info(fh, hints[i]); + if (OMPI_SUCCESS != ret) { + return ret; + } + } + + return OMPI_SUCCESS; +} + +static int mca_fs_gpfs_register_info(ompio_file_t *fh) +{ + int ret; + + ret = mca_fs_gpfs_subscribe_hints(fh, mca_fs_gpfs_hints); + if (OMPI_SUCCESS != ret) { + return ret; + } + +#ifdef HAVE_C_SIOX_H + ret = mca_fs_gpfs_subscribe_hints(fh, mca_fs_gpfs_siox_hints); + if (OMPI_SUCCESS != ret) { + return ret; + } +#endif + + return OMPI_SUCCESS; +} + +static int mca_fs_gpfs_cache_info(ompio_file_t *fh) +{ + int ret; + + /* + * Registration makes supported keys public; this second pass records + * only the values that survived registration so get_info remains tied + * to what the component accepted. + */ + ret = mca_fs_gpfs_remember_hints(fh, mca_fs_gpfs_hints); + if (OMPI_SUCCESS != ret) { + return ret; + } + +#ifdef HAVE_C_SIOX_H + ret = mca_fs_gpfs_remember_hints(fh, mca_fs_gpfs_siox_hints); + if (OMPI_SUCCESS != ret) { + return ret; + } +#endif + + return OMPI_SUCCESS; +} int mca_fs_gpfs_file_open (struct ompi_communicator_t *comm, @@ -52,6 +379,21 @@ mca_fs_gpfs_file_open (struct ompi_communicator_t *comm, perm = mca_fs_base_get_file_perm(fh); amode = mca_fs_base_get_file_amode(fh->f_rank, access_mode); + ret = mca_fs_gpfs_info_cache_alloc(fh); + if (OMPI_SUCCESS != ret) { + return ret; + } + + ret = mca_fs_gpfs_register_info(fh); + if (OMPI_SUCCESS != ret) { + return ret; + } + + ret = mca_fs_gpfs_cache_info(fh); + if (OMPI_SUCCESS != ret) { + return ret; + } + if(OMPIO_ROOT == fh->f_rank) { fh->fd = open (filename, amode, perm); if ( 0 > fh->fd ) { diff --git a/ompi/mca/fs/lustre/fs_lustre.c b/ompi/mca/fs/lustre/fs_lustre.c index 494114416b4..139b3b75efb 100644 --- a/ompi/mca/fs/lustre/fs_lustre.c +++ b/ompi/mca/fs/lustre/fs_lustre.c @@ -13,6 +13,7 @@ * Copyright (c) 2018 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2018 DataDirect Networks. All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -125,5 +126,6 @@ int mca_fs_lustre_module_init (ompio_file_t *file) int mca_fs_lustre_module_finalize (ompio_file_t *file) { + mca_fs_lustre_info_cache_free(file); return OMPI_SUCCESS; } diff --git a/ompi/mca/fs/lustre/fs_lustre.h b/ompi/mca/fs/lustre/fs_lustre.h index ebd9da168b7..dcdabe1b127 100644 --- a/ompi/mca/fs/lustre/fs_lustre.h +++ b/ompi/mca/fs/lustre/fs_lustre.h @@ -14,6 +14,7 @@ * and Technology (RIST). All rights reserved. * Copyright (c) 2016-2017 IBM Corporation. All rights reserved. * Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserverd. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -57,6 +58,7 @@ int mca_fs_lustre_component_file_unquery (ompio_file_t *file); int mca_fs_lustre_module_init (ompio_file_t *file); int mca_fs_lustre_module_finalize (ompio_file_t *file); +void mca_fs_lustre_info_cache_free (ompio_file_t *file); OMPI_DECLSPEC extern mca_fs_base_component_2_0_0_t mca_fs_lustre_component; /* diff --git a/ompi/mca/fs/lustre/fs_lustre_file_open.c b/ompi/mca/fs/lustre/fs_lustre_file_open.c index 9318825987e..e9c9b8c2d0d 100644 --- a/ompi/mca/fs/lustre/fs_lustre_file_open.c +++ b/ompi/mca/fs/lustre/fs_lustre_file_open.c @@ -14,6 +14,7 @@ * and Technology (RIST). All rights reserved. * Copyright (c) 2016-2017 IBM Corporation. All rights reserved. * Copyright (c) 2024 Advanced Micro Devices, Inc. All rights reserverd. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -35,8 +36,34 @@ #include "ompi/info/info.h" #include +#include +#include +#include static void *alloc_lum(void); +struct mca_fs_lustre_info_cache_t; +static const char *mca_fs_lustre_layout_hint_cb(opal_infosubscriber_t *object, + const char *key, + const char *value); +static const char *mca_fs_lustre_stripe_size_cb(opal_infosubscriber_t *object, + const char *key, + const char *value); +static const char *mca_fs_lustre_stripe_width_cb(opal_infosubscriber_t *object, + const char *key, + const char *value); +static const char *mca_fs_lustre_get_cached_info(ompio_file_t *fh, + const char *key); +static bool mca_fs_lustre_info_has_key(opal_infosubscriber_t *object, + const char *key); +static int mca_fs_lustre_info_cache_alloc(ompio_file_t *fh); +static int mca_fs_lustre_remember_info(ompio_file_t *fh, const char *key); +static int mca_fs_lustre_remember_info_value(ompio_file_t *fh, const char *key, + const char *value); +static int mca_fs_lustre_register_info(ompio_file_t *fh); +static int mca_fs_lustre_parse_int_hint(opal_info_t *info, const char *key, + int *value); +static int mca_fs_lustre_set_default_hint(ompio_file_t *fh, const char *key, + int value); static void *alloc_lum(void) { @@ -50,6 +77,290 @@ static void *alloc_lum(void) return malloc(MAX(v1, v3)); } +struct mca_fs_lustre_info_cache_t { + char *striping_unit; + char *striping_factor; + char *stripe_size; + char *stripe_width; +}; + +/* + * Lustre layout hints are consumed while creating/opening the file. They + * cannot be safely applied later to an already-open file, so post-open + * callbacks return the open-time cached value instead of the new user value. + * Returning NULL tells the subscriber layer that the new value was not + * accepted and therefore must not become public in MPI_File_get_info. + */ +static const char *mca_fs_lustre_layout_hint_cb(opal_infosubscriber_t *object, + const char *key, + const char *value) +{ + ompi_file_t *file; + mca_common_ompio_data_t *data; + ompio_file_t *fh; + + if (NULL == object || NULL == key || NULL == value) { + return NULL; + } + + file = (ompi_file_t *) object; + data = (mca_common_ompio_data_t *) file->f_io_selected_data; + if (NULL == data) { + return NULL; + } + fh = &data->ompio_fh; + + if (MCA_COMMON_OMPIO_INFO_PHASE_OPEN != fh->f_info_phase) { + return mca_fs_lustre_get_cached_info(fh, key); + } + + if ('\0' == value[0]) { + return NULL; + } + + return value; +} + +static bool mca_fs_lustre_info_has_key(opal_infosubscriber_t *object, + const char *key) +{ + int flag; + opal_cstring_t *value; + + if (NULL == object || NULL == object->s_info || NULL == key) { + return false; + } + + opal_info_get(object->s_info, key, &value, &flag); + if (!flag) { + return false; + } + + OBJ_RELEASE(value); + return true; +} + +static const char *mca_fs_lustre_stripe_size_cb(opal_infosubscriber_t *object, + const char *key, + const char *value) +{ + /* + * If both MPI-IO canonical spelling and the older OMPIO alias are + * present, keep the canonical key public. This preserves the existing + * parser precedence and gives get_info a single accepted spelling. + */ + if (mca_fs_lustre_info_has_key(object, "striping_unit")) { + return NULL; + } + + return mca_fs_lustre_layout_hint_cb(object, key, value); +} + +static const char *mca_fs_lustre_stripe_width_cb(opal_infosubscriber_t *object, + const char *key, + const char *value) +{ + /* + * Same rule as stripe_size: the MPI spelling wins over the historical + * alias when both are supplied. + */ + if (mca_fs_lustre_info_has_key(object, "striping_factor")) { + return NULL; + } + + return mca_fs_lustre_layout_hint_cb(object, key, value); +} + +static char **mca_fs_lustre_info_cache_slot(ompio_file_t *fh, const char *key) +{ + struct mca_fs_lustre_info_cache_t *cache; + + if (NULL == fh || NULL == fh->f_fs_ptr || NULL == key) { + return NULL; + } + + cache = (struct mca_fs_lustre_info_cache_t *) fh->f_fs_ptr; + if (0 == strcmp(key, "striping_unit")) { + return &cache->striping_unit; + } + if (0 == strcmp(key, "striping_factor")) { + return &cache->striping_factor; + } + if (0 == strcmp(key, "stripe_size")) { + return &cache->stripe_size; + } + if (0 == strcmp(key, "stripe_width")) { + return &cache->stripe_width; + } + + return NULL; +} + +static const char *mca_fs_lustre_get_cached_info(ompio_file_t *fh, + const char *key) +{ + char **slot; + + slot = mca_fs_lustre_info_cache_slot(fh, key); + if (NULL == slot) { + return NULL; + } + + return *slot; +} + +static int mca_fs_lustre_info_cache_alloc(ompio_file_t *fh) +{ + if (NULL == fh) { + return OMPI_ERR_BAD_PARAM; + } + + if (NULL != fh->f_fs_ptr) { + return OMPI_SUCCESS; + } + + fh->f_fs_ptr = calloc(1, sizeof(struct mca_fs_lustre_info_cache_t)); + if (NULL == fh->f_fs_ptr) { + return OMPI_ERR_OUT_OF_RESOURCE; + } + + return OMPI_SUCCESS; +} + +static int mca_fs_lustre_remember_info_value(ompio_file_t *fh, const char *key, + const char *value) +{ + char **slot; + char *copy; + + if (NULL == value) { + return OMPI_ERR_BAD_PARAM; + } + + slot = mca_fs_lustre_info_cache_slot(fh, key); + if (NULL == slot) { + return OMPI_ERR_BAD_PARAM; + } + + copy = strdup(value); + if (NULL == copy) { + return OMPI_ERR_OUT_OF_RESOURCE; + } + + free(*slot); + *slot = copy; + return OMPI_SUCCESS; +} + +static int mca_fs_lustre_remember_info(ompio_file_t *fh, const char *key) +{ + int flag; + int ret; + opal_cstring_t *value_str; + + opal_info_get(fh->f_info, key, &value_str, &flag); + if (!flag) { + return OMPI_SUCCESS; + } + + ret = mca_fs_lustre_remember_info_value(fh, key, value_str->string); + OBJ_RELEASE(value_str); + return ret; +} + +void mca_fs_lustre_info_cache_free(ompio_file_t *file) +{ + struct mca_fs_lustre_info_cache_t *cache; + + if (NULL == file || NULL == file->f_fs_ptr) { + return; + } + + cache = (struct mca_fs_lustre_info_cache_t *) file->f_fs_ptr; + free(cache->striping_unit); + free(cache->striping_factor); + free(cache->stripe_size); + free(cache->stripe_width); + free(cache); + file->f_fs_ptr = NULL; +} + +static int mca_fs_lustre_register_info(ompio_file_t *fh) +{ + int ret; + + ret = mca_common_ompio_info_subscribe(fh, "striping_unit", NULL, + mca_fs_lustre_layout_hint_cb); + if (OMPI_SUCCESS != ret) { + return ret; + } + + ret = mca_common_ompio_info_subscribe(fh, "stripe_size", NULL, + mca_fs_lustre_stripe_size_cb); + if (OMPI_SUCCESS != ret) { + return ret; + } + + ret = mca_common_ompio_info_subscribe(fh, "striping_factor", NULL, + mca_fs_lustre_layout_hint_cb); + if (OMPI_SUCCESS != ret) { + return ret; + } + + return mca_common_ompio_info_subscribe(fh, "stripe_width", NULL, + mca_fs_lustre_stripe_width_cb); +} + +static int mca_fs_lustre_parse_int_hint(opal_info_t *info, const char *key, + int *value) +{ + int flag; + opal_cstring_t *value_str; + + opal_info_get(info, key, &value_str, &flag); + if (!flag) { + return 0; + } + + if (1 == sscanf(value_str->string, "%d", value)) { + OBJ_RELEASE(value_str); + return 1; + } + + OBJ_RELEASE(value_str); + return 0; +} + +static int mca_fs_lustre_set_default_hint(ompio_file_t *fh, const char *key, + int value) +{ + char value_string[32]; + int ret; + int size; + + if (0 >= value) { + return OMPI_SUCCESS; + } + + size = snprintf(value_string, sizeof(value_string), "%d", value); + if (0 > size || sizeof(value_string) <= (size_t) size) { + return OMPI_ERR_VALUE_OUT_OF_BOUNDS; + } + + ret = mca_common_ompio_info_set(fh, key, value_string); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* + * MCA defaults did not come from the user's MPI_Info, so report them + * with the canonical public name. Cache the same value so later + * set_info/set_view attempts cannot overwrite get_info with an + * unapplied layout request. + */ + return mca_fs_lustre_remember_info_value(fh, key, value_string); +} + /* * file_open_lustre * @@ -62,55 +373,88 @@ int mca_fs_lustre_file_open (struct ompi_communicator_t *comm, const char* filename, int access_mode, - struct opal_info_t *info, + /* Info hints are applied through the OMPIO subscription + * path; this component reads the accepted values from + * fh->f_info and its private cache. */ + struct opal_info_t *info __opal_attribute_unused__, ompio_file_t *fh) { int amode, perm; int rc, ret=OMPI_SUCCESS; - int flag; int fs_lustre_stripe_size = -1; int fs_lustre_stripe_width = -1; - opal_cstring_t *stripe_str; char *rfilename = (char *)filename; struct lov_user_md *lump=NULL; perm = mca_fs_base_get_file_perm(fh); amode = mca_fs_base_get_file_amode(fh->f_rank, access_mode); - opal_info_get (info, "striping_unit", &stripe_str, &flag); - if ( flag ) { - sscanf ( stripe_str->string, "%d", &fs_lustre_stripe_size ); - OBJ_RELEASE(stripe_str); + ret = mca_fs_lustre_info_cache_alloc(fh); + if (OMPI_SUCCESS != ret) { + return ret; } - else { - //internal info object name used earlier. Kept for backwards compatibility. - opal_info_get (info, "stripe_size", &stripe_str, &flag); - if ( flag ) { - sscanf ( stripe_str->string, "%d", &fs_lustre_stripe_size ); - OBJ_RELEASE(stripe_str); - } + + ret = mca_fs_lustre_register_info(fh); + if (OMPI_SUCCESS != ret) { + return ret; } - - opal_info_get (info, "striping_factor", &stripe_str, &flag); - if ( flag ) { - sscanf ( stripe_str->string, "%d", &fs_lustre_stripe_width ); - OBJ_RELEASE(stripe_str); + + /* + * Preserve whichever spelling was actually accepted from MPI_Info. + * The parser still prefers canonical spellings over aliases to match + * the old behavior, but aliases remain visible when they were the + * accepted user spelling. + */ + if (mca_fs_lustre_parse_int_hint(fh->f_info, "striping_unit", + &fs_lustre_stripe_size)) { + ret = mca_fs_lustre_remember_info(fh, "striping_unit"); + if (OMPI_SUCCESS != ret) { + return ret; + } + } else { + // internal info object name used earlier. Kept for backwards compatibility. + if (mca_fs_lustre_parse_int_hint(fh->f_info, "stripe_size", + &fs_lustre_stripe_size)) { + ret = mca_fs_lustre_remember_info(fh, "stripe_size"); + if (OMPI_SUCCESS != ret) { + return ret; + } + } } - else { - //internal info object name used earlier. Kept for backwards compatibility. - opal_info_get (info, "stripe_width", &stripe_str, &flag); - if ( flag ) { - sscanf ( stripe_str->string, "%d", &fs_lustre_stripe_width ); - OBJ_RELEASE(stripe_str); + + if (mca_fs_lustre_parse_int_hint(fh->f_info, "striping_factor", + &fs_lustre_stripe_width)) { + ret = mca_fs_lustre_remember_info(fh, "striping_factor"); + if (OMPI_SUCCESS != ret) { + return ret; + } + } else { + // internal info object name used earlier. Kept for backwards compatibility. + if (mca_fs_lustre_parse_int_hint(fh->f_info, "stripe_width", + &fs_lustre_stripe_width)) { + ret = mca_fs_lustre_remember_info(fh, "stripe_width"); + if (OMPI_SUCCESS != ret) { + return ret; + } } } - + if (fs_lustre_stripe_size < 0) { fs_lustre_stripe_size = mca_fs_lustre_stripe_size; + ret = mca_fs_lustre_set_default_hint(fh, "striping_unit", + fs_lustre_stripe_size); + if (OMPI_SUCCESS != ret) { + return ret; + } } if (fs_lustre_stripe_width < 0) { fs_lustre_stripe_width = mca_fs_lustre_stripe_width; + ret = mca_fs_lustre_set_default_hint(fh, "striping_factor", + fs_lustre_stripe_width); + if (OMPI_SUCCESS != ret) { + return ret; + } } /* Check for soft links and replace filename by the actual diff --git a/ompi/mca/io/ompio/io_ompio.c b/ompi/mca/io/ompio/io_ompio.c index 506b6897e46..bb8ba0a695a 100644 --- a/ompi/mca/io/ompio/io_ompio.c +++ b/ompi/mca/io/ompio/io_ompio.c @@ -16,6 +16,7 @@ * Copyright (c) 2015-2018 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -183,8 +184,8 @@ int ompi_io_ompio_generate_current_file_view (struct ompio_file_t *fh, d[i] -= base; } ompi_datatype_create_struct (3, - blocklen, - d, + OMPI_COUNT_ARRAY_CREATE(blocklen), + OMPI_DISP_ARRAY_CREATE(d), types, &io_array_type); ompi_datatype_commit (&io_array_type); diff --git a/ompi/mca/io/ompio/io_ompio.h b/ompi/mca/io/ompio/io_ompio.h index d38bfb1acb3..302310966b9 100644 --- a/ompi/mca/io/ompio/io_ompio.h +++ b/ompi/mca/io/ompio/io_ompio.h @@ -17,6 +17,7 @@ * Copyright (c) 2022-2024 Advanced Micro Devices, Inc. All rights reserved. * Copyright (c) 2024 Triad National Security, LLC. All rights * reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -162,6 +163,10 @@ int mca_io_ompio_file_get_size (struct ompi_file_t *fh, OMPI_MPI_OFFSET_TYPE * size); int mca_io_ompio_file_get_amode (struct ompi_file_t *fh, int *amode); +int mca_io_ompio_file_set_info (struct ompi_file_t *fh, + struct ompi_info_t *info); +int mca_io_ompio_file_get_info (struct ompi_file_t *fh, + struct ompi_info_t **info_used); int mca_io_ompio_file_sync (struct ompi_file_t *fh); int mca_io_ompio_file_seek (struct ompi_file_t *fh, OMPI_MPI_OFFSET_TYPE offet, diff --git a/ompi/mca/io/ompio/io_ompio_file_open.c b/ompi/mca/io/ompio/io_ompio_file_open.c index 76e4eb6c497..a2e05a91b93 100644 --- a/ompi/mca/io/ompio/io_ompio_file_open.c +++ b/ompi/mca/io/ompio/io_ompio_file_open.c @@ -14,6 +14,7 @@ * and Technology (RIST). All rights reserved. * Copyright (c) 2016 Cisco Systems, Inc. All rights reserved. * Copyright (c) 2016-2017 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -103,6 +104,56 @@ int mca_io_ompio_file_close (ompi_file_t *fh) return ret; } +int mca_io_ompio_file_set_info (ompi_file_t *fh, + ompi_info_t *info) +{ + int ret; + mca_common_ompio_data_t *data; + mca_common_ompio_info_phase_t previous_phase; + + if (NULL == fh || NULL == info) { + return OMPI_ERR_BAD_PARAM; + } + + data = (mca_common_ompio_data_t *) fh->f_io_selected_data; + if (NULL == data) { + return OMPI_ERR_BAD_PARAM; + } + + OPAL_THREAD_LOCK(&fh->f_lock); + previous_phase = data->ompio_fh.f_info_phase; + data->ompio_fh.f_info_phase = MCA_COMMON_OMPIO_INFO_PHASE_SET_INFO; + + ret = mca_common_ompio_info_apply(&data->ompio_fh, &info->super); + + data->ompio_fh.f_info_phase = previous_phase; + OPAL_THREAD_UNLOCK(&fh->f_lock); + + return ret; +} + +int mca_io_ompio_file_get_info (ompi_file_t *fh, + ompi_info_t **info_used) +{ + int ret; + mca_common_ompio_data_t *data; + + if (NULL == fh || NULL == info_used) { + return OMPI_ERR_BAD_PARAM; + } + + data = (mca_common_ompio_data_t *) fh->f_io_selected_data; + if (NULL == data) { + return OMPI_ERR_BAD_PARAM; + } + + OPAL_THREAD_LOCK(&fh->f_lock); + ret = mca_common_ompio_info_dup(&data->ompio_fh, info_used); + OPAL_THREAD_UNLOCK(&fh->f_lock); + + return ret; +} + int mca_io_ompio_file_preallocate (ompi_file_t *fh, OMPI_MPI_OFFSET_TYPE diskspace) { @@ -613,4 +664,3 @@ int mca_io_ompio_file_get_position_shared (ompi_file_t *fp, return ret; } - diff --git a/ompi/mca/io/ompio/io_ompio_file_set_view.c b/ompi/mca/io/ompio/io_ompio_file_set_view.c index 5a4f8136295..3c6b31a0bc7 100644 --- a/ompi/mca/io/ompio/io_ompio_file_set_view.c +++ b/ompi/mca/io/ompio/io_ompio_file_set_view.c @@ -13,6 +13,8 @@ * Copyright (c) 2015-2018 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2016-2017 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -48,8 +50,8 @@ static int datatype_duplicate (ompi_datatype_t *oldtype, ompi_datatype_t **newt ompi_datatype_destroy (&type); return MPI_ERR_INTERN; } - - ompi_datatype_set_args( type, 0, NULL, 0, NULL, 1, &oldtype, MPI_COMBINER_DUP ); + + ompi_datatype_set_args( type, 0, 0, NULL, 0, OMPI_DISP_ARRAY_NULL, 1, &oldtype, MPI_COMBINER_DUP ); *newtype = type; return OMPI_SUCCESS; @@ -64,7 +66,11 @@ int mca_io_ompio_file_set_view (ompi_file_t *fp, { int ret=OMPI_SUCCESS; mca_common_ompio_data_t *data; + mca_common_ompio_info_phase_t previous_phase; ompio_file_t *fh; + opal_info_t *previous_info; + opal_info_t *staged_info; + int previous_bytes_per_agg; if ( (strcmp(datarep, "native") && strcmp(datarep, "NATIVE") && strcmp(datarep, "external32") && strcmp(datarep, "EXTERNAL32"))) { @@ -86,7 +92,59 @@ int mca_io_ompio_file_set_view (ompi_file_t *fp, OPAL_THREAD_LOCK(&fp->f_lock); - ret = mca_common_ompio_set_view(fh, disp, etype, filetype, datarep, info); + previous_info = fp->super.s_info; + previous_bytes_per_agg = fh->f_bytes_per_agg; + + /* + * set_view setup consumes accepted hints while it builds the new file + * view. Apply the user's info to a staged copy first: the setup code + * sees the requested values, but get_info will not expose them unless + * the whole view change succeeds. + */ + staged_info = OBJ_NEW(opal_info_t); + if (NULL == staged_info) { + OPAL_THREAD_UNLOCK(&fp->f_lock); + return OMPI_ERR_OUT_OF_RESOURCE; + } + + if (NULL != previous_info) { + ret = opal_info_dup_public(previous_info, &staged_info); + if (OPAL_SUCCESS != ret) { + OBJ_RELEASE(staged_info); + OPAL_THREAD_UNLOCK(&fp->f_lock); + return ret; + } + } + + fp->super.s_info = staged_info; + fh->f_info = staged_info; + + previous_phase = fh->f_info_phase; + fh->f_info_phase = MCA_COMMON_OMPIO_INFO_PHASE_SET_VIEW; + + ret = mca_common_ompio_info_apply(fh, info); + fh->f_info_phase = previous_phase; + + if (OMPI_SUCCESS == ret) { + ret = mca_common_ompio_set_view(fh, disp, etype, filetype, datarep, info); + } + + if (OMPI_SUCCESS == ret) { + if (NULL != previous_info) { + OBJ_RELEASE(previous_info); + } + } else { + /* + * Restore both the public info object and the internal field updated + * by cb_buffer_size. Without the field rollback, a failed set_view + * could leave behavior changed even though get_info was restored. + */ + fp->super.s_info = previous_info; + fh->f_info = previous_info; + fh->f_bytes_per_agg = previous_bytes_per_agg; + OBJ_RELEASE(staged_info); + } + OPAL_THREAD_UNLOCK(&fp->f_lock); return ret; } @@ -112,4 +170,3 @@ int mca_io_ompio_file_get_view (struct ompi_file_t *fp, return OMPI_SUCCESS; } - diff --git a/ompi/mca/io/ompio/io_ompio_module.c b/ompi/mca/io/ompio/io_ompio_module.c index 4b53e70d237..af4d868ed21 100644 --- a/ompi/mca/io/ompio/io_ompio_module.c +++ b/ompi/mca/io/ompio/io_ompio_module.c @@ -13,6 +13,7 @@ * Copyright (c) 2016-2019 IBM Corporation. All rights reserved. * Copyright (c) 2024 Triad National Security, LLC. All rights * reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -38,8 +39,8 @@ mca_io_base_module_3_0_0_t mca_io_ompio_module = { mca_io_ompio_file_preallocate, mca_io_ompio_file_get_size, mca_io_ompio_file_get_amode, - NULL, - NULL, + mca_io_ompio_file_set_info, + mca_io_ompio_file_get_info, mca_io_ompio_file_set_view, mca_io_ompio_file_get_view, diff --git a/ompi/mca/mtl/ofi/README.md b/ompi/mca/mtl/ofi/README.md index 0f9adc91c7f..bc353b6a082 100644 --- a/ompi/mca/mtl/ofi/README.md +++ b/ompi/mca/mtl/ofi/README.md @@ -187,7 +187,7 @@ approach also favours only creating as many contexts as needed. Number of threads = Number of communicators = Number of contexts For example, when using PSM2 provider, the number of contexts is - dictated by the Intel Omni-Path HFI1 driver module. + dictated by the Cornelis Networks Omni-Path HFI1 driver module. * OPAL layer allows for multiple threads to enter progress simultaneously. To enable this feature, user needs to set MCA diff --git a/ompi/mca/osc/osc.h b/ompi/mca/osc/osc.h index 39063ef0914..10748625db6 100644 --- a/ompi/mca/osc/osc.h +++ b/ompi/mca/osc/osc.h @@ -216,6 +216,15 @@ typedef int (*ompi_osc_base_module_put_fn_t)(const void *origin_addr, struct ompi_datatype_t *target_dt, struct ompi_win_t *win); +typedef int (*ompi_osc_base_module_put_notify_fn_t)(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win); typedef int (*ompi_osc_base_module_get_fn_t)(void *origin_addr, size_t origin_count, @@ -226,6 +235,36 @@ typedef int (*ompi_osc_base_module_get_fn_t)(void *origin_addr, struct ompi_datatype_t *target_dt, struct ompi_win_t *win); +typedef int (*ompi_osc_base_module_get_notify_fn_t)(void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win); + +typedef int (*ompi_osc_base_module_win_get_notify_value_fn_t)(struct ompi_win_t *win, + int notify, + OMPI_MPI_COUNT_TYPE *value); + +typedef int (*ompi_osc_base_module_win_reset_notify_value_fn_t)(struct ompi_win_t *win, + int notify, + OMPI_MPI_COUNT_TYPE *value); + +typedef int (*ompi_osc_base_module_win_set_num_notify_fn_t)(struct ompi_win_t *win, + struct opal_info_t *info, + int num_notifications); + +typedef int (*ompi_osc_base_module_win_get_num_notify_fn_t)(struct ompi_win_t *win, + int target_rank, + int *num_notifications); + +typedef int (*ompi_osc_base_module_win_get_notify_bounds_fn_t)(struct ompi_win_t *win, + int *num_sb, + int *num_ub, + OMPI_MPI_COUNT_TYPE *value_ub); typedef int (*ompi_osc_base_module_accumulate_fn_t)(const void *origin_addr, size_t origin_count, @@ -237,6 +276,17 @@ typedef int (*ompi_osc_base_module_accumulate_fn_t)(const void *origin_addr, struct ompi_op_t *op, struct ompi_win_t *win); +typedef int (*ompi_osc_base_module_accumulate_notify_fn_t)(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win); + typedef int (*ompi_osc_base_module_compare_and_swap_fn_t)(const void *origin_addr, const void *compare_addr, void *result_addr, @@ -266,6 +316,20 @@ typedef int (*ompi_osc_base_module_get_accumulate_fn_t)(const void *origin_addr, struct ompi_op_t *op, struct ompi_win_t *win); +typedef int (*ompi_osc_base_module_get_accumulate_notify_fn_t)(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_datatype, + void *result_addr, + size_t result_count, + struct ompi_datatype_t *result_datatype, + int target_rank, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_datatype, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win); + typedef int (*ompi_osc_base_module_rput_fn_t)(const void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_dt, @@ -276,6 +340,17 @@ typedef int (*ompi_osc_base_module_rput_fn_t)(const void *origin_addr, struct ompi_win_t *win, struct ompi_request_t **request); +typedef int (*ompi_osc_base_module_rput_notify_fn_t)(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **request); + typedef int (*ompi_osc_base_module_rget_fn_t)(void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_dt, @@ -286,6 +361,16 @@ typedef int (*ompi_osc_base_module_rget_fn_t)(void *origin_addr, struct ompi_win_t *win, struct ompi_request_t **request); +typedef int (*ompi_osc_base_module_rget_notify_fn_t)(void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **request); typedef int (*ompi_osc_base_module_raccumulate_fn_t)(const void *origin_addr, size_t origin_count, @@ -298,6 +383,18 @@ typedef int (*ompi_osc_base_module_raccumulate_fn_t)(const void *origin_addr, struct ompi_win_t *win, struct ompi_request_t **request); +typedef int (*ompi_osc_base_module_raccumulate_notify_fn_t)(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **request); + typedef int (*ompi_osc_base_module_rget_accumulate_fn_t)(const void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_datatype, @@ -312,6 +409,21 @@ typedef int (*ompi_osc_base_module_rget_accumulate_fn_t)(const void *origin_addr struct ompi_win_t *win, struct ompi_request_t **request); +typedef int (*ompi_osc_base_module_rget_accumulate_notify_fn_t)(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_datatype, + void *result_addr, + size_t result_count, + struct ompi_datatype_t *result_datatype, + int target_rank, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_datatype, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **request); + typedef int (*ompi_osc_base_module_fence_fn_t)(int mpi_assert, struct ompi_win_t *win); @@ -407,6 +519,19 @@ struct ompi_osc_base_module_4_0_0_t { ompi_osc_base_module_flush_all_fn_t osc_flush_all; ompi_osc_base_module_flush_local_fn_t osc_flush_local; ompi_osc_base_module_flush_local_all_fn_t osc_flush_local_all; + ompi_osc_base_module_put_notify_fn_t osc_put_notify; + ompi_osc_base_module_get_notify_fn_t osc_get_notify; + ompi_osc_base_module_win_get_notify_value_fn_t osc_win_get_notify_value; + ompi_osc_base_module_win_reset_notify_value_fn_t osc_win_reset_notify_value; + ompi_osc_base_module_win_set_num_notify_fn_t osc_win_set_num_notify; + ompi_osc_base_module_win_get_num_notify_fn_t osc_win_get_num_notify; + ompi_osc_base_module_win_get_notify_bounds_fn_t osc_win_get_notify_bounds; + ompi_osc_base_module_rput_notify_fn_t osc_rput_notify; + ompi_osc_base_module_rget_notify_fn_t osc_rget_notify; + ompi_osc_base_module_accumulate_notify_fn_t osc_accumulate_notify; + ompi_osc_base_module_get_accumulate_notify_fn_t osc_get_accumulate_notify; + ompi_osc_base_module_raccumulate_notify_fn_t osc_raccumulate_notify; + ompi_osc_base_module_rget_accumulate_notify_fn_t osc_rget_accumulate_notify; }; typedef struct ompi_osc_base_module_4_0_0_t ompi_osc_base_module_4_0_0_t; typedef ompi_osc_base_module_4_0_0_t ompi_osc_base_module_t; diff --git a/ompi/mca/osc/sm/osc_sm.h b/ompi/mca/osc/sm/osc_sm.h index 363d3429a63..c4c38536246 100644 --- a/ompi/mca/osc/sm/osc_sm.h +++ b/ompi/mca/osc/sm/osc_sm.h @@ -23,6 +23,12 @@ typedef opal_atomic_uint64_t osc_sm_post_atomic_type_t; #define OSC_SM_POST_BITS 6 #define OSC_SM_POST_MASK 0x3f +/* Per-rank notification counter capacity reserved inline in the main shared + * segment at window creation, and the value reported as + * MPI_WIN_NOTIFICATION_NUM_SB -- the number of counters osc/sm supports without + * any further allocation. */ +#define OSC_SM_DEFAULT_NOTIFY_COUNTERS 16 + /* data shared across all peers */ struct ompi_osc_sm_global_state_t { int use_barrier_for_fence; @@ -47,6 +53,9 @@ struct ompi_osc_sm_node_state_t { opal_atomic_int32_t complete_count; ompi_osc_sm_lock_t lock; opal_atomic_lock_t accumulate_lock; + uint32_t notify_counter_count; + uint32_t notify_counter_capacity; + uint64_t notify_counter_offset; }; typedef struct ompi_osc_sm_node_state_t ompi_osc_sm_node_state_t; @@ -57,6 +66,10 @@ struct ompi_osc_sm_component_t { unsigned int priority; char *backing_directory; + + /** Notification counters reserved per MPI process at window creation when + * the window's info gives no mpi_assert_max_num_notify hint */ + unsigned int num_notify_counters; }; typedef struct ompi_osc_sm_component_t ompi_osc_sm_component_t; OMPI_DECLSPEC extern ompi_osc_sm_component_t mca_osc_sm_component; @@ -80,6 +93,12 @@ struct ompi_osc_sm_module_t { void **bases; ptrdiff_t *disp_units; + opal_atomic_int64_t **notify_bases; + opal_shmem_ds_t notify_seg_ds; + void *notify_segment_base; + unsigned int notify_max_assert; + + ompi_group_t *start_group; ompi_group_t *post_group; @@ -105,6 +124,9 @@ int ompi_osc_sm_detach(struct ompi_win_t *win, const void *base); int ompi_osc_sm_free(struct ompi_win_t *win); +void ompi_osc_sm_refresh_notify_bases(ompi_osc_sm_module_t *module); + + int ompi_osc_sm_put(const void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_dt, @@ -114,6 +136,16 @@ int ompi_osc_sm_put(const void *origin_addr, struct ompi_datatype_t *target_dt, struct ompi_win_t *win); + int ompi_osc_sm_put_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win); + int ompi_osc_sm_get(void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_dt, @@ -123,6 +155,37 @@ int ompi_osc_sm_get(void *origin_addr, struct ompi_datatype_t *target_dt, struct ompi_win_t *win); +int ompi_osc_sm_get_notify(void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win); + +int ompi_osc_sm_win_get_notify_value(struct ompi_win_t *win, + int notify, + OMPI_MPI_COUNT_TYPE *value); + +int ompi_osc_sm_win_reset_notify_value(struct ompi_win_t *win, + int notify, + OMPI_MPI_COUNT_TYPE *value); + +int ompi_osc_sm_win_set_num_notify(struct ompi_win_t *win, + struct opal_info_t *info, + int num_notifications); + +int ompi_osc_sm_win_get_num_notify(struct ompi_win_t *win, + int target_rank, + int *num_notifications); + +int ompi_osc_sm_win_get_notify_bounds(struct ompi_win_t *win, + int *num_sb, + int *num_ub, + OMPI_MPI_COUNT_TYPE *value_ub); + int ompi_osc_sm_accumulate(const void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_dt, @@ -133,6 +196,17 @@ int ompi_osc_sm_accumulate(const void *origin_addr, struct ompi_op_t *op, struct ompi_win_t *win); +int ompi_osc_sm_accumulate_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win); + int ompi_osc_sm_compare_and_swap(const void *origin_addr, const void *compare_addr, void *result_addr, @@ -162,6 +236,20 @@ int ompi_osc_sm_get_accumulate(const void *origin_addr, struct ompi_op_t *op, struct ompi_win_t *win); +int ompi_osc_sm_get_accumulate_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_datatype, + void *result_addr, + size_t result_count, + struct ompi_datatype_t *result_datatype, + int target_rank, + MPI_Aint target_disp, + size_t target_count, + struct ompi_datatype_t *target_datatype, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win); + int ompi_osc_sm_rput(const void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_dt, @@ -172,6 +260,17 @@ int ompi_osc_sm_rput(const void *origin_addr, struct ompi_win_t *win, struct ompi_request_t **request); +int ompi_osc_sm_rput_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **request); + int ompi_osc_sm_rget(void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_dt, @@ -182,6 +281,17 @@ int ompi_osc_sm_rget(void *origin_addr, struct ompi_win_t *win, struct ompi_request_t **request); +int ompi_osc_sm_rget_notify(void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **request); + int ompi_osc_sm_raccumulate(const void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_dt, @@ -193,6 +303,18 @@ int ompi_osc_sm_raccumulate(const void *origin_addr, struct ompi_win_t *win, struct ompi_request_t **request); +int ompi_osc_sm_raccumulate_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **request); + int ompi_osc_sm_rget_accumulate(const void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_datatype, @@ -207,6 +329,21 @@ int ompi_osc_sm_rget_accumulate(const void *origin_addr, struct ompi_win_t *win, struct ompi_request_t **request); +int ompi_osc_sm_rget_accumulate_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_datatype, + void *result_addr, + size_t result_count, + struct ompi_datatype_t *result_datatype, + int target_rank, + MPI_Aint target_disp, + size_t target_count, + struct ompi_datatype_t *target_datatype, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **request); + int ompi_osc_sm_fence(int mpi_assert, struct ompi_win_t *win); int ompi_osc_sm_start(struct ompi_group_t *group, diff --git a/ompi/mca/osc/sm/osc_sm_comm.c b/ompi/mca/osc/sm/osc_sm_comm.c index bbd5873bf96..9838d4d94a8 100644 --- a/ompi/mca/osc/sm/osc_sm_comm.c +++ b/ompi/mca/osc/sm/osc_sm_comm.c @@ -17,9 +17,347 @@ #include "ompi/mca/osc/osc.h" #include "ompi/mca/osc/base/base.h" #include "ompi/mca/osc/base/osc_base_obj_convert.h" +#include "ompi/communicator/communicator.h" +#include "opal/align.h" +#include "opal/util/printf.h" +#include "opal/util/sys_limits.h" + +#include +#include #include "osc_sm.h" +static inline opal_atomic_int64_t * +osc_sm_target_notify_base(ompi_osc_sm_module_t *module, int target) +{ + return module->notify_bases[target]; +} + +static inline int +osc_sm_check_notify_idx(ompi_osc_sm_module_t *module, int target, int notify) +{ + if (notify < 0 || (uint32_t) notify >= module->node_states[target].notify_counter_count) { + return MPI_ERR_RMA_NOTIFICATION; + } + + return OMPI_SUCCESS; +} + +static inline void +osc_sm_notify_accumulate_done(ompi_osc_sm_module_t *module, int target, int notify) +{ + opal_atomic_mb(); + opal_atomic_add(&osc_sm_target_notify_base(module, target)[notify], 1); +} + +int +ompi_osc_sm_win_get_notify_value(struct ompi_win_t *win, + int notify, + OMPI_MPI_COUNT_TYPE *value) +{ + ompi_osc_sm_module_t *module = (ompi_osc_sm_module_t *) win->w_osc_module; + int rank = ompi_comm_rank(module->comm); + int ret; + + ret = osc_sm_check_notify_idx(module, rank, notify); + if (OMPI_SUCCESS != ret) { + return ret; + } + + *value = (OMPI_MPI_COUNT_TYPE) osc_sm_target_notify_base(module, rank)[notify]; + opal_atomic_rmb(); + + return OMPI_SUCCESS; +} + +int +ompi_osc_sm_win_reset_notify_value(struct ompi_win_t *win, + int notify, + OMPI_MPI_COUNT_TYPE *value) +{ + ompi_osc_sm_module_t *module = (ompi_osc_sm_module_t *) win->w_osc_module; + int rank = ompi_comm_rank(module->comm); + int ret; + + ret = osc_sm_check_notify_idx(module, rank, notify); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Atomically swap the counter to 0 and return the previous value. */ + *value = (OMPI_MPI_COUNT_TYPE) opal_atomic_swap_64( + &osc_sm_target_notify_base(module, rank)[notify], 0); + opal_atomic_rmb(); + + return OMPI_SUCCESS; +} + +/* Move every rank's notification counters into a newly created shared segment + * sized for the capacities in new_caps. Collective over the window's + * communicator; every MPI process must call it with an identical new_caps. + */ +static int +osc_sm_grow_notify_counters(ompi_osc_sm_module_t *module, const unsigned long *new_caps, + unsigned long new_count) +{ + int comm_size = ompi_comm_size(module->comm); + int rank = ompi_comm_rank(module->comm); + opal_shmem_ds_t new_seg_ds; + opal_shmem_ds_t old_seg_ds = module->notify_seg_ds; + void *old_segment_base = module->notify_segment_base; + void *new_base; + unsigned long total_counters = 0; + size_t seg_size; + char *data_file; + int ret, i, status; + + for (i = 0 ; i < comm_size ; ++i) { + total_counters += new_caps[i]; + } + seg_size = total_counters * sizeof(int64_t); + seg_size += OPAL_ALIGN_PAD_AMOUNT(seg_size, opal_getpagesize()); + + memset(&new_seg_ds, 0, sizeof(new_seg_ds)); + + if (0 == rank) { + ret = opal_asprintf(&data_file, "%s" OPAL_PATH_SEP "osc_sm_notify.%s.%x.%d.%s.%lu", + mca_osc_sm_component.backing_directory, ompi_process_info.nodename, + OMPI_PROC_MY_NAME->jobid, (int) OMPI_PROC_MY_NAME->vpid, + ompi_comm_print_cid(module->comm), total_counters); + if (ret > 0) { + (void) opal_shmem_segment_create(&new_seg_ds, data_file, seg_size); + free(data_file); + } + } + + ret = module->comm->c_coll->coll_bcast(&new_seg_ds, sizeof(new_seg_ds), MPI_BYTE, 0, + module->comm, + module->comm->c_coll->coll_bcast_module); + if (OMPI_SUCCESS != ret) { + return ret; + } + + if ('\0' == new_seg_ds.seg_name[0]) { + return MPI_ERR_NO_MEM; + } + + new_base = opal_shmem_segment_attach(&new_seg_ds); + + /* Attach can fail at some MPI processes and not others. Agree on the + * outcome before touching any shared state */ + status = (NULL == new_base) ? 1 : 0; + ret = module->comm->c_coll->coll_allreduce(MPI_IN_PLACE, &status, 1, MPI_INT, MPI_MAX, + module->comm, + module->comm->c_coll->coll_allreduce_module); + if (OMPI_SUCCESS != ret) { + return ret; + } + + if (0 != status) { + if (NULL != new_base) { + opal_shmem_segment_detach(&new_seg_ds); + } + return MPI_ERR_NO_MEM; + } + + module->notify_segment_base = new_base; + module->notify_seg_ds = new_seg_ds; + + /* Republish the layout. Every MPI process computes the same offsets from + * the same new_caps, so these stores are identical everywhere; node_states + * lives in the main segment, which does not move. */ + total_counters = 0; + for (i = 0 ; i < comm_size ; ++i) { + module->node_states[i].notify_counter_capacity = (uint32_t) new_caps[i]; + module->node_states[i].notify_counter_offset = total_counters * sizeof(int64_t); + total_counters += new_caps[i]; + } + + ompi_osc_sm_refresh_notify_bases(module); + + memset((void *) module->notify_bases[rank], 0, + module->node_states[rank].notify_counter_capacity * sizeof(int64_t)); + + module->node_states[rank].notify_counter_count = (uint32_t) new_count; + opal_atomic_wmb(); + + /* Everyone has opened the new segment and published their layout. Both + * facts are needed before we continue: the unlink below removes the name + * that the attach above resolves, and an origin returning from this call + * may immediately validate a notification index against our count. */ + ret = module->comm->c_coll->coll_barrier(module->comm, + module->comm->c_coll->coll_barrier_module); + if (OMPI_SUCCESS != ret) { + return ret; + } + + if (0 == rank) { + opal_shmem_unlink(&module->notify_seg_ds); + } + + /* Dropping the old mapping is purely local -- munmap in one MPI process + * does not disturb any other process's view -- so it needs no + * synchronization of its own. On the first growth there is nothing to + * drop: the counters were inline in the main segment, which stays mapped + * for the lifetime of the window. */ + if (NULL != old_segment_base) { + opal_shmem_segment_detach(&old_seg_ds); + } + + return OMPI_SUCCESS; +} + +int +ompi_osc_sm_win_set_num_notify(struct ompi_win_t *win, + struct opal_info_t *info, + int num_notifications) +{ + ompi_osc_sm_module_t *module = (ompi_osc_sm_module_t *) win->w_osc_module; + int comm_size = ompi_comm_size(module->comm); + int rank = ompi_comm_rank(module->comm); + unsigned long requested = (unsigned long) num_notifications; + unsigned long *new_caps; + bool grow = false, bad; + int ret, i; + + /* "mpi_assert_same_num_notifications" would let us skip the allgather below + * and derive the layout from our own num_notifications. Not taken up yet; + * the allgather is one collective on a procedure that is already + * synchronizing and collective. */ + (void) info; + + /* num_notifications is a local argument -- MPI-5.1 12.6.1 allows it to + * differ between MPI processes -- but this is a synchronizing collective. + * A rank that rejected its own value and returned here would leave every + * other rank blocked in the allgather below, turning an erroneous argument + * into a hang. So the validity rides through the collective as a sentinel + * and all ranks fail together. A multi-process window defers the decision; + * a single-process one has nobody to agree with and can answer now. */ + bad = (num_notifications < 0) + || (0 != module->notify_max_assert && + requested > (unsigned long) module->notify_max_assert); + + if (bad && 1 == comm_size) { + return MPI_ERR_ARG; + } + + if (bad) { + /* Leave our counters alone: the call is going to fail on every rank, so + * the window must come out of it unchanged rather than half-reset. */ + requested = ULONG_MAX; + goto agree; + } + + memset((void *) module->notify_bases[rank], 0, + module->node_states[rank].notify_counter_capacity * sizeof(int64_t)); + module->node_states[rank].notify_counter_count = + (requested > module->node_states[rank].notify_counter_capacity) + ? module->node_states[rank].notify_counter_capacity + : (uint32_t) requested; + opal_atomic_wmb(); + + if (1 == comm_size) { + /* No shared segment for a single-process window; the counters are a + * plain allocation, so growing them is a plain reallocation and none of + * the collective machinery below applies. */ + if (requested > module->node_states[0].notify_counter_capacity) { + void *grown = calloc(requested, sizeof(int64_t)); + if (NULL == grown) { + return MPI_ERR_NO_MEM; + } + free((void *) module->notify_bases[0]); + module->notify_bases[0] = (opal_atomic_int64_t *) grown; + module->node_states[0].notify_counter_capacity = (uint32_t) requested; + module->node_states[0].notify_counter_count = (uint32_t) requested; + } + return OMPI_SUCCESS; + } + +agree: + new_caps = malloc(sizeof(*new_caps) * comm_size); + if (NULL == new_caps) { + return OMPI_ERR_TEMP_OUT_OF_RESOURCE; + } + + ret = module->comm->c_coll->coll_allgather(&requested, 1, MPI_UNSIGNED_LONG, + new_caps, 1, MPI_UNSIGNED_LONG, + module->comm, + module->comm->c_coll->coll_allgather_module); + if (OMPI_SUCCESS != ret) { + free(new_caps); + return ret; + } + + for (i = 0 ; i < comm_size ; ++i) { + if (ULONG_MAX == new_caps[i]) { + /* Some rank supplied an invalid count. Every rank sees the same + * gathered array, so they all report the same error and none of + * them reconfigures. */ + free(new_caps); + return MPI_ERR_ARG; + } + } + + for (i = 0 ; i < comm_size ; ++i) { + if (new_caps[i] > module->node_states[i].notify_counter_capacity) { + grow = true; + } else { + /* Never shrink: a rank that lowered its count keeps the space it + * already has, so that only genuine growth costs a reallocation. */ + new_caps[i] = module->node_states[i].notify_counter_capacity; + } + } + + if (grow) { + ret = osc_sm_grow_notify_counters(module, new_caps, requested); + free(new_caps); + return ret; + } + + free(new_caps); + + return OMPI_SUCCESS; +} + +int +ompi_osc_sm_win_get_num_notify(struct ompi_win_t *win, + int target_rank, + int *num_notifications) +{ + ompi_osc_sm_module_t *module = (ompi_osc_sm_module_t *) win->w_osc_module; + + if (target_rank < 0 || target_rank >= ompi_comm_size(module->comm)) { + return MPI_ERR_RANK; + } + + *num_notifications = (int) module->node_states[target_rank].notify_counter_count; + + return OMPI_SUCCESS; +} + +int +ompi_osc_sm_win_get_notify_bounds(struct ompi_win_t *win, + int *num_sb, + int *num_ub, + OMPI_MPI_COUNT_TYPE *value_ub) +{ + ompi_osc_sm_module_t *module = (ompi_osc_sm_module_t *) win->w_osc_module; + + if (0 != module->notify_max_assert) { + *num_sb = (int) module->notify_max_assert; + *num_ub = (int) module->notify_max_assert; + } else { + *num_sb = (int) mca_osc_sm_component.num_notify_counters; + *num_ub = INT_MAX; + } + + /* Counters are int64_t and only ever incremented by one per notified + * operation, so the representable maximum is the real bound. */ + *value_ub = (OMPI_MPI_COUNT_TYPE) INT64_MAX; + + return OMPI_SUCCESS; +} + int ompi_osc_sm_rput(const void *origin_addr, size_t origin_count, @@ -59,6 +397,56 @@ ompi_osc_sm_rput(const void *origin_addr, return OMPI_SUCCESS; } +int +ompi_osc_sm_rput_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **ompi_req) +{ + int ret; + ompi_osc_sm_module_t *module = + (ompi_osc_sm_module_t*) win->w_osc_module; + void *remote_address; + + OPAL_OUTPUT_VERBOSE((50, ompi_osc_base_framework.framework_output, + "rput_notify: 0x%lx, %zu, %s, %d, %d, %zu, %s, %d, 0x%lx", + (unsigned long) origin_addr, origin_count, + origin_dt->name, target, (int) target_disp, + target_count, target_dt->name, + notify, + (unsigned long) win)); + + ret = osc_sm_check_notify_idx(module, target, notify); + if (OMPI_SUCCESS != ret) { + return ret; + } + + remote_address = ((char*) (module->bases[target])) + module->disp_units[target] * target_disp; + + ret = ompi_datatype_sndrcv((void *)origin_addr, origin_count, origin_dt, + remote_address, target_count, target_dt); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Release ordering: the data must be visible at the target before the + * notification is */ + opal_atomic_wmb(); + opal_atomic_add(&osc_sm_target_notify_base(module, target)[notify], 1); + + /* the only valid field of RMA request status is the MPI_ERROR field. + * ompi_request_empty has status MPI_SUCCESS and indicates the request is + * complete. */ + *ompi_req = &ompi_request_empty; + + return OMPI_SUCCESS; +} int ompi_osc_sm_rget(void *origin_addr, @@ -99,6 +487,54 @@ ompi_osc_sm_rget(void *origin_addr, return OMPI_SUCCESS; } +int +ompi_osc_sm_rget_notify(void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **ompi_req) +{ + int ret; + ompi_osc_sm_module_t *module = + (ompi_osc_sm_module_t*) win->w_osc_module; + void *remote_address; + + OPAL_OUTPUT_VERBOSE((50, ompi_osc_base_framework.framework_output, + "rget_notify: 0x%lx, %zu, %s, %d, %d, %zu, %s, %d, 0x%lx", + (unsigned long) origin_addr, origin_count, + origin_dt->name, target, (int) target_disp, + target_count, target_dt->name, + notify, + (unsigned long) win)); + + ret = osc_sm_check_notify_idx(module, target, notify); + if (OMPI_SUCCESS != ret) { + return ret; + } + + remote_address = ((char*) (module->bases[target])) + module->disp_units[target] * target_disp; + + ret = ompi_datatype_sndrcv(remote_address, target_count, target_dt, + origin_addr, origin_count, origin_dt); + if (OMPI_SUCCESS != ret) { + return ret; + } + + opal_atomic_mb(); + opal_atomic_add(&osc_sm_target_notify_base(module, target)[notify], 1); + + /* the only valid field of RMA request status is the MPI_ERROR field. + * ompi_request_empty has status MPI_SUCCESS and indicates the request is + * complete. */ + *ompi_req = &ompi_request_empty; + + return OMPI_SUCCESS; +} int ompi_osc_sm_raccumulate(const void *origin_addr, @@ -147,6 +583,67 @@ ompi_osc_sm_raccumulate(const void *origin_addr, } +int +ompi_osc_sm_raccumulate_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **ompi_req) +{ + int ret; + ompi_osc_sm_module_t *module = + (ompi_osc_sm_module_t*) win->w_osc_module; + void *remote_address; + + OPAL_OUTPUT_VERBOSE((50, ompi_osc_base_framework.framework_output, + "raccumulate_notify: 0x%lx, %zu, %s, %d, %d, %zu, %s, %s, %d, 0x%lx", + (unsigned long) origin_addr, origin_count, + origin_dt->name, target, (int) target_disp, + target_count, target_dt->name, + op->o_name, notify, + (unsigned long) win)); + + ret = osc_sm_check_notify_idx(module, target, notify); + if (OMPI_SUCCESS != ret) { + return ret; + } + + remote_address = ((char*) (module->bases[target])) + module->disp_units[target] * target_disp; + + opal_atomic_lock(&module->node_states[target].accumulate_lock); + if (op == &ompi_mpi_op_replace.op) { + ret = ompi_datatype_sndrcv((void *)origin_addr, origin_count, origin_dt, + remote_address, target_count, target_dt); + } else { + ret = ompi_osc_base_sndrcv_op(origin_addr, origin_count, origin_dt, + remote_address, target_count, target_dt, + op); + } + opal_atomic_unlock(&module->node_states[target].accumulate_lock); + + /* Only notify once the accumulate actually happened -- a counter bumped for + * an operation that failed would tell the target that data it never + * received is ready. */ + if (OMPI_SUCCESS != ret) { + return ret; + } + + osc_sm_notify_accumulate_done(module, target, notify); + + /* the only valid field of RMA request status is the MPI_ERROR field. + * ompi_request_empty has status MPI_SUCCESS and indicates the request is + * complete. */ + *ompi_req = &ompi_request_empty; + + return ret; +} + int ompi_osc_sm_rget_accumulate(const void *origin_addr, @@ -205,6 +702,78 @@ ompi_osc_sm_rget_accumulate(const void *origin_addr, } +int +ompi_osc_sm_rget_accumulate_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + void *result_addr, + size_t result_count, + struct ompi_datatype_t *result_dt, + int target, + MPI_Aint target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win, + struct ompi_request_t **ompi_req) +{ + int ret; + ompi_osc_sm_module_t *module = + (ompi_osc_sm_module_t*) win->w_osc_module; + void *remote_address; + + OPAL_OUTPUT_VERBOSE((50, ompi_osc_base_framework.framework_output, + "rget_accumulate_notify: 0x%lx, %zu, %s, %d, %d, %zu, %s, %s, %d, 0x%lx", + (unsigned long) origin_addr, origin_count, + origin_dt->name, target, (int) target_disp, + target_count, target_dt->name, + op->o_name, notify, + (unsigned long) win)); + + ret = osc_sm_check_notify_idx(module, target, notify); + if (OMPI_SUCCESS != ret) { + return ret; + } + + remote_address = ((char*) (module->bases[target])) + module->disp_units[target] * target_disp; + + opal_atomic_lock(&module->node_states[target].accumulate_lock); + + ret = ompi_datatype_sndrcv(remote_address, target_count, target_dt, + result_addr, result_count, result_dt); + if (OMPI_SUCCESS != ret || op == &ompi_mpi_op_no_op.op) goto done; + + if (op == &ompi_mpi_op_replace.op) { + ret = ompi_datatype_sndrcv((void *)origin_addr, origin_count, origin_dt, + remote_address, target_count, target_dt); + } else { + ret = ompi_osc_base_sndrcv_op(origin_addr, origin_count, origin_dt, + remote_address, target_count, target_dt, + op); + } + + done: + opal_atomic_unlock(&module->node_states[target].accumulate_lock); + + /* Only notify once the operation actually happened. MPI_NO_OP is not a + * failure: the target window was still read into the result buffer, which is + * an access the notification is required to cover. */ + if (OMPI_SUCCESS != ret) { + return ret; + } + + osc_sm_notify_accumulate_done(module, target, notify); + + /* the only valid field of RMA request status is the MPI_ERROR field. + * ompi_request_empty has status MPI_SUCCESS and indicates the request is + * complete. */ + *ompi_req = &ompi_request_empty; + + return ret; +} + + int ompi_osc_sm_put(const void *origin_addr, size_t origin_count, @@ -236,6 +805,52 @@ ompi_osc_sm_put(const void *origin_addr, } +int +ompi_osc_sm_put_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win) +{ + int ret; + ompi_osc_sm_module_t *module = + (ompi_osc_sm_module_t*) win->w_osc_module; + void *remote_address; + + OPAL_OUTPUT_VERBOSE((50, ompi_osc_base_framework.framework_output, + "put_notify: 0x%lx, %zu, %s, %d, %d, %zu, %s, %d, 0x%lx", + (unsigned long) origin_addr, origin_count, + origin_dt->name, target, (int) target_disp, + target_count, target_dt->name, + notify, + (unsigned long) win)); + + ret = osc_sm_check_notify_idx(module, target, notify); + if (OMPI_SUCCESS != ret) { + return ret; + } + + remote_address = ((char*) (module->bases[target])) + module->disp_units[target] * target_disp; + + ret = ompi_datatype_sndrcv((void *)origin_addr, origin_count, origin_dt, + remote_address, target_count, target_dt); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* (MPI-5.1 section 12.3, "The notification counter will be + * updated at the target only after the completion of the data movement + * operation at the target"). */ + opal_atomic_wmb(); + opal_atomic_add(&osc_sm_target_notify_base(module, target)[notify], 1); + + return ret; +} + int ompi_osc_sm_get(void *origin_addr, size_t origin_count, @@ -267,6 +882,50 @@ ompi_osc_sm_get(void *origin_addr, } +int +ompi_osc_sm_get_notify(void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, + struct ompi_win_t *win) +{ + int ret; + ompi_osc_sm_module_t *module = + (ompi_osc_sm_module_t*) win->w_osc_module; + void *remote_address; + + OPAL_OUTPUT_VERBOSE((50, ompi_osc_base_framework.framework_output, + "get: 0x%lx, %zu, %s, %d, %d, %zu, %s, 0x%lx", + (unsigned long) origin_addr, origin_count, + origin_dt->name, target, (int) target_disp, + target_count, target_dt->name, + (unsigned long) win)); + + ret = osc_sm_check_notify_idx(module, target, notify); + if (OMPI_SUCCESS != ret) { + return ret; + } + + remote_address = ((char*) (module->bases[target])) + module->disp_units[target] * target_disp; + + ret = ompi_datatype_sndrcv(remote_address, target_count, target_dt, + origin_addr, origin_count, origin_dt); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Full barrier, not opal_atomic_rmb(): see ompi_osc_sm_rget_notify(). */ + opal_atomic_mb(); + opal_atomic_add(&osc_sm_target_notify_base(module, target)[notify], 1); + + return ret; +} + + int ompi_osc_sm_accumulate(const void *origin_addr, size_t origin_count, @@ -308,6 +967,62 @@ ompi_osc_sm_accumulate(const void *origin_addr, } +int +ompi_osc_sm_accumulate_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, + ptrdiff_t target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win) +{ + int ret; + ompi_osc_sm_module_t *module = + (ompi_osc_sm_module_t*) win->w_osc_module; + void *remote_address; + + OPAL_OUTPUT_VERBOSE((50, ompi_osc_base_framework.framework_output, + "accumulate_notify: 0x%lx, %zu, %s, %d, %d, %zu, %s, %s, %d, 0x%lx", + (unsigned long) origin_addr, origin_count, + origin_dt->name, target, (int) target_disp, + target_count, target_dt->name, + op->o_name, notify, + (unsigned long) win)); + + ret = osc_sm_check_notify_idx(module, target, notify); + if (OMPI_SUCCESS != ret) { + return ret; + } + + remote_address = ((char*) (module->bases[target])) + module->disp_units[target] * target_disp; + + opal_atomic_lock(&module->node_states[target].accumulate_lock); + if (op == &ompi_mpi_op_replace.op) { + ret = ompi_datatype_sndrcv((void *)origin_addr, origin_count, origin_dt, + remote_address, target_count, target_dt); + } else { + ret = ompi_osc_base_sndrcv_op(origin_addr, origin_count, origin_dt, + remote_address, target_count, target_dt, + op); + } + opal_atomic_unlock(&module->node_states[target].accumulate_lock); + + /* Only notify once the accumulate actually happened -- a counter bumped for + * an operation that failed would tell the target that data it never + * received is ready. */ + if (OMPI_SUCCESS != ret) { + return ret; + } + + osc_sm_notify_accumulate_done(module, target, notify); + + return ret; +} + + int ompi_osc_sm_get_accumulate(const void *origin_addr, size_t origin_count, @@ -359,6 +1074,72 @@ ompi_osc_sm_get_accumulate(const void *origin_addr, } +int +ompi_osc_sm_get_accumulate_notify(const void *origin_addr, + size_t origin_count, + struct ompi_datatype_t *origin_dt, + void *result_addr, + size_t result_count, + struct ompi_datatype_t *result_dt, + int target, + MPI_Aint target_disp, + size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, + int notify, + struct ompi_win_t *win) +{ + int ret; + ompi_osc_sm_module_t *module = + (ompi_osc_sm_module_t*) win->w_osc_module; + void *remote_address; + + OPAL_OUTPUT_VERBOSE((50, ompi_osc_base_framework.framework_output, + "get_accumulate_notify: 0x%lx, %zu, %s, %d, %d, %zu, %s, %s, %d, 0x%lx", + (unsigned long) origin_addr, origin_count, + origin_dt->name, target, (int) target_disp, + target_count, target_dt->name, + op->o_name, notify, + (unsigned long) win)); + + ret = osc_sm_check_notify_idx(module, target, notify); + if (OMPI_SUCCESS != ret) { + return ret; + } + + remote_address = ((char*) (module->bases[target])) + module->disp_units[target] * target_disp; + + opal_atomic_lock(&module->node_states[target].accumulate_lock); + + ret = ompi_datatype_sndrcv(remote_address, target_count, target_dt, + result_addr, result_count, result_dt); + if (OMPI_SUCCESS != ret || op == &ompi_mpi_op_no_op.op) goto done; + + if (op == &ompi_mpi_op_replace.op) { + ret = ompi_datatype_sndrcv((void *)origin_addr, origin_count, origin_dt, + remote_address, target_count, target_dt); + } else { + ret = ompi_osc_base_sndrcv_op(origin_addr, origin_count, origin_dt, + remote_address, target_count, target_dt, + op); + } + + done: + opal_atomic_unlock(&module->node_states[target].accumulate_lock); + + /* Only notify once the operation actually happened. MPI_NO_OP is not a + * failure: the target window was still read into the result buffer, which is + * an access the notification is required to cover. */ + if (OMPI_SUCCESS != ret) { + return ret; + } + + osc_sm_notify_accumulate_done(module, target, notify); + + return ret; +} + + int ompi_osc_sm_compare_and_swap(const void *origin_addr, const void *compare_addr, @@ -437,5 +1218,5 @@ ompi_osc_sm_fetch_and_op(const void *origin_addr, done: opal_atomic_unlock(&module->node_states[target].accumulate_lock); - return OMPI_SUCCESS;; + return OMPI_SUCCESS; } diff --git a/ompi/mca/osc/sm/osc_sm_component.c b/ompi/mca/osc/sm/osc_sm_component.c index ad2b3cae25a..055fb460ee7 100644 --- a/ompi/mca/osc/sm/osc_sm_component.c +++ b/ompi/mca/osc/sm/osc_sm_component.c @@ -31,7 +31,9 @@ #include "ompi/request/request.h" #include "opal/util/sys_limits.h" #include "opal/align.h" +#include "opal/util/info.h" #include "opal/util/printf.h" +#include "opal/class/opal_cstring.h" #include "opal/mca/mpool/base/base.h" #include "osc_sm.h" @@ -79,16 +81,29 @@ ompi_osc_sm_module_t ompi_osc_sm_module_template = { .osc_free = ompi_osc_sm_free, .osc_put = ompi_osc_sm_put, + .osc_put_notify = ompi_osc_sm_put_notify, .osc_get = ompi_osc_sm_get, + .osc_get_notify = ompi_osc_sm_get_notify, + .osc_win_get_notify_value = ompi_osc_sm_win_get_notify_value, + .osc_win_reset_notify_value = ompi_osc_sm_win_reset_notify_value, + .osc_win_set_num_notify = ompi_osc_sm_win_set_num_notify, + .osc_win_get_num_notify = ompi_osc_sm_win_get_num_notify, + .osc_win_get_notify_bounds = ompi_osc_sm_win_get_notify_bounds, .osc_accumulate = ompi_osc_sm_accumulate, + .osc_accumulate_notify = ompi_osc_sm_accumulate_notify, .osc_compare_and_swap = ompi_osc_sm_compare_and_swap, .osc_fetch_and_op = ompi_osc_sm_fetch_and_op, .osc_get_accumulate = ompi_osc_sm_get_accumulate, + .osc_get_accumulate_notify = ompi_osc_sm_get_accumulate_notify, .osc_rput = ompi_osc_sm_rput, + .osc_rput_notify = ompi_osc_sm_rput_notify, .osc_rget = ompi_osc_sm_rget, + .osc_rget_notify = ompi_osc_sm_rget_notify, .osc_raccumulate = ompi_osc_sm_raccumulate, + .osc_raccumulate_notify = ompi_osc_sm_raccumulate_notify, .osc_rget_accumulate = ompi_osc_sm_rget_accumulate, + .osc_rget_accumulate_notify = ompi_osc_sm_rget_accumulate_notify, .osc_fence = ompi_osc_sm_fence, @@ -138,9 +153,83 @@ static int component_register (void) &mca_osc_sm_component.priority); free(description_str); + mca_osc_sm_component.num_notify_counters = OSC_SM_DEFAULT_NOTIFY_COUNTERS; + opal_asprintf(&description_str, + "Number of RMA notification counters reserved per MPI process " + "in the shared memory segment of each window. Windows whose " + "info gives an mpi_assert_max_num_notify value use that " + "instead. MPI_Win_set_num_notify may exceed this value, at " + "the cost of allocating a new shared segment (default: %u)", + mca_osc_sm_component.num_notify_counters); + (void) mca_base_component_var_register(&mca_osc_sm_component.super.osc_version, + "num_notify_counters", description_str, + MCA_BASE_VAR_TYPE_UNSIGNED_INT, NULL, 0, 0, + OPAL_INFO_LVL_3, MCA_BASE_VAR_SCOPE_GROUP, + &mca_osc_sm_component.num_notify_counters); + free(description_str); + return OPAL_SUCCESS; } + +/* Read the mpi_assert_max_num_notify info key (MPI-5.1 section 12.2). */ +static int osc_sm_reserved_notify_counters(opal_info_t *info, unsigned int *assert_value, + unsigned int *reserved) +{ + opal_cstring_t *value_string; + int flag = 0, value = 0; + + *assert_value = 0; + *reserved = mca_osc_sm_component.num_notify_counters; + + if (NULL == info) { + return OMPI_SUCCESS; + } + + if (OMPI_SUCCESS != opal_info_get(info, "mpi_assert_max_num_notify", + &value_string, &flag) || !flag) { + return OMPI_SUCCESS; + } + + if (OPAL_SUCCESS != opal_cstring_to_int(value_string, &value)) { + OBJ_RELEASE(value_string); + return MPI_ERR_INFO; + } + OBJ_RELEASE(value_string); + + /* A negative value is a malformed key rather than "no assertion"; only 0 + * carries the "assume nothing" meaning. */ + if (value < 0) { + return MPI_ERR_INFO; + } + + if (0 != value) { + *assert_value = (unsigned int) value; + *reserved = (unsigned int) value; + } + + return OMPI_SUCCESS; +} + + +void +ompi_osc_sm_refresh_notify_bases(ompi_osc_sm_module_t *module) +{ + int comm_size = ompi_comm_size(module->comm); + char *base; + int i; + + /* Once the counters have been grown they live in their own segment; + * before that they sit inline in the main one. */ + base = (NULL != module->notify_segment_base) ? (char *) module->notify_segment_base + : (char *) module->segment_base; + + for (i = 0 ; i < comm_size ; ++i) { + module->notify_bases[i] = (opal_atomic_int64_t *) + (base + module->node_states[i].notify_counter_offset); + } +} + static int component_open(void) { @@ -201,6 +290,7 @@ component_select(struct ompi_win_t *win, void **base, size_t size, ptrdiff_t dis bool unlink_needed = false; int ret = OMPI_ERROR; size_t memory_alignment = OPAL_ALIGN_MIN; + unsigned int notify_assert = 0, notify_reserved = 0; assert(MPI_WIN_FLAVOR_SHARED == flavor || MPI_WIN_FLAVOR_ALLOCATE == flavor); @@ -231,6 +321,16 @@ component_select(struct ompi_win_t *win, void **base, size_t size, ptrdiff_t dis module->flavor = flavor; + /* How many notification counters to reserve per MPI process. Read before + * the segment is sized, since the reservation is part of its layout. */ + ret = osc_sm_reserved_notify_counters(info, ¬ify_assert, ¬ify_reserved); + if (OMPI_SUCCESS != ret) goto error; + module->notify_max_assert = notify_assert; + module->notify_segment_base = NULL; + + module->notify_bases = calloc(comm_size, sizeof(module->notify_bases[0])); + if (NULL == module->notify_bases) return OMPI_ERR_TEMP_OUT_OF_RESOURCE; + /* create the segment */ if (1 == comm_size) { module->segment_base = NULL; @@ -251,13 +351,22 @@ component_select(struct ompi_win_t *win, void **base, size_t size, ptrdiff_t dis module->posts = calloc (1, sizeof(module->posts[0]) + sizeof (module->posts[0][0])); if (NULL == module->posts) return OMPI_ERR_TEMP_OUT_OF_RESOURCE; module->posts[0] = (osc_sm_post_atomic_type_t *) (module->posts + 1); + + /* Notification counters for the single process case. */ + module->notify_bases[0] = calloc(notify_reserved, sizeof(int64_t)); + if (NULL == module->notify_bases[0]) return OMPI_ERR_TEMP_OUT_OF_RESOURCE; + module->node_states[0].notify_counter_capacity = notify_reserved; + module->node_states[0].notify_counter_count = notify_reserved; + module->node_states[0].notify_counter_offset = 0; } else { - unsigned long total, *rbuf; + unsigned long total, total_counters, gather_values[2], *rbuf; int i, flag; size_t pagesize; size_t state_size; size_t posts_size, post_size = (comm_size + OSC_SM_POST_MASK) / (OSC_SM_POST_MASK + 1); + size_t notify_counters_size; size_t data_base_size; + opal_atomic_int64_t *notify_counters_base; opal_output_verbose(MCA_BASE_VERBOSE_DEBUG, ompi_osc_base_framework.framework_output, "allocating shared memory region of size %ld\n", (long) size); @@ -265,7 +374,7 @@ component_select(struct ompi_win_t *win, void **base, size_t size, ptrdiff_t dis /* get the pagesize */ pagesize = opal_getpagesize(); - rbuf = malloc(sizeof(unsigned long) * comm_size); + rbuf = malloc(sizeof(unsigned long) * comm_size * 2 ); if (NULL == rbuf) return OMPI_ERR_TEMP_OUT_OF_RESOURCE; /* Note that the alloc_shared_noncontig info key only has @@ -289,9 +398,10 @@ component_select(struct ompi_win_t *win, void **base, size_t size, ptrdiff_t dis "allocating window using contiguous strategy"); } - total = size; - ret = module->comm->c_coll->coll_allgather(&total, 1, MPI_UNSIGNED_LONG, - rbuf, 1, MPI_UNSIGNED_LONG, + gather_values[0] = size; + gather_values[1] = notify_reserved; + ret = module->comm->c_coll->coll_allgather(gather_values, 2, MPI_UNSIGNED_LONG, + rbuf, 2, MPI_UNSIGNED_LONG, module->comm, module->comm->c_coll->coll_allgather_module); if (OMPI_SUCCESS != ret) { @@ -300,8 +410,10 @@ component_select(struct ompi_win_t *win, void **base, size_t size, ptrdiff_t dis } total = 0; + total_counters = 0; for (i = 0 ; i < comm_size ; ++i) { - total += rbuf[i]; + total += rbuf[2 * i]; + total_counters += rbuf[2 * i + 1]; if (module->noncontig) { total += OPAL_ALIGN_PAD_AMOUNT(total, pagesize); } @@ -312,7 +424,9 @@ component_select(struct ompi_win_t *win, void **base, size_t size, ptrdiff_t dis state_size += OPAL_ALIGN_PAD_AMOUNT(state_size, 64); posts_size = comm_size * post_size * sizeof (module->posts[0][0]); posts_size += OPAL_ALIGN_PAD_AMOUNT(posts_size, 64); - data_base_size = state_size + posts_size; + notify_counters_size = total_counters * sizeof(uint64_t); + notify_counters_size += OPAL_ALIGN_PAD_AMOUNT(notify_counters_size, 64); + data_base_size = state_size + posts_size + notify_counters_size; data_base_size += OPAL_ALIGN_PAD_AMOUNT(data_base_size, pagesize); if (0 == ompi_comm_rank (module->comm)) { char *data_file; @@ -373,15 +487,26 @@ component_select(struct ompi_win_t *win, void **base, size_t size, ptrdiff_t dis module->global_state = (ompi_osc_sm_global_state_t *) (module->posts[0] + comm_size * post_size); module->node_states = (ompi_osc_sm_node_state_t *) (module->global_state + 1); - for (i = 0, total = data_base_size ; i < comm_size ; ++i) { + /* set up notify counters in shared memory after node_states */ + notify_counters_base = (opal_atomic_int64_t *) ((char *)(module->node_states + comm_size) + + OPAL_ALIGN_PAD_AMOUNT((uintptr_t)(module->node_states + comm_size), 64)); + + for (i = 0, total = data_base_size, total_counters = 0 ; i < comm_size ; ++i) { if (i > 0) { module->posts[i] = module->posts[i - 1] + post_size; } - module->sizes[i] = rbuf[i]; + module->node_states[i].notify_counter_capacity = (uint32_t) rbuf[2 * i + 1]; + module->node_states[i].notify_counter_count = (uint32_t) rbuf[2 * i + 1]; + module->node_states[i].notify_counter_offset = + (uint64_t) ((char *) (notify_counters_base + total_counters) - + (char *) module->segment_base); + total_counters += rbuf[2 * i + 1]; + + module->sizes[i] = rbuf[2 * i]; if (module->sizes[i] || !module->noncontig) { module->bases[i] = ((char *) module->segment_base) + total; - total += rbuf[i]; + total += rbuf[2 * i]; if (module->noncontig) { total += OPAL_ALIGN_PAD_AMOUNT(total, pagesize); } @@ -390,12 +515,19 @@ component_select(struct ompi_win_t *win, void **base, size_t size, ptrdiff_t dis } } + ompi_osc_sm_refresh_notify_bases(module); + + /* Zero only this process's own counters. */ + memset((void *) module->notify_bases[ompi_comm_rank(module->comm)], 0, + notify_reserved * sizeof(int64_t)); + free(rbuf); } /* initialize my state shared */ module->my_node_state = &module->node_states[ompi_comm_rank(module->comm)]; - memset (module->my_node_state, 0, sizeof(*module->my_node_state)); + module->my_node_state->complete_count = 0; + memset (&module->my_node_state->lock, 0, sizeof(module->my_node_state->lock)); *base = module->bases[ompi_comm_rank(module->comm)]; @@ -550,6 +682,10 @@ ompi_osc_sm_free(struct ompi_win_t *win) module->comm->c_coll->coll_barrier(module->comm, module->comm->c_coll->coll_barrier_module); + if (NULL != module->notify_segment_base) { + opal_shmem_segment_detach (&module->notify_seg_ds); + } + opal_shmem_segment_detach (&module->seg_ds); } else { free(module->node_states); @@ -558,7 +694,14 @@ ompi_osc_sm_free(struct ompi_win_t *win) mca_mpool_base_default_module->mpool_free(mca_mpool_base_default_module, module->bases[0]); } + /* free the counters for the single process case */ + /* cast away the atomic/volatile qualifier for free(), as in + * opal/runtime/opal_progress.c */ + if (NULL != module->notify_bases) { + free((void *) module->notify_bases[0]); + } } + free(module->notify_bases); free(module->disp_units); free(module->outstanding_locks); free(module->sizes); @@ -605,6 +748,13 @@ ompi_osc_sm_get_info(struct ompi_win_t *win, struct opal_info_t **info_used) (module->noncontig) ? "true" : "false"); } + /* Report the assertion back only when one was actually given. */ + if (0 != module->notify_max_assert) { + char value_str[16]; + snprintf(value_str, sizeof(value_str), "%u", module->notify_max_assert); + opal_info_set(info, "mpi_assert_max_num_notify", value_str); + } + *info_used = info; return OMPI_SUCCESS; diff --git a/ompi/mca/osc/ubcl/configure.m4 b/ompi/mca/osc/ubcl/configure.m4 index add1db7c94b..1677000c469 100644 --- a/ompi/mca/osc/ubcl/configure.m4 +++ b/ompi/mca/osc/ubcl/configure.m4 @@ -1,4 +1,4 @@ -# Copyright (c) 2025 Bull SAS. All rights reserved. +# Copyright (c) 2025-2026 Bull SAS. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -21,6 +21,12 @@ AC_DEFUN([MCA_ompi_osc_ubcl_CONFIG], [ [osc_ubcl_happy="yes"], [osc_ubcl_happy="no"]) + # See ompi/mca/common/ubcl/configure.m4 comment explaining why UBCL linking + # flags are removed only in dynamic mode + AS_IF([test "$compile_mode" = "dso"], + [osc_ubcl_LDFLAGS=""], + [AC_MSG_WARN([Only DSO mode of osc/ubcl is tested (see --enable-mca-dso)])]) + AS_IF([test "$osc_ubcl_happy" = "yes"], [$1], [$2]) diff --git a/ompi/mca/osc/ubcl/osc_ubcl.c b/ompi/mca/osc/ubcl/osc_ubcl.c index 5e81ed1add3..5a81d0a763d 100644 --- a/ompi/mca/osc/ubcl/osc_ubcl.c +++ b/ompi/mca/osc/ubcl/osc_ubcl.c @@ -1,6 +1,6 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* - * Copyright (c) 2025 Bull SAS. All rights reserved. + * Copyright (c) 2025-2026 Bull SAS. All rights reserved. * * $COPYRIGHT$ * @@ -250,7 +250,9 @@ static int component_query(struct ompi_win_t *win, void **base, size_t size, ptr if (MPI_WIN_FLAVOR_ALLOCATE != flavor && MPI_WIN_FLAVOR_DYNAMIC != flavor && 0 < size && NULL != base && NULL != *base && opal_accelerator.check_addr(*base, &dev_id, &flags) > 0) { - mca_osc_ubcl_log(20, "GPU buffer not supported by osc/ubcl"); + mca_osc_ubcl_warn( + OPAL_ERR_NOT_SUPPORTED, + "GPU buffer not supported by osc/ubcl: disqualifying UBCL for this window creation"); return OPAL_ERR_NOT_SUPPORTED; } @@ -478,8 +480,11 @@ static int win_attach(struct ompi_win_t *win, void *base, size_t size) wid = (ubcl_wid_t) module->wid; /* Accelerator buffer is not supported as attached buffer */ - if (opal_accelerator.check_addr(base, &dev_id, &flags)) { - mca_osc_ubcl_warn(OPAL_ERR_NOT_SUPPORTED, "GPU buffer not supported by osc/ubcl"); + if (0 < size && NULL != base && opal_accelerator.check_addr(base, &dev_id, &flags)) { + mca_osc_ubcl_error( + OPAL_ERR_NOT_SUPPORTED, + "GPU buffer not supported by osc/ubcl: UBCL fail to attach %zu B starting at %p", size, + base); return OPAL_ERR_NOT_SUPPORTED; } diff --git a/ompi/mca/osc/ubcl/osc_ubcl_accumulate.c b/ompi/mca/osc/ubcl/osc_ubcl_accumulate.c index 58756665ee1..c3082188537 100644 --- a/ompi/mca/osc/ubcl/osc_ubcl_accumulate.c +++ b/ompi/mca/osc/ubcl/osc_ubcl_accumulate.c @@ -1,6 +1,6 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* - * Copyright (c) 2025 Bull SAS. All rights reserved. + * Copyright (c) 2025-2026 Bull SAS. All rights reserved. * * $COPYRIGHT$ * @@ -18,6 +18,7 @@ * of these functions, refer to ompi/mca/osc/osc.h. */ +#include "opal/include/opal_config.h" #include "ompi/mca/osc/ubcl/osc_ubcl.h" #include "opal/mca/common/ubcl/common_ubcl.h" #include "ompi/mca/osc/ubcl/osc_ubcl_info.h" @@ -211,8 +212,11 @@ static int get_logical_ubcl_type(struct ompi_datatype_t *origin_dt, #if OMPI_HAVE_FORTRAN_LOGICAL8 || MPI_LOGICAL8 == origin_dt #endif +/* To ease backport to older ompi versions */ +#if defined OMPI_HAVE_FORTRAN_LOGICAL16 #if OMPI_HAVE_FORTRAN_LOGICAL16 || MPI_LOGICAL16 == origin_dt +#endif #endif ) { ret = OMPI_ERR_NOT_IMPLEMENTED; diff --git a/ompi/mca/osc/ubcl/osc_ubcl_get.c b/ompi/mca/osc/ubcl/osc_ubcl_get.c index f0fb8ad7706..20824deac62 100644 --- a/ompi/mca/osc/ubcl/osc_ubcl_get.c +++ b/ompi/mca/osc/ubcl/osc_ubcl_get.c @@ -1,6 +1,6 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* - * Copyright (c) 2025 Bull SAS. All rights reserved. + * Copyright (c) 2025-2026 Bull SAS. All rights reserved. * * $COPYRIGHT$ * @@ -41,7 +41,7 @@ int ompi_osc_ubcl_rget(void *origin_addr, size_t origin_count, size_t target_span; size_t target_iov_count; struct iovec *target_iov; - void *target_addr; + char *target_addr; mca_common_ubcl_endpoint_t *endpoint; ubcl_memory_descriptor_t sbuf_md; mca_osc_ubcl_module_t *module; @@ -112,7 +112,10 @@ int ompi_osc_ubcl_rget(void *origin_addr, size_t origin_count, if (opal_convertor_on_device(&osc_req->origin_convertor)) { opal_free_list_return(&mca_osc_ubcl_component.req_free_list, &(osc_req->super)); - mca_osc_ubcl_warn(OPAL_ERR_NOT_SUPPORTED, "GPU buffer not supported by osc/ubcl"); + mca_osc_ubcl_error( + OPAL_ERR_NOT_SUPPORTED, + "GPU buffer not supported by osc/ubcl: cannot perform MPI_Get of buffer %p", + origin_addr); ret = OPAL_ERR_NOT_SUPPORTED; goto exit; } diff --git a/ompi/mca/osc/ubcl/osc_ubcl_put.c b/ompi/mca/osc/ubcl/osc_ubcl_put.c index ae45c45d511..17a7479e6a2 100644 --- a/ompi/mca/osc/ubcl/osc_ubcl_put.c +++ b/ompi/mca/osc/ubcl/osc_ubcl_put.c @@ -1,6 +1,6 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* - * Copyright (c) 2025 Bull SAS. All rights reserved. + * Copyright (c) 2025-2026 Bull SAS. All rights reserved. * * $COPYRIGHT$ * @@ -50,7 +50,7 @@ int ompi_osc_ubcl_rput(const void *origin_addr, size_t origin_count, size_t span; size_t target_iov_count; struct iovec *target_iov; - void *target_addr; + char *target_addr; mca_common_ubcl_endpoint_t *endpoint; ubcl_memory_descriptor_t sbuf_md; mca_osc_ubcl_module_t *module = (mca_osc_ubcl_module_t *) win->w_osc_module; @@ -120,7 +120,10 @@ int ompi_osc_ubcl_rput(const void *origin_addr, size_t origin_count, if (opal_convertor_on_device(&osc_req->origin_convertor)) { opal_free_list_return(&mca_osc_ubcl_component.req_free_list, &(osc_req->super)); - mca_osc_ubcl_warn(OPAL_ERR_NOT_SUPPORTED, "GPU buffer not supported by osc/ubcl"); + mca_osc_ubcl_error( + OPAL_ERR_NOT_SUPPORTED, + "GPU buffer not supported by osc/ubcl: cannot perform MPI_Put of buffer %p", + origin_addr); ret = OPAL_ERR_NOT_SUPPORTED; goto exit; } diff --git a/ompi/mca/osc/ucx/osc_ucx.h b/ompi/mca/osc/ucx/osc_ucx.h index bc3dc8a91b3..f79db5d1070 100644 --- a/ompi/mca/osc/ucx/osc_ucx.h +++ b/ompi/mca/osc/ucx/osc_ucx.h @@ -27,6 +27,11 @@ #define OMPI_OSC_UCX_POST_PEER_MAX 32 #define OMPI_OSC_UCX_ATTACH_MAX 48 #define OMPI_OSC_UCX_MEM_ADDR_MAX_LEN 1024 +/* Default number of RMA notification counters reserved per MPI process in each + * window's registered memory region. Overridden per job by the + * osc_ucx_num_notify_counters MCA parameter and per window by the + * "mpi_assert_max_num_notify" info key. */ +#define OMPI_OSC_UCX_DEFAULT_NOTIFY_COUNTERS 16 typedef struct ompi_osc_ucx_component { @@ -43,6 +48,9 @@ typedef struct ompi_osc_ucx_component { bool no_locks; /* Default value of the no_locks info key for new windows */ bool acc_single_intrinsic; unsigned int priority; + /* Number of notification counters reserved per MPI process in each window, + * unless the window's info gives "mpi_assert_max_num_notify". */ + unsigned int num_notify_counters; /* directory where to place backing files */ char *backing_directory; } ompi_osc_ucx_component_t; @@ -122,6 +130,24 @@ typedef struct ompi_osc_ucx_module { struct ompi_communicator_t *comm; int flavor; size_t size; + int *notify_counts; /* per-rank number of notification counters *attached* at each + * rank (size comm_size), as set by MPI_WIN_SET_NUM_NOTIFY and + * kept consistent across the group by an allgather. Always + * <= notify_capacity. */ + unsigned int notify_capacity; /* notification counters currently reserved per rank. + * Agreed on across the group and uniform. Grown on + * demand by MPI_WIN_SET_NUM_NOTIFY unless + * notify_max_assert caps it. */ + unsigned int notify_max_assert; /* non-zero only if *every* rank passed + * "mpi_assert_max_num_notify" at window creation. + * Then the agreed reservation is a hard cap and the + * counters never grow (MPI-5.1 12.2: the assertion + * lets the implementation optimize the allocation). + * Zero means no rank asserted a bound, so the + * standard's "does not assume any limit" applies. */ + uint64_t *notify_addrs; /* per-rank base address of the notification counters + * (size comm_size) */ + void *notify_base; /* this rank's counters; notify_capacity uint64_t */ size_t *sizes; /* used if not every process has the same size */ uint64_t *addrs; uint64_t *state_addrs; @@ -149,6 +175,11 @@ typedef struct ompi_osc_ucx_module { opal_common_ucx_ctx_t *ctx; opal_common_ucx_wpmem_t *mem; opal_common_ucx_wpmem_t *state_mem; + /* Notification counters get their own registration rather than being + * appended to the window data: the data region for MPI_WIN_FLAVOR_CREATE + * belongs to the user and has no room for them, and a dynamic window has + * no data region at all. */ + opal_common_ucx_wpmem_t *notify_mem; ompi_osc_ucx_mem_ranges_t *epoc_outstanding_ops_mems; bool skip_sync_check; bool noncontig_shared_win; @@ -277,6 +308,75 @@ int ompi_osc_find_attached_region_position(ompi_osc_dynamic_win_info_t *dynamic_ int ompi_osc_ucx_dynamic_lock(ompi_osc_ucx_module_t *module, int target); int ompi_osc_ucx_dynamic_unlock(ompi_osc_ucx_module_t *module, int target); +int ompi_osc_ucx_put_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, struct ompi_win_t *win); +int ompi_osc_ucx_get_notify(void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, struct ompi_win_t *win); +int ompi_osc_ucx_rput_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, struct ompi_win_t *win, + struct ompi_request_t **request); +int ompi_osc_ucx_rget_notify(void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, struct ompi_win_t *win, + struct ompi_request_t **request); +int ompi_osc_ucx_accumulate_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, int notify, + struct ompi_win_t *win); +int ompi_osc_ucx_get_accumulate_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + void *result_addr, size_t result_count, + struct ompi_datatype_t *result_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, int notify, + struct ompi_win_t *win); +int ompi_osc_ucx_raccumulate_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, int notify, + struct ompi_win_t *win, + struct ompi_request_t **request); +int ompi_osc_ucx_rget_accumulate_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + void *result_addr, size_t result_count, + struct ompi_datatype_t *result_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, int notify, + struct ompi_win_t *win, + struct ompi_request_t **request); +int ompi_osc_ucx_win_get_notify_value(struct ompi_win_t *win, int notify, + OMPI_MPI_COUNT_TYPE *value); +int ompi_osc_ucx_win_get_notify_bounds(struct ompi_win_t *win, int *num_sb, int *num_ub, + OMPI_MPI_COUNT_TYPE *value_ub); +int ompi_osc_ucx_win_reset_notify_value(struct ompi_win_t *win, int notify, + OMPI_MPI_COUNT_TYPE *value); +int ompi_osc_ucx_win_set_num_notify(struct ompi_win_t *win, struct opal_info_t *info, + int num_notifications); +/* Collectively re-reserve new_capacity notification counters per rank, replacing + * the current registration. Defined in osc_ucx_component.c because it needs the + * component's address-exchange helper. */ +int ompi_osc_ucx_grow_notify_counters(ompi_osc_ucx_module_t *module, + unsigned int new_capacity); + +int ompi_osc_ucx_win_get_num_notify(struct ompi_win_t *win, int target_rank, + int *num_notifications); + /* returns the size at the peer */ static inline size_t ompi_osc_ucx_get_size(ompi_osc_ucx_module_t *module, int rank) { diff --git a/ompi/mca/osc/ucx/osc_ucx_comm.c b/ompi/mca/osc/ucx/osc_ucx_comm.c index 0354edb71c0..1f400f85fb9 100644 --- a/ompi/mca/osc/ucx/osc_ucx_comm.c +++ b/ompi/mca/osc/ucx/osc_ucx_comm.c @@ -17,9 +17,15 @@ #include "ompi/mca/osc/base/osc_base_obj_convert.h" #include "opal/mca/common/ucx/common_ucx.h" +#include + #include "osc_ucx.h" #include "osc_ucx_request.h" +#include + +#include "ompi/attribute/attribute.h" + #define CHECK_VALID_RKEY(_module, _target, _count) \ if (!((_module)->win_info_array[_target]).rkey_init && ((_count) > 0)) { \ @@ -603,6 +609,589 @@ int ompi_osc_ucx_get(void *origin_addr, size_t origin_count, } } +static int osc_ucx_request_over_flush(ompi_osc_ucx_module_t *module, + struct ompi_win_t *win, int target, + ucp_ep_h *ep, enum req_type req_type, + struct ompi_request_t **request); + +static int accumulate_req(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, struct ompi_win_t *win, + ompi_osc_ucx_accumulate_request_t *ucx_req); + +static int get_accumulate_req(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + void *result_addr, size_t result_count, + struct ompi_datatype_t *result_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, struct ompi_win_t *win, + ompi_osc_ucx_accumulate_request_t *ucx_req); + +/* Returns the remote address of notify counter[notify] for the given target. + * The counters have their own registered region (module->notify_mem), separate + * from the window data, so this is independent of the window's flavor and size. */ +static inline uint64_t +osc_ucx_notify_counter_addr(ompi_osc_ucx_module_t *module, int target, int notify) +{ + return module->notify_addrs[target] + (uint64_t)notify * sizeof(uint64_t); +} + +/* A region of module->notify_capacity notification counters is registered per + * rank at window creation (see osc_ucx_component.c), but only the first + * notify_counts[rank] of them are considered *attached* by + * MPI_WIN_SET_NUM_NOTIFY. Per the MPI Standard it is erroneous to reference a + * counter that is out of range at the target, so validate against the target + * rank's attached count. */ +#define CHECK_NOTIFY_IDX(module, notify, rank) \ + if ((notify) < 0 || (notify) >= (module)->notify_counts[rank]) { \ + return MPI_ERR_RMA_NOTIFICATION; \ + } + +/* Increments the target's notification counter once the preceding data + * operation has been ordered ahead of it. Shared by every notified + * operation; they differ only in which base operation they issue first and + * in whether a fence or a flush is needed to order it. + * + * Note that for the request-based variants the data operation has already been + * issued and *request already handed back by the time this can fail, so an + * error return leaves that request outstanding and the caller still has to + * complete it. A transport failure here is not recoverable in any case. */ +static inline int +osc_ucx_notify_target(ompi_osc_ucx_module_t *module, int target, int notify, + ucp_ep_h *ep) +{ + int ret = opal_common_ucx_wpmem_post(module->notify_mem, + UCP_ATOMIC_POST_OP_ADD, 1, + target, sizeof(uint64_t), + osc_ucx_notify_counter_addr(module, target, notify), + ep); + return (OPAL_SUCCESS == ret) ? OMPI_SUCCESS : OMPI_ERROR; +} + +int ompi_osc_ucx_win_get_notify_value(struct ompi_win_t *win, int notify, + OMPI_MPI_COUNT_TYPE *value) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + int my_rank = ompi_comm_rank(module->comm); + + CHECK_NOTIFY_IDX(module, notify, my_rank); + + /* Origins increment this counter with a UCX atomic, which the transport may + * emulate in software on the local worker rather than offload to the NIC. + * In that case the counter only advances while the worker is progressed, so + * a consumer spinning on MPI_WIN_GET_NOTIFY_VALUE -- the natural way to wait + * for a notification -- would never observe the update. Progress the worker + * here so that such a loop makes forward progress on its own, as every other + * spin-wait in this component does. */ + opal_common_ucx_wpool_progress(mca_osc_ucx_component.wpool); + + volatile uint64_t *counter = + (volatile uint64_t *)osc_ucx_notify_counter_addr(module, my_rank, notify); + *value = (OMPI_MPI_COUNT_TYPE)*counter; + opal_atomic_rmb(); + return OMPI_SUCCESS; +} + +int ompi_osc_ucx_win_reset_notify_value(struct ompi_win_t *win, int notify, + OMPI_MPI_COUNT_TYPE *value) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + int my_rank = ompi_comm_rank(module->comm); + uint64_t result_value = 0; + ucp_ep_h *ep; + int ret; + + CHECK_NOTIFY_IDX(module, notify, my_rank); + + OSC_UCX_GET_DEFAULT_EP(ep, module, my_rank); + + /* The counter is incremented by remote origins through UCX network atomic + * operations, so reset it with a UCX atomic swap (targeting our own rank) + * rather than a CPU atomic. That keeps the read-and-zero atomic with + * respect to those concurrent network atomics — a plain CPU swap is not + * ordered against them. The fetch returns the counter's previous value. */ + ret = opal_common_ucx_wpmem_fetch(module->notify_mem, + UCP_ATOMIC_FETCH_OP_SWAP, 0, + my_rank, &result_value, sizeof(result_value), + osc_ucx_notify_counter_addr(module, my_rank, notify), + ep); + if (OPAL_SUCCESS != ret) { + OSC_UCX_VERBOSE(1, "opal_common_ucx_wpmem_fetch failed: %d", ret); + return OMPI_ERROR; + } + + *value = (OMPI_MPI_COUNT_TYPE)result_value; + return OMPI_SUCCESS; +} + +int ompi_osc_ucx_win_get_num_notify(struct ompi_win_t *win, int target_rank, + int *num_notifications) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + + if (target_rank < 0 || target_rank >= ompi_comm_size(module->comm)) { + return MPI_ERR_RANK; + } + + /* Local query (MPI_WIN_GET_NUM_NOTIFY, §12.6.1): return the number of + * notification counters currently attached at target_rank, as last + * published by MPI_WIN_SET_NUM_NOTIFY. */ + *num_notifications = module->notify_counts[target_rank]; + return OMPI_SUCCESS; +} + +int ompi_osc_ucx_win_set_num_notify(struct ompi_win_t *win, struct opal_info_t *info, + int num_notifications) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + int comm_size = ompi_comm_size(module->comm); + int requested = num_notifications; + int *requested_counts; + unsigned int needed; + int ret, i; + + (void) info; /* "mpi_assert_same_num_notifications" is an optimization hint only */ + + /* When every rank asserted "mpi_assert_max_num_notify" at window creation, + * that value is a hard upper bound and asking for more is erroneous. + * Otherwise MPI-5.1 12.2 says no limit is assumed, so a request above the + * current reservation grows the counters below rather than failing. + * + * This is a synchronizing collective, so a rank with a bad argument must not + * return before the allgather below -- that would leave the rest of the + * group blocked in it. Mark the request instead and let every rank discover + * the error from the gathered values. */ + if (requested < 0 || + (0 != module->notify_max_assert && + (unsigned int) requested > module->notify_max_assert)) { + requested = -1; + } + + requested_counts = malloc(comm_size * sizeof(int)); + if (NULL == requested_counts) { + return OMPI_ERR_TEMP_OUT_OF_RESOURCE; + } + + /* All notification counters (existing and newly attached) are reset to zero + * by this call. Resetting before the allgather is what makes the standard's + * "will not return until ... all processes have adjusted the number of + * notification counters" hold: completing the collective implies every rank + * has already reset, so no rank can return and then have a peer wipe the + * notification it just delivered. It is erroneous to call this while an + * access epoch is open, so no concurrent network atomics touch the counters + * and a plain local reset is sufficient. */ + if (NULL != module->notify_base) { + memset(module->notify_base, 0, module->notify_capacity * sizeof(uint64_t)); + } + opal_atomic_wmb(); + + /* Publish every rank's requested count to the whole group so that origins + * can validate notification indices against the target's count. Gathering + * the requested value directly is what makes MPI_WIN_GET_NUM_NOTIFY return + * the value given here, including when it lowers the count. */ + ret = module->comm->c_coll->coll_allgather(&requested, 1, MPI_INT, + requested_counts, 1, MPI_INT, + module->comm, + module->comm->c_coll->coll_allgather_module); + if (OMPI_SUCCESS != ret) { + free(requested_counts); + return ret; + } + + for (i = 0; i < comm_size; i++) { + if (0 > requested_counts[i]) { + /* Some rank asked for a count outside [0, notify_capacity]. Every + * rank sees the same gathered array and bails identically, so the + * attached counts stay as they were rather than the group ending up + * half-reconfigured. The counters have been zeroed, which is + * harmless for a call that is erroneous anyway. */ + free(requested_counts); + return MPI_ERR_ARG; + } + } + + /* Every rank sees the same gathered array, so they all reach the same + * decision about whether to grow and to what size, without extra + * communication. The reservation is uniform across the window (window + * creation agrees it with an allreduce), so it grows to the largest request + * anyone made. Never shrink: a rank that lowered its count keeps the space + * it already has, so only genuine growth costs a re-registration and + * alternating high/low requests do not thrash the NIC. */ + needed = module->notify_capacity; + for (i = 0; i < comm_size; i++) { + if ((unsigned int) requested_counts[i] > needed) { + needed = (unsigned int) requested_counts[i]; + } + } + + if (needed > module->notify_capacity) { + ret = ompi_osc_ucx_grow_notify_counters(module, needed); + if (OMPI_SUCCESS != ret) { + free(requested_counts); + return ret; + } + + /* MPI_WIN_NOTIFICATION_NUM_SB is the count supported without paying for + * a re-registration, so it has to follow the reservation rather than + * stay at whatever was cached when the window was created. */ + ret = ompi_attr_set_int(WIN_ATTR, win, &win->w_keyhash, + MPI_WIN_NOTIFICATION_NUM_SB, + (int) module->notify_capacity, true); + if (OMPI_SUCCESS != ret) { + free(requested_counts); + return ret; + } + } + + memcpy(module->notify_counts, requested_counts, comm_size * sizeof(int)); + free(requested_counts); + + return OMPI_SUCCESS; +} + +int ompi_osc_ucx_win_get_notify_bounds(struct ompi_win_t *win, int *num_sb, int *num_ub, + OMPI_MPI_COUNT_TYPE *value_ub) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + + /* The current reservation is what is supported without paying for a + * re-registration, so it is the suggested bound. The hard bound is only + * real when every rank asserted "mpi_assert_max_num_notify" at window + * creation; otherwise MPI_WIN_SET_NUM_NOTIFY grows the counters on demand + * and the only limit is what can be allocated. Neither depends on the + * window's flavor. */ + *num_sb = (int) module->notify_capacity; + *num_ub = (0 != module->notify_max_assert) ? (int) module->notify_max_assert + : INT_MAX; + + /* Counters are uint64_t and only ever incremented by one per notified + * operation, but they are returned to the user as a signed MPI_Count, so + * the representable maximum of that type is the real bound. */ + *value_ub = (OMPI_MPI_COUNT_TYPE) INT64_MAX; + + return OMPI_SUCCESS; +} + +int ompi_osc_ucx_put_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, struct ompi_win_t *win) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + ucp_ep_h *ep; + int ret; + + CHECK_NOTIFY_IDX(module, notify, target); + + OSC_UCX_GET_DEFAULT_EP(ep, module, target); + + ret = ompi_osc_ucx_put(origin_addr, origin_count, origin_dt, + target, target_disp, target_count, target_dt, win); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Flush to ensure the PUT is visible at the target before the counter + * increment arrives. */ + ret = opal_common_ucx_wpmem_fence(module->mem); + if (OPAL_SUCCESS != ret) { + return OMPI_ERROR; + } + + /* Atomically increment the target's notify counter in-place using the + * same mem handle as the window data. */ + return osc_ucx_notify_target(module, target, notify, ep); +} + +int ompi_osc_ucx_get_notify(void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, struct ompi_win_t *win) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + ucp_ep_h *ep; + int ret; + + CHECK_NOTIFY_IDX(module, notify, target); + + OSC_UCX_GET_DEFAULT_EP(ep, module, target); + + ret = ompi_osc_ucx_get(origin_addr, origin_count, origin_dt, + target, target_disp, target_count, target_dt, win); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Flush to ensure the GET data is locally available before issuing the + * counter increment back to the target. */ + ret = opal_common_ucx_ctx_flush(module->ctx, OPAL_COMMON_UCX_SCOPE_EP, target); + if (OPAL_SUCCESS != ret) { + return OMPI_ERROR; + } + + return osc_ucx_notify_target(module, target, notify, ep); +} + +int ompi_osc_ucx_rput_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, struct ompi_win_t *win, + struct ompi_request_t **request) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + ucp_ep_h *ep; + int ret; + + CHECK_NOTIFY_IDX(module, notify, target); + + OSC_UCX_GET_DEFAULT_EP(ep, module, target); + + ret = check_sync_state(module, target, true); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Issue the data movement and the notification first, and only then build + * the request over a flush -- that way the flush covers both, so completing + * the request means the counter update has been pushed out and not merely + * queued behind the origin's next MPI call. */ + ret = ompi_osc_ucx_put(origin_addr, origin_count, origin_dt, target, + target_disp, target_count, target_dt, win); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Fence to order the PUT before the counter increment. */ + ret = opal_common_ucx_wpmem_fence(module->mem); + if (OPAL_SUCCESS != ret) { + return OMPI_ERROR; + } + + ret = osc_ucx_notify_target(module, target, notify, ep); + if (OMPI_SUCCESS != ret) { + return ret; + } + + return osc_ucx_request_over_flush(module, win, target, ep, RPUT_REQ, request); +} + +int ompi_osc_ucx_rget_notify(void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + int notify, struct ompi_win_t *win, + struct ompi_request_t **request) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + ucp_ep_h *ep; + int ret; + + CHECK_NOTIFY_IDX(module, notify, target); + + OSC_UCX_GET_DEFAULT_EP(ep, module, target); + + ret = check_sync_state(module, target, true); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* As in rput_notify, the data movement and the notification both precede + * the request-bearing flush so that the request covers both. */ + ret = ompi_osc_ucx_get(origin_addr, origin_count, origin_dt, target, + target_disp, target_count, target_dt, win); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Blocking flush: the notification tells the target its window has been + * read, so the GET must have completed before the counter is incremented + * (MPI-5.1 12.6.4 requires that order). */ + ret = opal_common_ucx_ctx_flush(module->ctx, OPAL_COMMON_UCX_SCOPE_EP, target); + if (OPAL_SUCCESS != ret) { + return OMPI_ERROR; + } + + ret = osc_ucx_notify_target(module, target, notify, ep); + if (OMPI_SUCCESS != ret) { + return ret; + } + + return osc_ucx_request_over_flush(module, win, target, ep, RGET_REQ, request); +} + +int ompi_osc_ucx_accumulate_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, int notify, + struct ompi_win_t *win) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + ucp_ep_h *ep; + int ret; + + CHECK_NOTIFY_IDX(module, notify, target); + + OSC_UCX_GET_DEFAULT_EP(ep, module, target); + + ret = ompi_osc_ucx_accumulate(origin_addr, origin_count, origin_dt, + target, target_disp, target_count, target_dt, + op, win); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Fence so that the accumulate is applied at the target before the counter + * increment, as §12.6.4 requires. */ + ret = opal_common_ucx_wpmem_fence(module->mem); + if (OPAL_SUCCESS != ret) { + return OMPI_ERROR; + } + + return osc_ucx_notify_target(module, target, notify, ep); +} + +int ompi_osc_ucx_get_accumulate_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + void *result_addr, size_t result_count, + struct ompi_datatype_t *result_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, int notify, + struct ompi_win_t *win) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + ucp_ep_h *ep; + int ret; + + CHECK_NOTIFY_IDX(module, notify, target); + + OSC_UCX_GET_DEFAULT_EP(ep, module, target); + + ret = ompi_osc_ucx_get_accumulate(origin_addr, origin_count, origin_dt, + result_addr, result_count, result_dt, + target, target_disp, target_count, target_dt, + op, win); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Flush rather than fence: the result buffer must be locally valid, and the + * update must have been applied at the target, before the target may + * observe the notification. */ + ret = opal_common_ucx_ctx_flush(module->ctx, OPAL_COMMON_UCX_SCOPE_EP, target); + if (OPAL_SUCCESS != ret) { + return OMPI_ERROR; + } + + return osc_ucx_notify_target(module, target, notify, ep); +} + +int ompi_osc_ucx_raccumulate_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, int notify, + struct ompi_win_t *win, + struct ompi_request_t **request) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + ucp_ep_h *ep; + int ret; + + CHECK_NOTIFY_IDX(module, notify, target); + + OSC_UCX_GET_DEFAULT_EP(ep, module, target); + + ret = check_sync_state(module, target, true); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Passing a NULL request runs accumulate_req as the blocking accumulate: it + * ends with a blocking flush, so the read-modify-write has completed at the + * target before we return here. The notification issued below is therefore + * correctly ordered after the window access (MPI-5.1 12.6.4). Building our + * own request afterwards -- rather than taking the pre-completed one from + * ompi_osc_ucx_raccumulate -- is what lets that request also cover the + * notification. */ + ret = accumulate_req(origin_addr, origin_count, origin_dt, target, target_disp, + target_count, target_dt, op, win, NULL); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Fence to order the accumulate ahead of the counter increment. */ + ret = opal_common_ucx_wpmem_fence(module->mem); + if (OPAL_SUCCESS != ret) { + return OMPI_ERROR; + } + + ret = osc_ucx_notify_target(module, target, notify, ep); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* The accumulate is already complete, so the request only has to represent + * the notification reaching the wire; RPUT_REQ selects the plain + * flush-completion behaviour rather than the accumulate state machine. */ + return osc_ucx_request_over_flush(module, win, target, ep, RPUT_REQ, request); +} + +int ompi_osc_ucx_rget_accumulate_notify(const void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + void *result_addr, size_t result_count, + struct ompi_datatype_t *result_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, + struct ompi_op_t *op, int notify, + struct ompi_win_t *win, + struct ompi_request_t **request) +{ + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t *)win->w_osc_module; + ucp_ep_h *ep; + int ret; + + CHECK_NOTIFY_IDX(module, notify, target); + + OSC_UCX_GET_DEFAULT_EP(ep, module, target); + + ret = check_sync_state(module, target, true); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* As in raccumulate_notify: a NULL request makes this the blocking form, + * which ends with a blocking flush, so both the fetched result and the + * update at the target are complete before the notification is issued. */ + ret = get_accumulate_req(origin_addr, origin_count, origin_dt, + result_addr, result_count, result_dt, + target, target_disp, target_count, target_dt, + op, win, NULL); + if (OMPI_SUCCESS != ret) { + return ret; + } + + /* Fence to order the accumulate ahead of the counter increment. */ + ret = opal_common_ucx_wpmem_fence(module->mem); + if (OPAL_SUCCESS != ret) { + return OMPI_ERROR; + } + + ret = osc_ucx_notify_target(module, target, notify, ep); + if (OMPI_SUCCESS != ret) { + return ret; + } + + return osc_ucx_request_over_flush(module, win, target, ep, RGET_REQ, request); +} + static inline bool ompi_osc_need_acc_lock(ompi_osc_ucx_module_t *module, int target) { ompi_osc_ucx_lock_t *lock = NULL; @@ -1490,31 +2079,30 @@ int ompi_osc_ucx_get_accumulate_nb(const void *origin_addr, size_t origin_count, target_count, target_dt, op, win, GET_ACCUMULATE); } -int ompi_osc_ucx_rput(const void *origin_addr, size_t origin_count, - struct ompi_datatype_t *origin_dt, - int target, ptrdiff_t target_disp, size_t target_count, - struct ompi_datatype_t *target_dt, - struct ompi_win_t *win, struct ompi_request_t **request) { - ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t*) win->w_osc_module; - ucp_ep_h *ep; - OSC_UCX_GET_DEFAULT_EP(ep, module, target); +/* Attach an MPI request to a nonblocking worker flush. + * + * The request completes when ucp_worker_flush_nb completes, and that flush + * covers every operation already issued on this worker -- whichever memory + * registration they used. So the request waits for exactly what the caller + * issued *before* getting here. The notified variants rely on this: they issue + * their counter atomic first, so completing the request implies the + * notification has been pushed to the target rather than left queued locally + * (MPI-5.1 12.6.4, advice to implementors). + * + * Allocating the request last also keeps the failure contract clean: anything + * that can fail has already run, so callers return their errors with *request + * untouched, as MPI expects of a call that reports an error. */ +static int osc_ucx_request_over_flush(ompi_osc_ucx_module_t *module, + struct ompi_win_t *win, int target, + ucp_ep_h *ep, enum req_type req_type, + struct ompi_request_t **request) +{ opal_common_ucx_wpmem_t *mem = module->mem; uint64_t remote_addr = (module->state_addrs[target]) + OSC_UCX_STATE_REQ_FLAG_OFFSET; ompi_osc_ucx_generic_request_t *ucx_req = NULL; int ret = OMPI_SUCCESS; - ret = check_sync_state(module, target, true); - if (ret != OMPI_SUCCESS) { - return ret; - } - - ret = ompi_osc_ucx_put(origin_addr, origin_count, origin_dt, target, target_disp, - target_count, target_dt, win); - if (ret != OMPI_SUCCESS) { - return ret; - } - - OMPI_OSC_UCX_GENERIC_REQUEST_ALLOC(win, ucx_req, RPUT_REQ); + OMPI_OSC_UCX_GENERIC_REQUEST_ALLOC(win, ucx_req, req_type); ucx_req->super.module = module; OSC_UCX_INCREMENT_OUTSTANDING_NB_OPS(module); @@ -1544,17 +2132,14 @@ int ompi_osc_ucx_rput(const void *origin_addr, size_t origin_count, return ret; } -int ompi_osc_ucx_rget(void *origin_addr, size_t origin_count, +int ompi_osc_ucx_rput(const void *origin_addr, size_t origin_count, struct ompi_datatype_t *origin_dt, int target, ptrdiff_t target_disp, size_t target_count, - struct ompi_datatype_t *target_dt, struct ompi_win_t *win, - struct ompi_request_t **request) { + struct ompi_datatype_t *target_dt, + struct ompi_win_t *win, struct ompi_request_t **request) { ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t*) win->w_osc_module; ucp_ep_h *ep; OSC_UCX_GET_DEFAULT_EP(ep, module, target); - opal_common_ucx_wpmem_t *mem = module->mem; - uint64_t remote_addr = (module->state_addrs[target]) + OSC_UCX_STATE_REQ_FLAG_OFFSET; - ompi_osc_ucx_generic_request_t *ucx_req = NULL; int ret = OMPI_SUCCESS; ret = check_sync_state(module, target, true); @@ -1562,40 +2147,37 @@ int ompi_osc_ucx_rget(void *origin_addr, size_t origin_count, return ret; } - ret = ompi_osc_ucx_get(origin_addr, origin_count, origin_dt, target, target_disp, + ret = ompi_osc_ucx_put(origin_addr, origin_count, origin_dt, target, target_disp, target_count, target_dt, win); if (ret != OMPI_SUCCESS) { return ret; } - OMPI_OSC_UCX_GENERIC_REQUEST_ALLOC(win, ucx_req, RGET_REQ); - ucx_req->super.module = module; + return osc_ucx_request_over_flush(module, win, target, ep, RPUT_REQ, request); +} - OSC_UCX_INCREMENT_OUTSTANDING_NB_OPS(module); - ret = opal_common_ucx_wpmem_flush_ep_nb(mem, target, ompi_osc_ucx_req_completion, ucx_req, ep); +int ompi_osc_ucx_rget(void *origin_addr, size_t origin_count, + struct ompi_datatype_t *origin_dt, + int target, ptrdiff_t target_disp, size_t target_count, + struct ompi_datatype_t *target_dt, struct ompi_win_t *win, + struct ompi_request_t **request) { + ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t*) win->w_osc_module; + ucp_ep_h *ep; + OSC_UCX_GET_DEFAULT_EP(ep, module, target); + int ret = OMPI_SUCCESS; + ret = check_sync_state(module, target, true); if (ret != OMPI_SUCCESS) { - /* fallback to using an atomic op to acquire a request handle */ - ret = opal_common_ucx_wpmem_fence(mem); - if (ret != OMPI_SUCCESS) { - OSC_UCX_VERBOSE(1, "opal_common_ucx_mem_fence failed: %d", ret); - OMPI_OSC_UCX_REQUEST_RETURN(ucx_req); - return OMPI_ERROR; - } - - ret = opal_common_ucx_wpmem_fetch_nb(mem, UCP_ATOMIC_FETCH_OP_FADD, - 0, target, &(module->req_result), - sizeof(uint64_t), remote_addr & (~0x7), - ompi_osc_ucx_req_completion, ucx_req, ep); - if (ret != OMPI_SUCCESS) { - OMPI_OSC_UCX_REQUEST_RETURN(ucx_req); - return ret; - } + return ret; } - *request = &ucx_req->super.super; + ret = ompi_osc_ucx_get(origin_addr, origin_count, origin_dt, target, target_disp, + target_count, target_dt, win); + if (ret != OMPI_SUCCESS) { + return ret; + } - return ret; + return osc_ucx_request_over_flush(module, win, target, ep, RGET_REQ, request); } int ompi_osc_ucx_raccumulate(const void *origin_addr, size_t origin_count, diff --git a/ompi/mca/osc/ucx/osc_ucx_component.c b/ompi/mca/osc/ucx/osc_ucx_component.c index 635a53a3e0f..4c33b366543 100644 --- a/ompi/mca/osc/ucx/osc_ucx_component.c +++ b/ompi/mca/osc/ucx/osc_ucx_component.c @@ -25,6 +25,10 @@ #include "osc_ucx.h" #include "osc_ucx_request.h" #include "opal/util/sys_limits.h" +#include "opal/util/info.h" +#include "opal/class/opal_cstring.h" + +#include #define memcpy_off(_dst, _src, _len, _off) \ memcpy(((char*)(_dst)) + (_off), _src, _len); \ @@ -102,6 +106,20 @@ ompi_osc_ucx_module_t ompi_osc_ucx_module_template = { .osc_fetch_and_op = ompi_osc_ucx_fetch_and_op, .osc_get_accumulate = ompi_osc_ucx_get_accumulate, + .osc_put_notify = ompi_osc_ucx_put_notify, + .osc_get_notify = ompi_osc_ucx_get_notify, + .osc_rput_notify = ompi_osc_ucx_rput_notify, + .osc_rget_notify = ompi_osc_ucx_rget_notify, + .osc_accumulate_notify = ompi_osc_ucx_accumulate_notify, + .osc_get_accumulate_notify = ompi_osc_ucx_get_accumulate_notify, + .osc_raccumulate_notify = ompi_osc_ucx_raccumulate_notify, + .osc_rget_accumulate_notify = ompi_osc_ucx_rget_accumulate_notify, + .osc_win_get_notify_value = ompi_osc_ucx_win_get_notify_value, + .osc_win_reset_notify_value = ompi_osc_ucx_win_reset_notify_value, + .osc_win_set_num_notify = ompi_osc_ucx_win_set_num_notify, + .osc_win_get_num_notify = ompi_osc_ucx_win_get_num_notify, + .osc_win_get_notify_bounds = ompi_osc_ucx_win_get_notify_bounds, + .osc_rput = ompi_osc_ucx_rput, .osc_rget = ompi_osc_ucx_rget, .osc_raccumulate = ompi_osc_ucx_raccumulate, @@ -150,6 +168,53 @@ static bool check_config_value_bool (char *key, opal_info_t *info) return flag_value[0]; } +/* Read the mpi_assert_max_num_notify info key (MPI-5.1 section 12.2) to decide + * how many notification counters to reserve per MPI process initially, and + * report in *asserted whether the key was actually given. + * + * The key is an assertion by the caller that it will not ask + * MPI_WIN_SET_NUM_NOTIFY for more than this, which lets us size the + * registration once and treat it as a hard upper bound. Without it the + * standard is explicit that "the implementation does not assume any limit", so + * the reservation is only a starting size and the counters grow on demand. */ +static int osc_ucx_reserved_notify_counters(opal_info_t *info, unsigned int *reserved, + bool *asserted) +{ + opal_cstring_t *value_string; + int flag = 0, value = 0; + + *reserved = mca_osc_ucx_component.num_notify_counters; + *asserted = false; + + if (NULL == info) { + return OMPI_SUCCESS; + } + + if (OMPI_SUCCESS != opal_info_get(info, "mpi_assert_max_num_notify", + &value_string, &flag) || !flag) { + return OMPI_SUCCESS; + } + + if (OPAL_SUCCESS != opal_cstring_to_int(value_string, &value)) { + OBJ_RELEASE(value_string); + return MPI_ERR_INFO; + } + OBJ_RELEASE(value_string); + + /* A negative value is a malformed key rather than "no assertion"; only 0 + * carries the "assume nothing" meaning. */ + if (value < 0) { + return MPI_ERR_INFO; + } + + if (0 != value) { + *reserved = (unsigned int) value; + *asserted = true; + } + + return OMPI_SUCCESS; +} + static int component_open(void) { opal_common_ucx_mca_register(); @@ -220,6 +285,23 @@ static int component_register(void) { MCA_BASE_VAR_SCOPE_GROUP, &ompi_osc_ucx_outstanding_ops_flush_threshold); free(description_str); + mca_osc_ucx_component.num_notify_counters = OMPI_OSC_UCX_DEFAULT_NOTIFY_COUNTERS; + + opal_asprintf(&description_str, + "Number of RMA notification counters reserved per MPI process " + "in the registered memory region of each window. Windows whose " + "info gives an mpi_assert_max_num_notify value use that instead. " + "This is a hard upper bound: the counters share the window's " + "registration, so MPI_Win_set_num_notify cannot exceed it " + "(default: %u)", + mca_osc_ucx_component.num_notify_counters); + (void) mca_base_component_var_register(&mca_osc_ucx_component.super.osc_version, + "num_notify_counters", description_str, + MCA_BASE_VAR_TYPE_UNSIGNED_INT, NULL, 0, 0, + OPAL_INFO_LVL_3, MCA_BASE_VAR_SCOPE_GROUP, + &mca_osc_ucx_component.num_notify_counters); + free(description_str); + opal_common_ucx_mca_var_register(&mca_osc_ucx_component.super.osc_version); if (0 == access ("/dev/shm", W_OK)) { @@ -559,7 +641,8 @@ static int component_select(struct ompi_win_t *win, void **base, size_t size, pt opal_common_ucx_mem_type_t mem_type; char *my_mem_addr; int my_mem_addr_size; - uint64_t my_info[3] = {0}; + uint64_t my_info[4] = {0}; + void *notify_base = NULL; char *recv_buf = NULL; void *dynamic_base = NULL; unsigned long adjusted_size = size; @@ -672,6 +755,48 @@ static int component_select(struct ompi_win_t *win, void **base, size_t size, pt module->flavor = flavor; module->size = size; + + /* How many notification counters to reserve per MPI process. Read before + * the counter region is allocated below. A malformed info value must not + * make this rank skip the allreduce that follows -- that would leave the + * rest of the group blocked in window creation -- so the failure is carried + * through the collective as a negative reservation instead. */ + unsigned int notify_reserved = 0; + bool notify_asserted = false; + int notify_values[3]; + /* The reservation is exchanged as an int and is used to size an allocation, + * so a value that does not fit is a bad configuration rather than a request + * to honor. Both failures ride the flag, not the value: MPI_MAX would hide + * a sentinel value behind some other rank's larger reservation. */ + bool notify_bad = (OMPI_SUCCESS != osc_ucx_reserved_notify_counters(info, ¬ify_reserved, + ¬ify_asserted)) + || notify_reserved > (unsigned int) INT_MAX; + notify_values[0] = notify_bad ? 1 : 0; + notify_values[1] = notify_bad ? 0 : (int) notify_reserved; + /* Carried as "some rank did NOT assert" so that it combines under MPI_MAX + * along with the other two values. */ + notify_values[2] = notify_asserted ? 0 : 1; + + /* info is allowed to differ between MPI processes, so agree on one + * reservation for the whole window. Taking the maximum keeps every rank's + * own assertion satisfiable, and propagates any rank's failure flag. */ + ret = module->comm->c_coll->coll_allreduce(MPI_IN_PLACE, notify_values, 3, + MPI_INT, MPI_MAX, module->comm, + module->comm->c_coll->coll_allreduce_module); + if (OMPI_SUCCESS != ret) { + goto error; + } + if (0 != notify_values[0]) { + ret = MPI_ERR_INFO; + goto error; + } + module->notify_capacity = (unsigned int) notify_values[1]; + /* Only treat the reservation as a hard cap when *every* rank asserted a + * bound. A rank that gave no key made no promise, so the window has to + * stay growable for it -- MPI-5.1 12.2 says an absent (zero) key means the + * implementation assumes no limit. */ + module->notify_max_assert = notify_values[2] ? 0 : (unsigned int) notify_values[1]; + module->no_locks = check_config_value_bool ("no_locks", info); module->acc_single_intrinsic = check_config_value_bool ("acc_single_intrinsic", info); module->skip_sync_check = false; @@ -849,7 +974,6 @@ static int component_select(struct ompi_win_t *win, void **base, size_t size, pt goto error; } - for (i = 0, total = 0; i < comm_size ; ++i) { size_t peer_size = ompi_osc_ucx_get_size(module, i); if (peer_size || !module->noncontig_shared_win) { @@ -884,7 +1008,8 @@ static int component_select(struct ompi_win_t *win, void **base, size_t size, pt ret = OMPI_ERR_BAD_PARAM; goto error; } - ret = opal_common_ucx_wpmem_create(module->ctx, mem_base, module->size, + ret = opal_common_ucx_wpmem_create(module->ctx, mem_base, + module->size, mem_type, &exchange_len_info, OPAL_COMMON_UCX_WPMEM_ADDR_EXCHANGE_FULL, (void *)module->comm, @@ -899,6 +1024,37 @@ static int component_select(struct ompi_win_t *win, void **base, size_t size, pt ucp_rkey_buffer_release(my_mem_addr); } + /* Notification counters live in their own registered region, like the + * window state does. Appending them to the window data would mean writing + * past the end of the user's buffer for MPI_WIN_FLAVOR_CREATE, and would + * leave dynamic windows -- which have no data region -- with nowhere to put + * them. */ + if (0 != module->notify_capacity) { + module->notify_base = calloc(module->notify_capacity, sizeof(uint64_t)); + if (NULL == module->notify_base) { + ret = OMPI_ERR_TEMP_OUT_OF_RESOURCE; + goto error; + } + + notify_base = module->notify_base; + ret = opal_common_ucx_wpmem_create(module->ctx, ¬ify_base, + module->notify_capacity * sizeof(uint64_t), + OPAL_COMMON_UCX_MEM_MAP, + &exchange_len_info, + OPAL_COMMON_UCX_WPMEM_ADDR_EXCHANGE_FULL, + (void *)module->comm, + &my_mem_addr, &my_mem_addr_size, + &module->notify_mem); + if (ret != OMPI_SUCCESS) { + goto error; + } + + if (my_mem_addr_size != 0) { + /* rkey object is already distributed among comm processes */ + ucp_rkey_buffer_release(my_mem_addr); + } + } + state_base = (void *)&(module->state); ret = opal_common_ucx_wpmem_create(module->ctx, &state_base, sizeof(ompi_osc_ucx_state_t), @@ -926,6 +1082,7 @@ static int component_select(struct ompi_win_t *win, void **base, size_t size, pt } my_info[1] = (uint64_t)state_base; my_info[2] = ompi_comm_rank(&ompi_mpi_comm_world.comm); + my_info[3] = (uint64_t)module->notify_base; recv_buf = (char *)calloc(comm_size, sizeof(my_info)); ret = comm->c_coll->coll_allgather((void *)my_info, sizeof(my_info), @@ -941,10 +1098,17 @@ static int component_select(struct ompi_win_t *win, void **base, size_t size, pt module->addrs = calloc(comm_size, sizeof(uint64_t)); module->state_addrs = calloc(comm_size, sizeof(uint64_t)); module->comm_world_ranks = calloc(comm_size, sizeof(uint64_t)); + /* Number of notification counters attached at each rank; starts at zero + * everywhere (consistent without communication) and is updated by + * MPI_WIN_SET_NUM_NOTIFY. Counters must be attached before use. */ + module->notify_counts = calloc(comm_size, sizeof(int)); + module->notify_addrs = calloc(comm_size, sizeof(uint64_t)); for (i = 0; i < comm_size; i++) { - memcpy(&(module->addrs[i]), recv_buf + i * 3 * sizeof(uint64_t), sizeof(uint64_t)); - memcpy(&(module->state_addrs[i]), recv_buf + i * 3 * sizeof(uint64_t) + sizeof(uint64_t), sizeof(uint64_t)); - memcpy(&(module->comm_world_ranks[i]), recv_buf + i * 3 * sizeof(uint64_t) + 2 * sizeof(uint64_t), sizeof(uint64_t)); + const char *entry = recv_buf + i * sizeof(my_info); + memcpy(&(module->addrs[i]), entry, sizeof(uint64_t)); + memcpy(&(module->state_addrs[i]), entry + sizeof(uint64_t), sizeof(uint64_t)); + memcpy(&(module->comm_world_ranks[i]), entry + 2 * sizeof(uint64_t), sizeof(uint64_t)); + memcpy(&(module->notify_addrs[i]), entry + 3 * sizeof(uint64_t), sizeof(uint64_t)); } free(recv_buf); @@ -957,6 +1121,7 @@ static int component_select(struct ompi_win_t *win, void **base, size_t size, pt module->state.acc_lock = TARGET_LOCK_UNLOCKED; module->state.dynamic_lock = TARGET_LOCK_UNLOCKED; module->state.dynamic_win_count = 0; + for (i = 0; i < OMPI_OSC_UCX_ATTACH_MAX; i++) { module->local_dynamic_win_info[i].refcnt = 0; } @@ -1091,6 +1256,92 @@ int ompi_osc_ucx_dynamic_unlock(ompi_osc_ucx_module_t *module, int target) { return OMPI_SUCCESS; } +/* Collectively replace the notification-counter registration with a larger one. + * + * Called from MPI_WIN_SET_NUM_NOTIFY, which the standard defines as a blocking, + * synchronizing collective procedure -- that is what makes this safe. Every + * rank has to take part even if its own request fits, because registering the + * memory exchanges rkeys with the whole group. + * + * Two properties of MPI_WIN_SET_NUM_NOTIFY keep this simple. It resets every + * counter to zero, so a freshly calloc'd region is already the required + * contents and no value has to be carried across. And it is erroneous to call + * it while an access epoch is open or with an active notification-threshold + * request, so no remote atomic can be in flight against the old region while it + * is being replaced. + * + * The allgather of the new base addresses doubles as the barrier that lets the + * old region be released: once it completes, every rank has published its new + * address and no rank can issue a notified operation until it returns from the + * enclosing collective. */ +int ompi_osc_ucx_grow_notify_counters(ompi_osc_ucx_module_t *module, + unsigned int new_capacity) +{ + int comm_size = ompi_comm_size(module->comm); + opal_common_ucx_wpmem_t *new_mem = NULL; + void *new_base = NULL, *reg_base; + char *my_mem_addr = NULL; + uint64_t my_addr, *new_addrs = NULL; + int my_mem_addr_size = 0; + int ret; + + new_base = calloc(new_capacity, sizeof(uint64_t)); + if (NULL == new_base) { + return MPI_ERR_NO_MEM; + } + + new_addrs = calloc(comm_size, sizeof(uint64_t)); + if (NULL == new_addrs) { + free(new_base); + return MPI_ERR_NO_MEM; + } + + reg_base = new_base; + ret = opal_common_ucx_wpmem_create(module->ctx, ®_base, + new_capacity * sizeof(uint64_t), + OPAL_COMMON_UCX_MEM_MAP, + &exchange_len_info, + OPAL_COMMON_UCX_WPMEM_ADDR_EXCHANGE_FULL, + (void *)module->comm, + &my_mem_addr, &my_mem_addr_size, + &new_mem); + if (OMPI_SUCCESS != ret) { + free(new_addrs); + free(new_base); + return ret; + } + + if (0 != my_mem_addr_size) { + /* rkey object is already distributed among comm processes */ + ucp_rkey_buffer_release(my_mem_addr); + } + + my_addr = (uint64_t) new_base; + ret = module->comm->c_coll->coll_allgather(&my_addr, sizeof(uint64_t), MPI_BYTE, + new_addrs, sizeof(uint64_t), MPI_BYTE, + module->comm, + module->comm->c_coll->coll_allgather_module); + if (OMPI_SUCCESS != ret) { + opal_common_ucx_wpmem_free(new_mem); + free(new_addrs); + free(new_base); + return ret; + } + + if (NULL != module->notify_mem) { + opal_common_ucx_wpmem_free(module->notify_mem); + } + free(module->notify_base); + free(module->notify_addrs); + + module->notify_mem = new_mem; + module->notify_base = new_base; + module->notify_addrs = new_addrs; + module->notify_capacity = new_capacity; + + return OMPI_SUCCESS; +} + int ompi_osc_ucx_win_attach(struct ompi_win_t *win, void *base, size_t len) { ompi_osc_ucx_module_t *module = (ompi_osc_ucx_module_t*) win->w_osc_module; int insert_index = -1, contain_index; @@ -1245,11 +1496,17 @@ int ompi_osc_ucx_free(struct ompi_win_t *win) { free(module->addrs); free(module->state_addrs); free(module->comm_world_ranks); + free(module->notify_counts); + free(module->notify_addrs); opal_common_ucx_wpmem_free(module->state_mem); if (NULL != module->mem) { opal_common_ucx_wpmem_free(module->mem); } + if (NULL != module->notify_mem) { + opal_common_ucx_wpmem_free(module->notify_mem); + } + free(module->notify_base); opal_common_ucx_wpctx_release(module->ctx); diff --git a/ompi/mca/part/persist/part_persist.h b/ompi/mca/part/persist/part_persist.h index 86fb9bac42d..57faeed82ec 100644 --- a/ompi/mca/part/persist/part_persist.h +++ b/ompi/mca/part/persist/part_persist.h @@ -242,7 +242,7 @@ mca_part_persist_progress(void) err = opal_datatype_type_size(&(req->req_datatype->super), &dt_size_); if(OMPI_SUCCESS != err) return OMPI_ERROR; - dt_size = (dt_size_ > (size_t) UINT_MAX) ? MPI_UNDEFINED : (uint32_t) dt_size_; + dt_size = (dt_size_ > (size_t) UINT_MAX) ? (uint32_t)MPI_UNDEFINED : (uint32_t) dt_size_; uint32_t bytes = req->real_count * dt_size; /* Set up persistent sends */ @@ -263,7 +263,7 @@ mca_part_persist_progress(void) err = opal_datatype_type_size(&(req->req_datatype->super), &dt_size_); if(OMPI_SUCCESS != err) return OMPI_ERROR; - dt_size = (dt_size_ > (size_t) UINT_MAX) ? MPI_UNDEFINED : (uint32_t) dt_size_; + dt_size = (dt_size_ > (size_t) UINT_MAX) ? (uint32_t)MPI_UNDEFINED : (uint32_t) dt_size_; uint32_t bytes = req->real_count * dt_size; @@ -382,7 +382,7 @@ mca_part_persist_precv_init(void *buf, /* Compute total number of bytes */ err = opal_datatype_type_size(&(req->req_datatype->super), &dt_size_); if(OMPI_SUCCESS != err) return OMPI_ERROR; - dt_size = (dt_size_ > (size_t) UINT_MAX) ? MPI_UNDEFINED : (uint32_t) dt_size_; + dt_size = (dt_size_ > (size_t) UINT_MAX) ? (uint32_t)MPI_UNDEFINED : (uint32_t) dt_size_; req->req_bytes = parts * count * dt_size; /* Set ompi request initial values */ @@ -442,7 +442,7 @@ mca_part_persist_psend_init(const void* buf, /* Determine total bytes to send. */ err = opal_datatype_type_size(&(req->req_datatype->super), &dt_size_); if(OMPI_SUCCESS != err) return OMPI_ERROR; - dt_size = (dt_size_ > (size_t) UINT_MAX) ? MPI_UNDEFINED : (uint32_t) dt_size_; + dt_size = (dt_size_ > (size_t) UINT_MAX) ? (uint32_t)MPI_UNDEFINED : (uint32_t) dt_size_; req->req_bytes = parts * count * dt_size; /* non-blocking send set-up data */ @@ -525,7 +525,6 @@ mca_part_persist_start(size_t count, ompi_request_t** requests) req->req_ompi.req_status.MPI_ERROR = OMPI_SUCCESS; req->req_ompi.req_status._cancelled = 0; req->req_part_complete = false; - req->req_ompi.req_complete = false; OPAL_ATOMIC_SWAP_PTR(&req->req_ompi.req_complete, REQUEST_PENDING); } @@ -568,6 +567,13 @@ mca_part_persist_parrived(size_t min_part, int _flag = false; mca_part_persist_request_t *req = (mca_part_persist_request_t *)request; + // An inactive (never-started) precv request has no flags array yet. + // MPI-5.0 sec 4.2.2 (p.119): an inactive request is trivially arrived. + if(NULL == req->flags && OMPI_REQUEST_INACTIVE == request->req_state) { + *flag = 1; + return err; + } + if(0 != req->flags) { _flag = 1; if(req->req_parts == req->real_parts) { diff --git a/ompi/mca/pml/cm/pml_cm_recvreq.h b/ompi/mca/pml/cm/pml_cm_recvreq.h index 1c1cca4616d..250305e6864 100644 --- a/ompi/mca/pml/cm/pml_cm_recvreq.h +++ b/ompi/mca/pml/cm/pml_cm_recvreq.h @@ -234,7 +234,7 @@ do { \ do { \ /* init/re-init the request */ \ request->req_base.req_pml_complete = false; \ - request->req_base.req_ompi.req_complete = false; \ + request->req_base.req_ompi.req_complete = REQUEST_PENDING; \ request->req_base.req_ompi.req_state = OMPI_REQUEST_ACTIVE; \ \ /* always set the req_status.MPI_TAG to ANY_TAG before starting the \ @@ -256,7 +256,7 @@ do { \ do { \ /* init/re-init the request */ \ request->req_base.req_pml_complete = false; \ - request->req_base.req_ompi.req_complete = false; \ + request->req_base.req_ompi.req_complete = REQUEST_PENDING; \ request->req_base.req_ompi.req_state = OMPI_REQUEST_ACTIVE; \ \ /* always set the req_status.MPI_TAG to ANY_TAG before starting the \ @@ -278,7 +278,7 @@ do { \ /* opal_output(0, "posting hvy request %d\n", request); */ \ /* init/re-init the request */ \ request->req_base.req_pml_complete = false; \ - request->req_base.req_ompi.req_complete = false; \ + request->req_base.req_ompi.req_complete = REQUEST_PENDING; \ request->req_base.req_ompi.req_state = OMPI_REQUEST_ACTIVE; \ \ /* always set the req_status.MPI_TAG to ANY_TAG before starting the \ diff --git a/ompi/mca/pml/ob1/pml_ob1.c b/ompi/mca/pml/ob1/pml_ob1.c index e0516d16fe0..a08f5e7398c 100644 --- a/ompi/mca/pml/ob1/pml_ob1.c +++ b/ompi/mca/pml/ob1/pml_ob1.c @@ -27,6 +27,7 @@ * reserved. * Copyright (c) 2022 IBM Corporation. All rights reserved * Copyright (c) 2023 Jeffrey M. Squyres. All rights reserved. + * Copyright (c) 2026 Stony Brook University. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -721,6 +722,11 @@ int mca_pml_ob1_dump(struct ompi_communicator_t* comm, int verbose) /* dump all btls used for eager messages */ for( n = 0; n < ep->btl_eager.arr_size; n++ ) { mca_bml_base_btl_t* bml_btl = &ep->btl_eager.bml_btls[n]; + if (bml_btl->btl->btl_dump == NULL) { + opal_output(0, "BTL %s does not provide dump callback\n", + bml_btl->btl->btl_component->btl_version.mca_component_name); + continue; + } bml_btl->btl->btl_dump(bml_btl->btl, bml_btl->btl_endpoint, verbose); } } diff --git a/ompi/mca/pml/ob1/pml_ob1.h b/ompi/mca/pml/ob1/pml_ob1.h index a0091793ab4..29a57d02f51 100644 --- a/ompi/mca/pml/ob1/pml_ob1.h +++ b/ompi/mca/pml/ob1/pml_ob1.h @@ -255,6 +255,12 @@ do { \ (opal_free_list_item_t*)pckt); \ } while(0) +/** + * A thread-safe function that should be called every time we need the OB1 + * progress to be turned (or kept) on. + */ +int mca_pml_ob1_enable_progress(int32_t count); + static inline void mca_pml_ob1_add_to_pending (ompi_proc_t *proc, mca_bml_base_btl_t *bml_btl, int order, mca_pml_ob1_hdr_t *hdr, size_t hdr_size) { @@ -270,6 +276,10 @@ static inline void mca_pml_ob1_add_to_pending (ompi_proc_t *proc, mca_bml_base_b OPAL_THREAD_SCOPED_LOCK(&mca_pml_ob1.lock, { opal_list_append(&mca_pml_ob1.pckt_pending, &pckt->super.super); }); + /* Drive mca_pml_ob1_progress() so the queue is retried from opal_progress() + * even if no further BTL completion fires to call + * MCA_PML_OB1_PROGRESS_PENDING. */ + mca_pml_ob1_enable_progress(1); } #define OB1_MATCHING_LOCK(lock) \ @@ -411,12 +421,6 @@ mca_pml_ob1_calc_weighted_length( mca_pml_ob1_com_btl_t *btls, int num_btls, siz btls[0].length += length_left; } -/** - * A thread-safe function that should be called every time we need the OB1 - * progress to be turned (or kept) on. - */ -int mca_pml_ob1_enable_progress(int32_t count); - int mca_pml_ob1_send_control_any (ompi_proc_t *proc, int order, mca_pml_ob1_hdr_t *hdr, size_t hdr_size, bool add_to_pending); int mca_pml_ob1_send_control_btl (mca_bml_base_btl_t *bml_btl, int order, mca_pml_ob1_hdr_t *hdr, size_t hdr_size, diff --git a/ompi/mca/pml/ob1/pml_ob1_irecv.c b/ompi/mca/pml/ob1/pml_ob1_irecv.c index 4ccb8ea00f2..7c2e89c2fb8 100644 --- a/ompi/mca/pml/ob1/pml_ob1_irecv.c +++ b/ompi/mca/pml/ob1/pml_ob1_irecv.c @@ -233,6 +233,9 @@ mca_pml_ob1_imrecv( void *buf, recvreq->req_rdma_idx = 0; recvreq->req_pending = false; recvreq->req_ack_sent = false; + recvreq->req_match_received = false; + /* release: pair with rmb in recv_request_pml_complete_check */ + opal_atomic_wmb(); MCA_PML_BASE_RECV_START(&recvreq->req_recv); @@ -325,6 +328,10 @@ mca_pml_ob1_mrecv( void *buf, recvreq->req_rdma_cnt = 0; recvreq->req_rdma_idx = 0; recvreq->req_pending = false; + recvreq->req_ack_sent = false; + recvreq->req_match_received = false; + /* release: pair with rmb in recv_request_pml_complete_check */ + opal_atomic_wmb(); MCA_PML_BASE_RECV_START(&recvreq->req_recv); diff --git a/ompi/mca/pml/ob1/pml_ob1_progress.c b/ompi/mca/pml/ob1/pml_ob1_progress.c index 930d5b7311e..935d5bf1357 100644 --- a/ompi/mca/pml/ob1/pml_ob1_progress.c +++ b/ompi/mca/pml/ob1/pml_ob1_progress.c @@ -71,6 +71,21 @@ int mca_pml_ob1_progress(void) completed_requests += mca_pml_ob1_process_pending_accelerator_async_copies(); + /* Drain the FIN/ACK control-packet retry queue. It is otherwise drained + * only as a side effect of BTL completion callbacks (see + * MCA_PML_OB1_PROGRESS_PENDING). If the BTL goes idle while packets are + * still queued -- e.g. the tail of an incast where btl_sendi() repeatedly + * returned OPAL_ERR_OUT_OF_RESOURCE -- no further completion fires, the + * queue is never revisited, and every peer waiting on those FINs hangs + * forever. Retrying it here, driven by mca_pml_ob1_progress_needed (which + * mca_pml_ob1_add_to_pending() bumps via mca_pml_ob1_enable_progress()), + * guarantees the queue makes progress even with no BTL traffic in flight. */ + if( opal_list_get_size(&mca_pml_ob1.pckt_pending) ) { + int pckt_before = (int) opal_list_get_size(&mca_pml_ob1.pckt_pending); + mca_pml_ob1_process_pending_packets(NULL); + completed_requests += pckt_before - (int) opal_list_get_size(&mca_pml_ob1.pckt_pending); + } + for( i = 0; i < queue_length; i++ ) { mca_pml_ob1_send_pending_t pending_type = MCA_PML_OB1_SEND_PENDING_NONE; mca_pml_ob1_send_request_t* sendreq; diff --git a/ompi/mca/pml/ob1/pml_ob1_recvfrag.c b/ompi/mca/pml/ob1/pml_ob1_recvfrag.c index 8e1d4d2e0cf..3445c45188a 100644 --- a/ompi/mca/pml/ob1/pml_ob1_recvfrag.c +++ b/ompi/mca/pml/ob1/pml_ob1_recvfrag.c @@ -768,6 +768,10 @@ void mca_pml_ob1_recv_frag_callback_ack (mca_btl_base_module_t *btl, sendreq->req_send.req_base.req_convertor.stream = stream; } + /* ensure all prior stores (copy_in_out, rdma_frag, throttle_sends, + * req_state, accelerator flags) are visible before complete_check + * may recycle the request */ + opal_atomic_wmb(); if (send_request_pml_complete_check(sendreq) == false) mca_pml_ob1_send_request_schedule(sendreq); } diff --git a/ompi/mca/pml/ob1/pml_ob1_recvreq.c b/ompi/mca/pml/ob1/pml_ob1_recvreq.c index a6a2866f2a2..74c8113ff7a 100644 --- a/ompi/mca/pml/ob1/pml_ob1_recvreq.c +++ b/ompi/mca/pml/ob1/pml_ob1_recvreq.c @@ -228,6 +228,9 @@ static void mca_pml_ob1_put_completion (mca_pml_ob1_rdma_frag_t *frag, int64_t r if (OPAL_LIKELY(0 < rdma_size)) { + /* ensure pipeline_depth and frag cleanup are visible before + * bytes_received update that complete_check observes */ + opal_atomic_wmb(); /* check completion status */ OPAL_THREAD_ADD_FETCH_SIZE_T(&recvreq->req_bytes_received, rdma_size); SPC_USER_OR_MPI(recvreq->req_recv.req_base.req_ompi.req_status.MPI_TAG, (ompi_spc_value_t)rdma_size, @@ -443,6 +446,9 @@ static void mca_pml_ob1_rget_completion (mca_btl_base_module_t* btl, struct mca_ MCA_PML_OB1_RDMA_FRAG_RETURN(frag); } + /* ensure all prior stores (bytes_received, error status, frag cleanup) + * are visible before complete_check may recycle the request */ + opal_atomic_wmb(); recv_request_pml_complete_check(recvreq); MCA_PML_OB1_PROGRESS_PENDING(bml_btl); @@ -596,6 +602,9 @@ void mca_pml_ob1_recv_request_progress_frag( mca_pml_ob1_recv_request_t* recvreq recvreq->req_recv.req_base.req_datatype); ); + /* ensure unpack stores are visible before bytes_received update + * that complete_check observes */ + opal_atomic_wmb(); OPAL_THREAD_ADD_FETCH_SIZE_T(&recvreq->req_bytes_received, bytes_received); SPC_USER_OR_MPI(recvreq->req_recv.req_base.req_ompi.req_status.MPI_TAG, (ompi_spc_value_t)bytes_received, OMPI_SPC_BYTES_RECEIVED_USER, OMPI_SPC_BYTES_RECEIVED_MPI); @@ -674,6 +683,9 @@ void mca_pml_ob1_recv_request_frag_copy_finished( mca_btl_base_module_t* btl, * known that the data has been copied out of the descriptor. */ des->des_cbfunc(NULL, NULL, des, 0); + /* ensure copy and descriptor cleanup are visible before + * bytes_received update that complete_check observes */ + opal_atomic_wmb(); OPAL_THREAD_ADD_FETCH_SIZE_T(&recvreq->req_bytes_received, bytes_received); SPC_USER_OR_MPI(recvreq->req_recv.req_base.req_ompi.req_status.MPI_TAG, (ompi_spc_value_t)bytes_received, OMPI_SPC_BYTES_RECEIVED_USER, OMPI_SPC_BYTES_RECEIVED_MPI); @@ -887,6 +899,9 @@ void mca_pml_ob1_recv_request_progress_rndv( mca_pml_ob1_recv_request_t* recvreq SPC_USER_OR_MPI(recvreq->req_recv.req_base.req_ompi.req_status.MPI_TAG, (ompi_spc_value_t)bytes_received, OMPI_SPC_BYTES_RECEIVED_USER, OMPI_SPC_BYTES_RECEIVED_MPI); } + /* ensure all prior stores (unpack, match, bytes_received) are visible + * before complete_check may recycle the request */ + opal_atomic_wmb(); /* check completion status */ if(recv_request_pml_complete_check(recvreq) == false && recvreq->req_rdma_offset < recvreq->req_send_offset) { diff --git a/ompi/mca/pml/ob1/pml_ob1_recvreq.h b/ompi/mca/pml/ob1/pml_ob1_recvreq.h index a266e3388bb..558975c2597 100644 --- a/ompi/mca/pml/ob1/pml_ob1_recvreq.h +++ b/ompi/mca/pml/ob1/pml_ob1_recvreq.h @@ -65,11 +65,14 @@ OBJ_CLASS_DECLARATION(mca_pml_ob1_recv_request_t); static inline bool lock_recv_request(mca_pml_ob1_recv_request_t *recvreq) { - return OPAL_THREAD_ADD_FETCH32(&recvreq->req_lock, 1) == 1; + bool ret = OPAL_THREAD_ADD_FETCH32(&recvreq->req_lock, 1) == 1; + opal_atomic_rmb(); + return ret; } static inline bool unlock_recv_request(mca_pml_ob1_recv_request_t *recvreq) { + opal_atomic_wmb(); return OPAL_THREAD_ADD_FETCH32(&recvreq->req_lock, -1) == 0; } @@ -247,9 +250,10 @@ static inline void recv_req_matched(mca_pml_ob1_recv_request_t *req, { req->req_recv.req_base.req_ompi.req_status.MPI_SOURCE = hdr->hdr_src; req->req_recv.req_base.req_ompi.req_status.MPI_TAG = hdr->hdr_tag; - req->req_match_received = true; - + /* ensure MPI_SOURCE, MPI_TAG, and req_bytes_packed (set by caller) + * are visible before req_match_received signals complete_check */ opal_atomic_wmb(); + req->req_match_received = true; if(req->req_recv.req_bytes_packed > 0) { #if OPAL_ENABLE_HETEROGENEOUS_SUPPORT diff --git a/ompi/mca/pml/ob1/pml_ob1_sendreq.c b/ompi/mca/pml/ob1/pml_ob1_sendreq.c index 0dd246917c0..f40dff6ce89 100644 --- a/ompi/mca/pml/ob1/pml_ob1_sendreq.c +++ b/ompi/mca/pml/ob1/pml_ob1_sendreq.c @@ -261,6 +261,10 @@ mca_pml_ob1_rndv_completion_request( mca_bml_base_btl_t* bml_btl, SPC_USER_OR_MPI(sendreq->req_send.req_base.req_ompi.req_status.MPI_TAG, (ompi_spc_value_t)req_bytes_delivered, OMPI_SPC_BYTES_SENT_USER, OMPI_SPC_BYTES_SENT_MPI); + /* ensure bytes_delivered is visible before req_state update, so that + * another thread's complete_check sees consistent state */ + opal_atomic_wmb(); + /* advance the request */ OPAL_THREAD_ADD_FETCH32(&sendreq->req_state, -1); @@ -360,6 +364,9 @@ mca_pml_ob1_rget_completion (mca_pml_ob1_rdma_frag_t *frag, int64_t rdma_length) MCA_PML_OB1_RDMA_FRAG_RETURN(frag); } + /* ensure all prior stores (bytes_delivered, rdma_frag, error status) + * are visible before complete_check may recycle the request */ + opal_atomic_wmb(); send_request_pml_complete_check(sendreq); if( OPAL_LIKELY(0 < rdma_length) ) { @@ -440,6 +447,9 @@ mca_pml_ob1_frag_completion( mca_btl_base_module_t* btl, sizeof(mca_pml_ob1_frag_hdr_t)); } + /* ensure prior non-atomic stores (e.g. error status) are visible + * before atomic updates that complete_check will observe */ + opal_atomic_wmb(); OPAL_THREAD_ADD_FETCH32(&sendreq->req_pipeline_depth, -1); OPAL_THREAD_ADD_FETCH_SIZE_T(&sendreq->req_bytes_delivered, req_bytes_delivered); SPC_USER_OR_MPI(sendreq->req_send.req_base.req_ompi.req_status.MPI_TAG, (ompi_spc_value_t)req_bytes_delivered, @@ -1318,11 +1328,13 @@ static void mca_pml_ob1_put_completion (mca_btl_base_module_t* btl, struct mca_b /* check completion status */ if( OPAL_UNLIKELY(OMPI_SUCCESS == status) ) { - /* TODO -- read ordering */ mca_pml_ob1_send_fin (sendreq->req_send.req_base.req_proc, bml_btl, frag->rdma_hdr.hdr_rdma.hdr_frag, frag->rdma_length, 0, 0); + /* ensure send_fin stores are visible before bytes_delivered + * update that complete_check observes */ + opal_atomic_wmb(); /* check for request completion */ OPAL_THREAD_ADD_FETCH_SIZE_T(&sendreq->req_bytes_delivered, frag->rdma_length); SPC_USER_OR_MPI(sendreq->req_send.req_base.req_ompi.req_status.MPI_TAG, (ompi_spc_value_t)frag->rdma_length, @@ -1411,6 +1423,7 @@ void mca_pml_ob1_send_request_put (mca_pml_ob1_send_request_t *sendreq, mca_pml_ob1_rdma_frag_t* frag; if(hdr->hdr_common.hdr_flags & MCA_PML_OB1_HDR_TYPE_ACK) { + opal_atomic_wmb(); /* ensure prior stores visible before req_state update */ OPAL_THREAD_ADD_FETCH32(&sendreq->req_state, -1); } @@ -1434,6 +1447,10 @@ void mca_pml_ob1_send_request_put (mca_pml_ob1_send_request_t *sendreq, /* rget fallback on put */ frag = sendreq->rdma_frag; sendreq->rdma_frag = NULL; + /* ensure rdma_frag = NULL is visible before req_state signals + * completion — plain store of 0 is the completion trigger that + * complete_check observes */ + opal_atomic_wmb(); sendreq->req_state = 0; } diff --git a/ompi/mca/pml/ob1/pml_ob1_sendreq.h b/ompi/mca/pml/ob1/pml_ob1_sendreq.h index e9946e90528..6d2a8b09d13 100644 --- a/ompi/mca/pml/ob1/pml_ob1_sendreq.h +++ b/ompi/mca/pml/ob1/pml_ob1_sendreq.h @@ -79,11 +79,14 @@ OBJ_CLASS_DECLARATION(mca_pml_ob1_send_range_t); static inline bool lock_send_request(mca_pml_ob1_send_request_t *sendreq) { - return OPAL_THREAD_ADD_FETCH32(&sendreq->req_lock, 1) == 1; + bool ret = OPAL_THREAD_ADD_FETCH32(&sendreq->req_lock, 1) == 1; + opal_atomic_rmb(); + return ret; } static inline bool unlock_send_request(mca_pml_ob1_send_request_t *sendreq) { + opal_atomic_wmb(); return OPAL_THREAD_ADD_FETCH32(&sendreq->req_lock, -1) == 0; } @@ -468,6 +471,17 @@ mca_pml_ob1_send_request_start_seq (mca_pml_ob1_send_request_t* sendreq, mca_bml sendreq->req_pending = MCA_PML_OB1_SEND_PENDING_NONE; sendreq->req_send.req_base.req_sequence = seqn; + /* drain any stale send ranges left from a previous lifecycle; + * not protected by a lock as the sendreq is owned exclusively + * by the current thread at this point in the lifecycle. */ + if (OPAL_UNLIKELY(!opal_list_is_empty(&sendreq->req_send_ranges))) { + opal_list_item_t *item; + OPAL_OUTPUT_VERBOSE((1, mca_pml_ob1_output, "stale send ranges on reused sendreq")); + while (NULL != (item = opal_list_remove_first(&sendreq->req_send_ranges))) { + opal_free_list_return(&mca_pml_ob1.send_ranges, (opal_free_list_item_t *)item); + } + } + MCA_PML_BASE_SEND_START( &sendreq->req_send ); for(size_t i = 0; i < mca_bml_base_btl_array_get_size(&endpoint->btl_eager); i++) { diff --git a/ompi/mca/pml/ubcl/configure.m4 b/ompi/mca/pml/ubcl/configure.m4 index 262de492c19..ac3b17efb6d 100644 --- a/ompi/mca/pml/ubcl/configure.m4 +++ b/ompi/mca/pml/ubcl/configure.m4 @@ -1,5 +1,5 @@ # -# Copyright (c) 2024 Bull SAS. All rights reserved. +# Copyright (c) 2024-2026 Bull SAS. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -19,6 +19,13 @@ AC_DEFUN([MCA_ompi_pml_ubcl_CONFIG], [ [pml_ubcl_happy="yes"], [pml_ubcl_happy="no"]) + # See ompi/mca/common/ubcl/configure.m4 comment explaining why UBCL linking + # flags are removed only in dynamic mode + AS_IF([test "$compile_mode" = "dso"], + [pml_ubcl_LDFLAGS=""], + # Static mode should work, but Bull provides support only for dynamic components + [AC_MSG_WARN([Only DSO mode of pml/ubcl is tested (see --enable-mca-dso)])]) + AC_REQUIRE([MCA_ompi_common_ubcl_CONFIG]) AC_REQUIRE([MCA_opal_common_ubcl_CONFIG]) diff --git a/ompi/mca/pml/ubcl/pml_ubcl_iprobe.c b/ompi/mca/pml/ubcl/pml_ubcl_iprobe.c index 6b6dbad0cee..b8e87fe6813 100644 --- a/ompi/mca/pml/ubcl/pml_ubcl_iprobe.c +++ b/ompi/mca/pml/ubcl/pml_ubcl_iprobe.c @@ -1,6 +1,6 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* - * Copyright (c) 2019-2025 Bull SAS. All rights reserved. + * Copyright (c) 2019-2026 Bull SAS. All rights reserved. * * $COPYRIGHT$ * @@ -32,6 +32,7 @@ int mca_pml_ubcl_iprobe(int src, int tag, struct ompi_communicator_t *comm, OPAL_OUTPUT_VERBOSE((75, mca_pml_ubcl_component.output, "UBCL_MODULE_IPROBE\n")); ubcl_status_t ubcl_status; + ubcl_error_t err; uint64_t cid; uint64_t rank; @@ -45,10 +46,13 @@ int mca_pml_ubcl_iprobe(int src, int tag, struct ompi_communicator_t *comm, } cid = ompi_comm_get_local_cid(comm); - ubcl_cid_t ubcl_cid= mca_pml_ubcl_compute_ubcl_cid(tag, cid); + ubcl_cid_t ubcl_cid = mca_pml_ubcl_compute_ubcl_cid(tag, cid); /* Call the UBCL api for iprobe */ - ubcl_iprobe(rank, tag, ubcl_cid, matched, &ubcl_status); + err = ubcl_iprobe(rank, tag, ubcl_cid, matched, &ubcl_status); + if (UBCL_SUCCESS != err) { + return ubcl_error_to_ompi(err); + } if (*matched) { mca_common_ubcl_status_to_ompi(status, ubcl_status, comm, src); } @@ -78,6 +82,7 @@ int mca_pml_ubcl_improbe(int src, int tag, struct ompi_communicator_t *comm, OPAL_OUTPUT_VERBOSE((75, mca_pml_ubcl_component.output, "UBCL_MODULE_IMPROBE\n")); ubcl_status_t ubcl_status; + ubcl_error_t err; uint64_t rank; uint64_t cid; if (OMPI_ANY_SOURCE == src) { @@ -95,7 +100,10 @@ int mca_pml_ubcl_improbe(int src, int tag, struct ompi_communicator_t *comm, ubcl_message_t *ubcl_message; /* Call the UBCL api for improbe */ - ubcl_improbe(rank, tag, ubcl_cid, matched, &ubcl_message, &ubcl_status); + err = ubcl_improbe(rank, tag, ubcl_cid, matched, &ubcl_message, &ubcl_status); + if (UBCL_SUCCESS != err) { + return ubcl_error_to_ompi(err); + } if (*matched) { mca_common_ubcl_status_to_ompi(status, ubcl_status, comm, src); *message = ompi_message_alloc(); diff --git a/ompi/mca/pml/ubcl/pml_ubcl_irecv.c b/ompi/mca/pml/ubcl/pml_ubcl_irecv.c index 9ea74d9e428..0f46cec2f4d 100644 --- a/ompi/mca/pml/ubcl/pml_ubcl_irecv.c +++ b/ompi/mca/pml/ubcl/pml_ubcl_irecv.c @@ -1,6 +1,6 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* - * Copyright (c) 2019-2025 Bull SAS. All rights reserved. + * Copyright (c) 2019-2026 Bull SAS. All rights reserved. * * $COPYRIGHT$ * @@ -91,17 +91,16 @@ void mca_pml_ubcl_irecv_start(struct ompi_request_t **request) mca_pml_ubcl_request_t, ompi_req); void *output_buf = (void *) req->buf; - ubcl_memory_descriptor_t rbuf_md; ubcl_error_t err = 0; size_t size; /* Init UBCL MD */ - err = ubcl_memory_descriptor_init(&rbuf_md); + err = ubcl_memory_descriptor_init(&req->md); if (UBCL_SUCCESS != err) { mca_pml_ubcl_error(ubcl_error_to_ompi(err), "Failed to initialize ubcl MD"); } if (pml_ubcl_request_is_cuda_buf(req)) { - err = ubcl_memory_descriptor_set_properties(UBCL_BUF_IS_CUDA, &rbuf_md); + err = ubcl_memory_descriptor_set_properties(UBCL_BUF_IS_CUDA, &req->md); if (UBCL_SUCCESS != err) { mca_pml_ubcl_error(ubcl_error_to_ompi(err), "Failed to set MD properties, got error: %d", err); @@ -113,7 +112,7 @@ void mca_pml_ubcl_irecv_start(struct ompi_request_t **request) ompi_datatype_type_size(req->datatype, &size); size *= req->count; - err = ubcl_memory_descriptor_build_contiguous(output_buf, size, &rbuf_md); + err = ubcl_memory_descriptor_build_contiguous(output_buf, size, &req->md); if (UBCL_SUCCESS != err) { mca_pml_ubcl_error(ubcl_error_to_ompi(err), "Failed to build memory descriptor for output buffer"); @@ -121,12 +120,9 @@ void mca_pml_ubcl_irecv_start(struct ompi_request_t **request) } /* Always build a custom MD representation so that we have a fallback */ - err = ubcl_memory_descriptor_build_custom((void *) &req->convertor, - pml_ubcl_datatype_pack, - pml_ubcl_datatype_unpack, - pml_ubcl_datatype_mem_size, - pml_ubcl_datatype_finish, - &rbuf_md); + err = ubcl_memory_descriptor_build_custom((void *) &req->convertor, pml_ubcl_datatype_pack, + pml_ubcl_datatype_unpack, pml_ubcl_datatype_mem_size, + pml_ubcl_datatype_finish, &req->md); if (UBCL_SUCCESS != err) { mca_pml_ubcl_error(ubcl_error_to_ompi(err), "Failed to build custom memory descriptor for input buffer"); @@ -136,9 +132,8 @@ void mca_pml_ubcl_irecv_start(struct ompi_request_t **request) MCA_PML_UBCL_REQUEST_ACTIVATE(req); if (req->message != NULL) { - err = ubcl_imrecv(rbuf_md, (ubcl_message_t **) &req->message, - (ubcl_completion_callback_fct) &ubcl_request_recv_complete_cb, - *request); + err = ubcl_imrecv(req->md, (ubcl_message_t **) &req->message, + (ubcl_completion_callback_fct) ubcl_request_recv_complete_cb, *request); } else { uint64_t rank; uint64_t cid; @@ -158,9 +153,9 @@ void mca_pml_ubcl_irecv_start(struct ompi_request_t **request) OPAL_OUTPUT_VERBOSE( (50, mca_pml_ubcl_component.output, "PML/UBCL IRECV: recv from rank=%zu\n", rank)); - err = ubcl_irecv(rbuf_md, tag, ubcl_cid, rank, - (ubcl_completion_callback_fct) &ubcl_request_recv_complete_cb, - *request, &req->ubcl_operation_handle); + err = ubcl_irecv(req->md, tag, ubcl_cid, rank, + (ubcl_completion_callback_fct) &ubcl_request_recv_complete_cb, *request, + &req->ubcl_operation_handle); } if (UBCL_ERROR == err) { diff --git a/ompi/mca/pml/ubcl/pml_ubcl_isend.c b/ompi/mca/pml/ubcl/pml_ubcl_isend.c index 3bd19f0852c..8dcfbc8ffe4 100644 --- a/ompi/mca/pml/ubcl/pml_ubcl_isend.c +++ b/ompi/mca/pml/ubcl/pml_ubcl_isend.c @@ -1,6 +1,6 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* - * Copyright (c) 2019-2025 Bull SAS. All rights reserved. + * Copyright (c) 2019-2026 Bull SAS. All rights reserved. * * $COPYRIGHT$ * @@ -106,7 +106,6 @@ void mca_pml_ubcl_isend_start(struct ompi_request_t **request) char *input_buf = NULL; mca_common_ubcl_endpoint_t *endpoint = NULL; - ubcl_memory_descriptor_t sbuf_md; ubcl_error_t err = 0; ubcl_send_mode_t send_mode; uint64_t cid; @@ -127,12 +126,12 @@ void mca_pml_ubcl_isend_start(struct ompi_request_t **request) endpoint = (mca_common_ubcl_endpoint_t *) req->proc->proc_endpoints[OMPI_PROC_ENDPOINT_TAG_PML]; /* Init UBCL MD */ - err = ubcl_memory_descriptor_init(&sbuf_md); + err = ubcl_memory_descriptor_init(&req->md); if (UBCL_SUCCESS != err) { mca_pml_ubcl_error(ubcl_error_to_ompi(err), "Failed to initialize ubcl MD"); } if (pml_ubcl_request_is_cuda_buf(req)) { - err = ubcl_memory_descriptor_set_properties(UBCL_BUF_IS_CUDA, &sbuf_md); + err = ubcl_memory_descriptor_set_properties(UBCL_BUF_IS_CUDA, &req->md); if (UBCL_SUCCESS != err) { mca_pml_ubcl_error(ubcl_error_to_ompi(err), "Failed to set MD properties, got error: %d", err); @@ -143,7 +142,7 @@ void mca_pml_ubcl_isend_start(struct ompi_request_t **request) if (! MCA_PML_UBCL_REQUEST_NEED_XPACK(req)) { ptrdiff_t gap = 0; size_t span = opal_datatype_span(&req->datatype->super, req->count, &gap); - err = ubcl_memory_descriptor_build_contiguous(input_buf+gap, span, &sbuf_md); + err = ubcl_memory_descriptor_build_contiguous(input_buf + gap, span, &req->md); if (UBCL_SUCCESS != err) { mca_pml_ubcl_error(ubcl_error_to_ompi(err), "Failed to build contiguous memory descriptor for input buffer"); @@ -151,11 +150,9 @@ void mca_pml_ubcl_isend_start(struct ompi_request_t **request) } /* Always build a custom MD representation so that we have a fallback */ - err = ubcl_memory_descriptor_build_custom((void *) &req->convertor, - pml_ubcl_datatype_pack, - pml_ubcl_datatype_unpack, - pml_ubcl_datatype_mem_size, - pml_ubcl_datatype_finish, &sbuf_md); + err = ubcl_memory_descriptor_build_custom((void *) &req->convertor, pml_ubcl_datatype_pack, + pml_ubcl_datatype_unpack, pml_ubcl_datatype_mem_size, + pml_ubcl_datatype_finish, &req->md); if (UBCL_SUCCESS != err) { mca_pml_ubcl_error(ubcl_error_to_ompi(err), "Failed to build custom memory descriptor for input buffer"); @@ -171,9 +168,9 @@ void mca_pml_ubcl_isend_start(struct ompi_request_t **request) OPAL_OUTPUT_VERBOSE( (50, mca_pml_ubcl_component.output, "PML/UBCL ISEND: sending to rank=%zu\n", endpoint->rank)); - err = ubcl_isend(sbuf_md, tag, ubcl_cid, endpoint->rank, send_mode, - (ubcl_completion_callback_fct) &ubcl_request_send_complete_cb, - *request, &req->ubcl_operation_handle); + err = ubcl_isend(req->md, tag, ubcl_cid, endpoint->rank, send_mode, + (ubcl_completion_callback_fct) ubcl_request_send_complete_cb, *request, + &req->ubcl_operation_handle); if (UBCL_ERROR == err) { mca_pml_ubcl_error(ubcl_error_to_ompi(err), "Failed to send data"); } diff --git a/ompi/mca/pml/ubcl/pml_ubcl_request.c b/ompi/mca/pml/ubcl/pml_ubcl_request.c index b5a206f504c..85699e3f803 100644 --- a/ompi/mca/pml/ubcl/pml_ubcl_request.c +++ b/ompi/mca/pml/ubcl/pml_ubcl_request.c @@ -1,6 +1,6 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* - * Copyright (c) 2019-2025 Bull SAS. All rights reserved. + * Copyright (c) 2019-2026 Bull SAS. All rights reserved. * * $COPYRIGHT$ * @@ -216,7 +216,6 @@ int mca_pml_ubcl_request_complete_cb(struct ompi_request_t *request) return mca_pml_ubcl_request_complete(request); } -/* TODO: Get a pointer to status and not a cpy ? */ void ubcl_request_send_complete_cb(ubcl_status_t status, void *cb_data) { if (UBCL_SUCCESS != status.status) { @@ -232,7 +231,9 @@ void ubcl_request_send_complete_cb(ubcl_status_t status, void *cb_data) /* This lock cannot be removed, even in thread single mode */ opal_atomic_lock(&req->req_lock); req->completed = 1; + ubcl_memory_descriptor_destruct(&req->md); opal_atomic_unlock(&req->req_lock); + if (req->is_buffered) { mca_pml_base_bsend_request_free(req->comm, (void*)req->buf); /* Bsend started completed, but could not be freed, now that UBCL is @@ -280,6 +281,7 @@ void ubcl_request_recv_complete_cb(ubcl_status_t status, void *cb_data) /* This lock cannot be removed, even in thread single mode */ opal_atomic_lock(&req->req_lock); req->completed = 1; + ubcl_memory_descriptor_destruct(&req->md); opal_atomic_unlock(&req->req_lock); ompi_request_complete(&(req->ompi_req), true); diff --git a/ompi/mca/pml/ubcl/pml_ubcl_request.h b/ompi/mca/pml/ubcl/pml_ubcl_request.h index d47fa598af8..256f276e39c 100644 --- a/ompi/mca/pml/ubcl/pml_ubcl_request.h +++ b/ompi/mca/pml/ubcl/pml_ubcl_request.h @@ -1,6 +1,6 @@ /* -*- Mode: C; c-basic-offset:4 ; indent-tabs-mode:nil -*- */ /* - * Copyright (c) 2019-2025 Bull SAS. All rights reserved. + * Copyright (c) 2019-2026 Bull SAS. All rights reserved. * * $COPYRIGHT$ * @@ -85,6 +85,7 @@ struct mca_pml_ubcl_request_t { struct ompi_communicator_t *comm; /**< Communicator */ struct ompi_proc_t *proc; /**< Remote ompi proc */ opal_convertor_t convertor; /**< Data convertor */ + ubcl_memory_descriptor_t md; ompi_request_complete_fn_t saved_complete_cb; /**< Saved callback from another component (e.g OSC pt2pt) */ void *saved_complete_cb_data; /**< Saved callback data from another component (e.g OSC pt2pt) */ diff --git a/ompi/mca/sharedfp/individual/sharedfp_individual.c b/ompi/mca/sharedfp/individual/sharedfp_individual.c index d6e06dd450e..35810b485c0 100644 --- a/ompi/mca/sharedfp/individual/sharedfp_individual.c +++ b/ompi/mca/sharedfp/individual/sharedfp_individual.c @@ -15,6 +15,7 @@ * and Technology (RIST). All rights reserved. * Copyright (c) 2024 Triad National Security, LLC. All rights * reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -30,6 +31,7 @@ #include "ompi_config.h" #include "mpi.h" +#include "ompi/file/file.h" #include "ompi/mca/sharedfp/sharedfp.h" #include "ompi/mca/sharedfp/base/base.h" #include "ompi/mca/sharedfp/individual/sharedfp_individual.h" @@ -64,6 +66,38 @@ static mca_sharedfp_base_module_2_0_0_t individual = { * ******************************************************************* */ +static const char *mca_sharedfp_individual_relaxed_ordering_cb(opal_infosubscriber_t *object, + const char *key, + const char *value) +{ + ompi_file_t *file; + mca_common_ompio_data_t *data; + ompio_file_t *fh; + + if (NULL == object || NULL == key || NULL == value || '\0' == value[0]) { + return NULL; + } + + file = (ompi_file_t *) object; + data = (mca_common_ompio_data_t *) file->f_io_selected_data; + if (NULL == data) { + return NULL; + } + fh = &data->ompio_fh; + + /* + * sharedfp selection queries every candidate component. This callback + * must accept the hint while selection is still in progress because the + * hint raises individual's priority, but once a sharedfp module is known + * the hint is public only if this component actually won selection. + */ + if (NULL != fh->f_sharedfp && &individual != fh->f_sharedfp) { + return NULL; + } + + return value; +} + int mca_sharedfp_individual_component_init_query(bool enable_progress_threads, bool enable_mpi_threads) { @@ -79,6 +113,7 @@ struct mca_sharedfp_base_module_2_0_0_t * mca_sharedfp_individual_component_file bool relaxed_order_flag=false; opal_info_t *info; int flag; + int ret; opal_cstring_t *info_str; *priority = 0; @@ -104,8 +139,22 @@ struct mca_sharedfp_base_module_2_0_0_t * mca_sharedfp_individual_component_file /*---------------------------------------------------------*/ /* 2. Did the user specify MPI_INFO relaxed ordering flag? */ + if (wronly_flag) { + /* + * This hint is meaningful only when this component can be selected. + * Registering it conditionally avoids reporting a sharedfp hint as + * accepted on read-only opens where individual sharedfp will not run. + */ + ret = mca_common_ompio_info_subscribe(fh, "OMPIO_SHAREDFP_RELAXED_ORDERING", + NULL, + mca_sharedfp_individual_relaxed_ordering_cb); + if (OMPI_SUCCESS != ret) { + return NULL; + } + } + info = fh->f_info; - if ( info != &(MPI_INFO_NULL->super) ){ + if ( wronly_flag && info != &(MPI_INFO_NULL->super) ){ opal_info_get ( info,"OMPIO_SHAREDFP_RELAXED_ORDERING", &info_str, &flag); if ( flag ) { if ( mca_sharedfp_individual_verbose ) { @@ -157,11 +206,18 @@ struct mca_sharedfp_base_module_2_0_0_t * mca_sharedfp_individual_component_file int mca_sharedfp_individual_component_file_unquery (ompio_file_t *file) { - /* This function might be needed for some purposes later. for now it - * does not have anything to do since there are no steps which need - * to be undone if this module is not selected */ + /* + * The query path may have subscribed and accepted the relaxed-ordering + * hint so it could participate in priority selection. If another + * sharedfp component wins, remove the public value; otherwise + * MPI_File_get_info would claim that a hint owned by the losing + * individual component is part of the active file stack. + */ + if (NULL != file && NULL != file->f_info) { + (void) opal_info_delete(file->f_info, "OMPIO_SHAREDFP_RELAXED_ORDERING"); + } - return OMPI_SUCCESS; + return OMPI_SUCCESS; } int mca_sharedfp_individual_module_init (ompio_file_t *file) diff --git a/ompi/mca/sharedfp/individual/sharedfp_individual_file_open.c b/ompi/mca/sharedfp/individual/sharedfp_individual_file_open.c index fbe0907167a..7dcd03170f3 100644 --- a/ompi/mca/sharedfp/individual/sharedfp_individual_file_open.c +++ b/ompi/mca/sharedfp/individual/sharedfp_individual_file_open.c @@ -13,6 +13,7 @@ * Copyright (c) 2015-2018 Research Organization for Information Science * and Technology (RIST). All rights reserved. * Copyright (c) 2016-2017 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -24,6 +25,8 @@ #include "ompi_config.h" #include "sharedfp_individual.h" +#include + #include "mpi.h" #include "ompi/constants.h" #include "ompi/mca/sharedfp/sharedfp.h" @@ -81,7 +84,7 @@ int mca_sharedfp_individual_file_open (struct ompi_communicator_t *comm, snprintf(datafilename, len, "%s%s%d",filename,".data.",fh->f_rank); - datafilehandle = (ompio_file_t *)malloc(sizeof(ompio_file_t)); + datafilehandle = (ompio_file_t *) calloc(1, sizeof(ompio_file_t)); if ( NULL == datafilehandle ) { opal_output(0, "mca_sharedfp_individual_file_open: unable to allocate memory\n"); free ( sh ); @@ -130,7 +133,7 @@ int mca_sharedfp_individual_file_open (struct ompi_communicator_t *comm, } snprintf ( metadatafilename, len, "%s%s%d", filename, ".metadata.",fh->f_rank); - metadatafilehandle = (ompio_file_t *)malloc(sizeof(ompio_file_t)); + metadatafilehandle = (ompio_file_t *) calloc(1, sizeof(ompio_file_t)); if ( NULL == metadatafilehandle ) { free (sh); free (datafilename); diff --git a/ompi/mca/sharedfp/sm/sharedfp_sm_file_open.c b/ompi/mca/sharedfp/sm/sharedfp_sm_file_open.c index edc453a7add..0a1f0af9d78 100644 --- a/ompi/mca/sharedfp/sm/sharedfp_sm_file_open.c +++ b/ompi/mca/sharedfp/sm/sharedfp_sm_file_open.c @@ -15,6 +15,7 @@ * and Technology (RIST). All rights reserved. * Copyright (c) 2015-2021 Cisco Systems, Inc. All rights reserved. * Copyright (c) 2016-2017 IBM Corporation. All rights reserved. + * Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. * $COPYRIGHT$ * * Additional copyrights may follow @@ -182,12 +183,18 @@ int mca_sharedfp_sm_file_open (struct ompi_communicator_t *comm, #if defined(HAVE_SEM_OPEN) + /* POSIX requires a named-semaphore name to begin with '/' (and to + contain no other '/'). Linux and macOS tolerate a missing leading + slash, but FreeBSD enforces POSIX: sem_open() of a name that does not + start with '/' fails with EINVAL. That made MPI_File_open() fail on + FreeBSD whenever the sm sharedfp component was selected (its default + priority is the highest), so always emit a leading-slash name. */ #if defined (__APPLE__) sm_data->sem_name = (char*) malloc( sizeof(char) * 32); - snprintf(sm_data->sem_name,31,"OMPIO_%s",filename_basename); + snprintf(sm_data->sem_name,31,"/OMPIO_%s",filename_basename); #else sm_data->sem_name = (char*) malloc( sizeof(char) * 253); - snprintf(sm_data->sem_name,252,"OMPIO_%s",filename_basename); + snprintf(sm_data->sem_name,252,"/OMPIO_%s",filename_basename); #endif // We're now done with filename_basename. Free it here so that we // don't have to keep freeing it in the error/return cases. diff --git a/ompi/mca/topo/base/topo_base_dist_graph_create.c b/ompi/mca/topo/base/topo_base_dist_graph_create.c index 66e2976deb5..43c899cb4b8 100644 --- a/ompi/mca/topo/base/topo_base_dist_graph_create.c +++ b/ompi/mca/topo/base/topo_base_dist_graph_create.c @@ -19,6 +19,7 @@ #include "ompi_config.h" #include "ompi/communicator/communicator.h" +#include "ompi/mca/coll/base/coll_tags.h" #include "ompi/info/info.h" #include "ompi/mca/topo/base/base.h" #include "ompi/datatype/ompi_datatype.h" @@ -27,8 +28,6 @@ #define IN_INDEX 0 #define OUT_INDEX 1 -#define MCA_TOPO_BASE_TAG_DIST_EDGE_IN -50 -#define MCA_TOPO_BASE_TAG_DIST_EDGE_OUT -51 typedef struct _dist_graph_elem { int in; @@ -172,7 +171,7 @@ int mca_topo_base_dist_graph_distribute(mca_topo_base_module_t* module, position *= 2; } err = MCA_PML_CALL(isend( &rin[position], count, (ompi_datatype_t*)&ompi_mpi_int, - i, MCA_TOPO_BASE_TAG_DIST_EDGE_IN, MCA_PML_BASE_SEND_STANDARD, + i, MCA_COLL_BASE_TAG_TOPO_DIST_EDGE_IN, MCA_PML_BASE_SEND_STANDARD, comm, &reqs[pending_reqs])); pending_reqs++; } @@ -183,7 +182,7 @@ int mca_topo_base_dist_graph_distribute(mca_topo_base_module_t* module, position *= 2; } err = MCA_PML_CALL(isend(&rout[position], count, (ompi_datatype_t*)&ompi_mpi_int, - i, MCA_TOPO_BASE_TAG_DIST_EDGE_OUT, MCA_PML_BASE_SEND_STANDARD, + i, MCA_COLL_BASE_TAG_TOPO_DIST_EDGE_OUT, MCA_PML_BASE_SEND_STANDARD, comm, &reqs[pending_reqs])); pending_reqs++; } @@ -210,7 +209,7 @@ int mca_topo_base_dist_graph_distribute(mca_topo_base_module_t* module, for( left_over = count, current_pos = i = 0; left_over > 0; i++ ) { MCA_PML_CALL(recv( &temp[count - left_over], left_over, (ompi_datatype_t*)&ompi_mpi_int, /* keep receiving in the same buffer */ - MPI_ANY_SOURCE, MCA_TOPO_BASE_TAG_DIST_EDGE_IN, + MPI_ANY_SOURCE, MCA_COLL_BASE_TAG_TOPO_DIST_EDGE_IN, comm, &status )); how_much = status._ucount / int_size; if (MPI_UNWEIGHTED != weights) { @@ -246,7 +245,7 @@ int mca_topo_base_dist_graph_distribute(mca_topo_base_module_t* module, for( left_over = count, current_pos = i = 0; left_over > 0; i++ ) { MCA_PML_CALL(recv( &temp[count - left_over], left_over, (ompi_datatype_t*)&ompi_mpi_int, /* keep receiving in the same buffer */ - MPI_ANY_SOURCE, MCA_TOPO_BASE_TAG_DIST_EDGE_OUT, + MPI_ANY_SOURCE, MCA_COLL_BASE_TAG_TOPO_DIST_EDGE_OUT, comm, &status )); how_much = status._ucount / int_size; diff --git a/ompi/mpi/Makefile.am b/ompi/mpi/Makefile.am index 80ae278fde2..f740b22a9cf 100644 --- a/ompi/mpi/Makefile.am +++ b/ompi/mpi/Makefile.am @@ -12,7 +12,7 @@ # Copyright (c) 2006-2018 Cisco Systems, Inc. All rights reserved. # Copyright (c) 2015 Research Organization for Information Science # and Technology (RIST). All rights reserved. -# Copyright (c) 2025 Jeffrey M. Squyres. All rights reserved. +# Copyright (c) 2025-2026 Jeffrey M. Squyres. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -24,6 +24,7 @@ EXTRA_DIST += \ mpi/fortran/configure-fortran-output-bottom.h \ mpi/help-mpi-api.txt \ mpi/bindings/bindings.py \ + mpi/bindings/check_f08_names.py \ mpi/bindings/ompi_bindings/consts.py \ mpi/bindings/ompi_bindings/c.py \ mpi/bindings/ompi_bindings/c_type.py \ diff --git a/ompi/mpi/bindings/bindings.py b/ompi/mpi/bindings/bindings.py index 9fa858db93b..1dfd4c3377c 100644 --- a/ompi/mpi/bindings/bindings.py +++ b/ompi/mpi/bindings/bindings.py @@ -1,6 +1,7 @@ # Copyright (c) 2024-2025 Triad National Security, LLC. All rights # reserved. # +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. # $COPYRIGHT$ # # Additional copyrights may follow @@ -35,6 +36,9 @@ def main(): help='generate ts suffixes for appropriate routines') parser_fortran.add_argument('--fort-std', choices=('f90', 'f08'), help='fortran standard to use for fortran module and code generation') + parser_fortran.add_argument('--pympistd-dir', default=None, + help='path to the pympistandard submodule; when given, the generated ' + 'Fortran interfaces use the standard MPI dummy-argument names') # Handler for generating actual code subparsers_fortran = parser_fortran.add_subparsers() parser_code = subparsers_fortran.add_parser('code', help='generate binding code') diff --git a/ompi/mpi/bindings/check_f08_names.py b/ompi/mpi/bindings/check_f08_names.py new file mode 100644 index 00000000000..65cb18beecf --- /dev/null +++ b/ompi/mpi/bindings/check_f08_names.py @@ -0,0 +1,311 @@ +# Copyright (c) 2026 Jeffrey M. Squyres. All rights reserved. +# +# $COPYRIGHT$ +# +# Additional copyrights may follow +# +# $HEADER$ +"""Validate the Open MPI mpi_f08 interfaces against the MPI standard. + +The mpi_f08 module is the only Fortran binding whose dummy-argument +*names* are part of the user-visible contract (keyword arguments). This +script loads the MPI Forum's pympistandard metadata and checks that every +mpi_f08 procedure's dummy arguments agree with the standard on three +things: + + * name -- the dummy-argument name (case-insensitive) + * intent -- INTENT(IN|OUT|INOUT) + * type -- the declared Fortran type + +It is intended to be run at build time over the generated Fortran source +(api_f08_generated.F90 and the interface headers) plus the hand-written +*_f08.F90 files, and exits non-zero -- failing the build -- if any +mpi_f08 interface has drifted from the standard. Only the mpi_f08 +module is in scope; the C back-end and the older mpi (f90) module are +not checked. + +Things the standard deliberately leaves to the implementation are not +flagged: + + * choice buffers (standard type 'TYPE(*), ...'); Open MPI renders + these with its own ignore-TKR macro and may attach INTENT(IN). + * any argument whose standard F08 intent is unspecified (None), e.g. + a TYPE(MPI_Status) argument that must also accept MPI_STATUS_IGNORE. + * a large-count (_c) procedure that the standard does not provide an + F08 binding for. +""" + +import argparse +import os +import re +import sys + + +# Automake-style result colors (PASS=green, FAIL=red, SKIP=blue), matching +# Automake's color-tests palette. +_STATUS_COLOR = {'PASS': '\033[0;32m', 'FAIL': '\033[0;31m', 'SKIP': '\033[1;34m'} +_COLOR_RESET = '\033[m' + + +def _use_color(): + """Colorize like Automake: AM_COLOR_TESTS=always forces it; otherwise a TTY.""" + setting = os.environ.get('AM_COLOR_TESTS', '') + if setting == 'always': + return True + if setting == 'no' or os.environ.get('NO_COLOR'): + return False + return sys.stdout.isatty() + + +def emit_status(status, label, reason=None): + """Print an Automake-style ':