8.1.2. Per-App-Context Mapping: Implementation Plan
8.1.2.1. Overview
This plan describes the implementation of per-app-context mapping, ranking, and binding
policies for PRRTE as specified in per-app-mapping.rst. The work adds nine new
PRTE_APP_* attribute keys, extends prte_rmaps_options_t with an app_idx field,
replaces the single-dispatch path in prte_rmaps_base_map_job() with a per-app loop
when any app carries per-app directives, and bumps the rmaps framework version from
4.0.0 to 5.0.0.
8.1.2.2. Current State
Item |
Current value |
|---|---|
Last |
|
|
|
|
no |
|
|
rmaps framework version |
|
Module struct |
|
|
no |
|
none |
8.1.2.3. Phases
8.1.2.3.1. Phase 1 — Data model (no logic change)
Goal: All new attributes and the app_idx field exist; the codebase compiles
cleanly with no behaviour change.
Files:
src/util/attr.h— add keys 26–34src/mca/rmaps/rmaps_types.h— addapp_idxtoprte_rmaps_options_t; addPRTE_RMAPS_BASE_VERSION_5_0_0macro and keep4_0_0as deprecated aliassrc/mca/rmaps/rmaps.h— addprte_rmaps_base_module_5_0_0_tstruct/typedef; keepprte_rmaps_base_module_ttypedef pointing at the new struct
No functional change. All existing code still compiles because the new app_idx field
is initialised to -1 (map all apps) and no component reads it yet.
8.1.2.3.2. Phase 2 — Parsing functions
Goal: The three new prte_rmaps_base_set_app_*_policy() functions exist,
parse correctly, and store results into app->attributes. The forbidden-modifier
guards (OVERSUBSCRIBE, NOOVERSUBSCRIBE, INHERIT, NOINHERIT) are enforced.
NOLOCAL is stored as a directive bit in PRTE_APP_MAPBY.
Files:
src/mca/rmaps/base/rmaps_base_frame.c— add the three parsing functionssrc/mca/rmaps/base/base.h— declare them
These functions mirror the existing prte_rmaps_base_set_mapping_policy() /
check_modifiers() logic but write into app->attributes instead of jdata->map.
8.1.2.3.3. Phase 3 — prte_rmaps_base_map_job() per-app dispatch
Goal: When at least one app carries PRTE_APP_MAPBY, PRTE_APP_RANKBY, or
PRTE_APP_BINDTO, the function enters a per-app loop instead of the single-dispatch
path. Otherwise existing behaviour is unchanged.
Sub-steps (in order):
Add
prte_rmaps_base_resolve_app_options()as a static helper — builds anapp_optionsstruct from the job-leveloptionsplus per-app attribute overrides. It must: (a) mask thePRTE_APP_MAPBY/RANKBY/BINDTOdirective bits off when writingopts->map/rank/bind(route overload toopts->overload); (b) refresh the map-derived fields (maptype/mapdepth/mapspan/ordered) when the app overrides its map; and (c) default ranking and binding from the app’s own map when no explicit per-app--rank-by/--bind-tois given — via small pure helpersprte_rmaps_base_derive_ranking()/derive_binding()— rather than inheriting the job-level ranking/binding.Add
prte_rmaps_base_compute_nprocs()call per app (extract from existing process-count logic if not already a helper, or call the existing macro inline).Add the
any_per_appscan.Replace the single-dispatch block with the per-app loop when
any_per_appis true.Handle the pre-loop promotion rules: display-map directive on any app → promote to job level; conflicting
INHERIT/NOINHERIT→ abort.
File:
src/mca/rmaps/base/rmaps_base_map_job.c
8.1.2.3.4. Phase 3a — per-app attribute plumbing (CLI → attributes)
Goal: The per-app PRTE_APP_* attributes actually reach map_job. This
plumbing largely already exists, but two things are required for it to work:
The
prte_rmaps_base_set_app_*_policy()helpers must store everyPRTE_APP_*attribute with ``PRTE_ATTR_GLOBAL``, notPRTE_ATTR_LOCAL.LOCALattributes are not packed and are silently dropped when the spawn request is relayed to the DVM master, so per-app directives stored asLOCALnever reachmap_jobandany_per_appis always false. This is the single most important correctness fix in the whole feature and produces no error when wrong — only silent fallback to the job-level policy.The spawn-assembly loops (
src/prted/prte.candsrc/prted/prun_common.c) must convert eachpmix_app_t.infofrom that app’s ownapp->infolist, not from the job-level info.
Files:
src/mca/rmaps/base/rmaps_base_frame.c(LOCAL→GLOBALin the three setters)src/prted/prte_app_parse.c,src/prted/pmix/pmix_server_dyn.c(already present)src/prted/prte.c,src/prted/prun_common.c(per-app info conversion)
8.1.2.3.5. Phase 4 — Ranking with app_idx
Goal: prte_rmaps_base_compute_vpids() accepts an app_idx parameter so the
per-app loop can call it once per app.
Files:
src/mca/rmaps/base/rmaps_base_ranking.c— addapp_idxparameter; when ≥ 0 process only the matching app context; global rank counter is passed between calls so vpids remain contiguoussrc/mca/rmaps/base/base.h— update declarationsrc/mca/rmaps/base/rmaps_base_map_job.c— update all call sites
8.1.2.3.6. Phase 5 — Component app_idx guards
Goal: Each of the five components skips app contexts that do not match
options->app_idx when that field is ≥ 0.
Files (one change each — add the app_idx guard to the inner app-context loop):
src/mca/rmaps/round_robin/rmaps_rr.csrc/mca/rmaps/ppr/rmaps_ppr.c— also remove the duplicate per-app PPR/PES override that is now handled centrally byprte_rmaps_base_resolve_app_options()src/mca/rmaps/seq/rmaps_seq.c— also checkPRTE_APP_MAP_FILEbeforePRTE_JOB_FILEsrc/mca/rmaps/rank_file/rmaps_rank_file.c— sameMAP_FILEfallbacksrc/mca/rmaps/lsf/rmaps_lsf.c
Also update each component file to reference PRTE_RMAPS_BASE_VERSION_5_0_0.
8.1.2.3.7. Phase 6 — Schizo / CLI wiring
Goal: MPMD per-app --map-by, --rank-by, --bind-to arguments call the new
prte_rmaps_base_set_app_* functions instead of the job-level variants. PMIx spawn
path maps PMIX_MAPBY/PMIX_RANKBY/PMIX_BINDTO in per-app info[] arrays to the
new attributes.
Files:
src/mca/schizo/prte/schizo_prte.csrc/mca/schizo/base/schizo_base_stubs.c(andbase.h)src/tools/prun/prun.csrc/prted/prte.c
8.1.2.3.7.1. Relax the tool-level argv pre-scan guards
Before schizo ever sees the command line, both prun (src/tools/prun/prun.c,
prte_prun()) and the prte HNP launcher (src/prted/prte.c, main()) walk
the raw argv to normalise option spellings (--rank-by → --rankby,
--bind-to → --bindto). In doing so they currently track rankby_found /
bindto_found booleans and reject a second occurrence of --rank-by or
--bind-to with the help-schizo-base.txt multi-instances error.
This guard makes per-app ranking and binding impossible: an MPMD line such as
prun app1 --rank-by node : app2 --rank-by slot
legitimately repeats --rank-by once per app context and trips the guard before
the per-app machinery is ever reached. Note --map-by already has no such
guard — it is renamed unconditionally on every occurrence — which is exactly why
per-app --map-by already works while --rank-by/--bind-to do not.
The fix is to make --rank-by and --bind-to behave like --map-by in this
pre-scan: drop the rankby_found / bindto_found tracking and the
multi-instances rejection, and rename every occurrence unconditionally.
Distinguishing a legitimate per-app repeat from an erroneous duplicate
job-level option is the schizo MPMD parser’s job (it associates each option with
its app context by : separator), not the argv pre-scan’s — the pre-scan
cannot tell the two apart and must not try.
The two pre-scan loops in prun and prte are exact copies of each other, so
rather than relaxing each in place the shared logic is factored into one helper,
prte_schizo_base_normalize_argv() (src/mca/schizo/base/schizo_base_stubs.c,
declared in src/mca/schizo/base/base.h). It normalises all four deprecated
spellings (--map-by, --rank-by, --bind-to, --runtime-options) in
place and returns any --personality value found. Both tools replace their
inline loop with a single call to it.
8.1.2.3.8. Phase 7 — Build system wiring
Goal: make check builds and runs the unit test suite.
Files:
Makefile.am— addtesttoSUBDIRSaftersrcconfig/prte_config_files.m4— addtest/Makefile,test/unit/Makefile,test/unit/rmaps/MakefiletoAC_CONFIG_FILEStest/Makefile.am— new,SUBDIRS = unittest/unit/Makefile.am— new,SUBDIRS = rmapstest/unit/rmaps/Makefile.am— new (see spec for contents)
8.1.2.3.9. Phase 8 — Unit tests
Goal: Eight test .c files exercise the new code paths; make check passes.
New files under test/unit/rmaps/:
test_rmaps_main.ctest_policy_parse.ctest_resolve_options.ctest_dispatch.ctest_round_robin.ctest_ppr.ctest_seq.ctest_rank_file.c
8.1.2.4. Dependency Graph
Phase 1 (data model)
└── Phase 2 (parsing functions)
└── Phase 3 (map_job dispatch)
├── Phase 4 (ranking app_idx) ← depends on Phase 3 call sites
└── Phase 5 (component guards) ← depends on Phase 1 for app_idx field
Phase 6 (schizo) ← depends on Phase 2
Phase 7 (build) ← independent, can be done any time
Phase 8 (tests) ← depends on all prior phases
Phases 1–5 must be done in order. Phase 6 can proceed in parallel with Phases 3–5 once Phase 2 is done. Phase 7 can be done any time before Phase 8.
8.1.2.5. Key Design Decisions (from spec)
Decision |
Consequence |
|---|---|
|
|
|
Same rejection; map_job promotes them if consistent, aborts if conflicting |
|
Stored as directive bit in |
|
Pre-loop promotion: any app with display-map attr → set on jdata |
|
seq/rank_file check app attr first, fall back to job attr |
Existing |
Backward-compatible: still read by ppr component; |
Global vpid assignment |
Monotonically increasing across apps; per-app ranking controls intra-app order only |
|
|
8.1.2.6. Attribute Mapping: prte_rmaps_options_t Field ↔ PRTE_APP_* Key
|
New attribute key |
PMIx type |
|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
map/rankfile path |
|
|
dist device name |
|
|
|
|
|
(core cpus flag) |
|
|
|
|
|
Note: prte_rmaps_options_t does not currently have a dist_device string field.
resolve_app_options() will need to read PRTE_APP_DIST_DEVICE and store it on the
options struct. Either add a char *dist_device field to prte_rmaps_options_t in
Phase 1, or handle it by reading the attribute directly inside the dist component if
one exists. Given no dist component exists in the current tree, this attribute can
be defined but left wired-up-later; resolve_app_options() reads it and sets a new
opts->dist_device field.
8.1.2.7. Risk Areas
``compute_vpids`` signature change — every call site must be updated. Search for all callers before changing the signature.
``app_idx`` init to ``-1`` vs zero —
memsetzeroes the struct, soapp_idx = 0without the explicit assignment would silently map only app[0]. The explicitoptions.app_idx = -1assignment is mandatory and easy to miss.Node list statefulness with ``NOLOCAL`` — the dedicated unit test (see spec §”NOLOCAL on app[0] with shared HNP node”) must pass before the branch is considered correct. If
prte_rmaps_base_get_target_nodes()modifies shared node state, the per-app loop will produce incorrect results for subsequent apps.ppr component duplicate logic — the ppr component currently reads
PRTE_APP_PPRandPRTE_APP_PES_PER_PROCdirectly (rmaps_ppr.c lines 166, 171). In Phase 5 this must be removed and the values must come throughresolve_app_options()to avoid double-application.Per-app MPMD parsing — the
:separator logic must associate each--map-by/--rank-by/--bind-towith its app context. This already happens insrc/prted/prte_app_parse.c(each app segment is parsed in its owncreate_app()call), so no schizo MPMD bookkeeping is needed — but the spawn-assembly loops must convert eachpmix_app_t.infofrom that app’s ownapp->info.``PRTE_ATTR_LOCAL`` vs ``GLOBAL`` (highest-risk, silent failure) — the
set_app_*_policyhelpers must store everyPRTE_APP_*attribute asPRTE_ATTR_GLOBAL.LOCALattributes are dropped when the spawn request is packed and relayed to the DVM master, so per-app directives stored asLOCALnever reachmap_job;any_per_appstays false and every app falls back to the job-level policy with no error. A single-app test masks this (its directive equals the job policy); always verify with a multi-app MPMD case.Directive-bit masking in ``resolve_app_options`` — the
PRTE_APP_*attributes carry directive bits (GIVEN, overload,IS_SET) in their high bits. Assigning the raw value toopts->map/rank/bindbreaks the bare-enum comparisons in the components andcompute_vpids. Mask withPRTE_GET_*_POLICYand route the overload bit toopts->overload.
8.1.2.8. Verification
Use the offline prterun --rtos donotlaunch --display map technique (see the spec
§”Offline end-to-end verification” and AGENTS.md) to confirm placement, ranks, and
bindings for both single-app and multi-app MPMD cases before relying on the unit
tests. The multi-app case is what catches risks 6 and 7.