Repository navigation
Conversation
…d semantics T08 needs a versioned bijection between the router's canonical logical_expert_id and the physical_expert_id recorded in the Envelope. Without one, nothing verifies that a recorded physical id corresponds to the logical id on the same row: a dispatch-table off-by-one would route a token to the wrong expert while every semantic comparison still passed. Two readings of physical_expert_id are in circulation and they disagree. assembler.assemble and checker.validate_plan treat it as a global permutation of range(E). The task contract defines it as the rank-local id under the current EP placement, which makes the bijection logical <-> (ep_rank, physical). With EP=8 and E=256 the rank-local ids are 0..31 on each rank, so sorted(mapping) is [0]*8 + [1]*8 + ... and the current check rejects a correct placement. This module does not pick a winner. A PlacementMap declares its semantics and validation applies the matching rule, so a map recorded under one reading can never be silently judged under the other, the fingerprint separates them, and whichever way the question is resolved the implementation already exists. Construction paths all return the same type: identity, adopt an existing flat list, explicit (ep_rank, physical) pairs, or generate block/interleave from an EP configuration. A global physical id is the expert's position in the rank-major concatenation of per-rank tables, the only numbering that agrees with the rank-local one about where an expert sits inside its own rank. plan_fragment extends the plan's placement_map additively: version and logical_to_physical keep their meaning, so consumers reading only those two are unaffected and no contract delta is needed. The Envelope rank is deliberately not compared against ep_rank. EP is not a token dimension, so a token recorded on one DP/CP/TP/PP rank routinely routes to experts hosted elsewhere; a control test pins this. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mocks.OWNERSHIP records what each parallel dimension does to token ownership. Each mode is clear in isolation and none is checkable in isolation: a rank under tp=2 cp=2 dp=2 pp=2 ep=2 is at once a sequence-replica, a sequence-partition, a batch-partition, a layer-replica and token-irrelevant, and no row of the table says whether rank 19 should hold token 5 at layer 2. The unit of ownership is a routing decision (absolute_layer, global_token_id), not a token. A token-keyed model cannot express pp: replica_by_layer at all, since the same token at two layers lives on two stages. Everything goes through carriers(): the set of global ranks that must hold a decision. Each dimension either shrinks that set or leaves it alone, so composition is an intersection and three rules cover every configuration -- a rank holding what it does not carry is AMBIGUOUS_GLOBAL_TOKEN_MAPPING, a carrier missing what it owes is INCOMPLETE_ARTIFACT, carriers disagreeing is ROUTE_SEMANTIC_FINGERPRINT_MISMATCH. carriers is built by inverting expected_decisions over the world rather than recomposing per-dimension rules, so the two directions cannot drift apart. rank_order and the CP-before-SP sequence split are declared, not inferred: guessing either yields a validator that agrees with the truth on symmetric configurations and silently disagrees on the rest. sp is a flag rather than a size because sequence parallelism subdivides the existing TP group, and the resulting tp mode is read from mocks.OWNERSHIP rather than restated. ep is kept separate from tp although the carrier arithmetic is identical: a fingerprint difference tracking the EP coordinate means expert placement leaked into the routing decision, a different bug with a different owner, so the failure message names the implicated dimension. Gate order is rank set -> per-rank ownership -> replica agreement. check_cross_config is token-centric, never rank-against-rank, since changing the layout is precisely what makes rank N hold different tokens. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
P7 already owns the control plane: Planner builds the grid, RuntimeTools materializes, PairedRunner executes, _resume restarts. The contract says T08 supplies a materializer hook and RoutePlan evidence and does not write a second planner, runner or tolerance, so this is an adapter and nothing else. P3_ROUTER_KNOBS extends V1_KNOBS additively, following the precedent in adapters/knobs.py. V1_KNOBS has no training-side pipeline, data or expert parallel size, so the configurations the router must be compared across cannot be expressed in it; nine paths are added and nothing is edited. The parallel sizes are PROCESS-scoped because a process cannot change its EP group mid-run, and router.placement_semantics is carried as a knob so P7's own identity machinery keeps the two readings of physical_expert_id from ever being spanned by one comparison. P3RouterMaterializer builds and validates the router's view -- a ParallelConfig and a PlacementMap -- before reporting anything as applied. A knob the router does not model is reported UNOBSERVABLE rather than waved through: APPLIED means the value was observed in the running system, and claiming it would make a router-only materialization look like a whole-system one. UNOBSERVABLE is in require_executable's rejected set, so a case mixing engine knobs is refused rather than half-run. An unbuildable configuration is ERROR, never FALLBACK, because there is no "something else" that ran instead. rank_token_manifest and materialize_rank_rows connect P7's rank ownership manifest to mocks.materialize_tokens, the hook the start kit left for this. Row slicing is delegated rather than reimplemented so row selection keeps one definition. materialize_rank_rows separates a rank working on a different pipeline stage (MISSING_RANK) from an over-provisioned rank that owns nothing at all (ZERO_ACTIVE_TOKENS); both are legal states and an empty slice would make either look like completed work. Tests drive the real RuntimeTools, not a stand-in: the claim under test is that P7's existing control plane accepts this adapter. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
P3 produces dz and stops; P1 owns the gate GEMM backward and the all-reduce that sums dW_gate across DP ranks belongs to the training framework. Nothing here implements or wraps a collective. It is recorded because a reduction has an order. Bucket composition, bucket order, algorithm and accumulation dtype fix the summation order of the gate weight gradient; move any of them and dW_gate shifts in the last bits, which shifts the next step's gate logits and router scores. The router is a discrete decision layer, so a last-bit move near the Top-K boundary flips a selection -- and by then it looks like a router defect. The record lets an unexplained routing change be checked against the collective first. CollectiveProvenance keeps configured_algorithm and algorithm as separate fields: a configured value may not stand in for a readback, and a difference with no fallback_reason is SILENT_FALLBACK. An unsourced record is not an observation, so it is missing evidence rather than weaker evidence. A missing field fails closed with MISSING_PROVENANCE instead of defaulting, since a default would make two unrelated runs compare equal. At DP=1 no all-reduce runs and none() is valid; demanding evidence of a collective that does not run would be theatre. The fingerprint covers everything that can move the summation order and excludes source and notes, so one collective observed two ways does not read as drift. collective_delta describes differences rather than raising, because two DP sizes must have different collectives; require_same_reduction is the stricter gate for a comparison that claims the collective is not its axis. In the P7 binding a missing record and a malformed one take different paths: DP>1 with no record is UNOBSERVABLE on training.data_parallel_size -- nobody failed to build anything, the router was handed no observation -- while a supplied record that does not hold together is ERROR. UNOBSERVABLE is in require_executable's rejected set, so a DP case with no collective evidence is not strictly executable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Comment |
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The T08 WS2 contracts on top of the start kit, one commit each:
rl_engine/p3/placement.py— the versionedlogical <-> physicalexpert placement map, supporting both readings of
physical_expert_id(see the open question below).
rl_engine/p3/ownership.py— token ownership under combinedparallelism, turning the
mocks.OWNERSHIPtable into behaviour.rl_engine/p3/p7_binding.py— the P7cross_configmaterializer andconsumer hooks.
rl_engine/p3/collective_provenance.py— the DP gate parameter-gradientcollective, recorded as provenance.
Design records:
docs/design/p3-placement-map.md,docs/design/p3-token-ownership.md,docs/design/p3-p7-binding.md,docs/design/p3-gate-grad-collective.md.Together these close the four T08 deliverables in the task contract §4.
Open question: what is
physical_expert_id?While implementing against the start kit I hit a disagreement I could not
resolve from the code, so the placement map implements both readings
rather than picking one.
checker.validate_plantreats it as a global permutation:The task contract (§1.2) defines it as the rank-local id under the current
EP placement, which makes the bijection
logical <-> (ep_rank, physical).These are not reconcilable. With
EP=8andE=256, rank-local ids are0..31on each of the eight ranks, so
sorted(mapping)is[0]*8 + [1]*8 + ...andthe current check rejects a correct placement.
A
PlacementMaptherefore declares itssemanticsand validation applies thematching rule, so a map recorded under one reading can never be silently
judged under the other, the fingerprint separates them, and whichever way the
question is resolved the implementation already exists. The reading is also
carried as a P7 knob (
router.placement_semantics), so a cross-configcomparison cannot span the two.
Which reading is authoritative? P4 needs the answer too, since it decides
what a dispatch target id means. If rank-local wins,
assembler.assembleandchecker.validate_planeach need their bijection check routed throughplacement_problems— one line in each. I deliberately did not make thatchange while the question is open.
1. Placement map
PlacementMap: version, semantics,ep_size,logical -> ep_rank,logical -> physical, strategy, and a provenance fingerprint.identity()(theassembler's current default),
from_logical_to_physical()(adopt a planalready recorded under the flat list),
from_pairs()(irregular placementsafter rebalancing),
from_ep_config()(block/interleave),from_plan_fragment().concatenation of per-rank tables — the only numbering that agrees with the
rank-local one about where an expert sits inside its own rank. For
blockit collapses to the identity permutation.
plan_fragment()extends the plan'splacement_mapadditively:versionandlogical_to_physicalkeep their meaning and position, soconsumers reading only those two are unaffected. No contract delta needed.
validate_placement_map(64),check_placement_version(65),validate_plan_placement(version -> structure -> rows).2. Token ownership
mocks.OWNERSHIPrecords what each dimension does. Each mode is clear inisolation and none is checkable in isolation: a rank under
tp=2 cp=2 dp=2 pp=2 ep=2is at once a sequence-replica, asequence-partition, a batch-partition, a layer-replica and token-irrelevant,
and no row of the table says whether rank 19 should hold token 5 at layer 2.
(absolute_layer, global_token_id), not a token.A token-keyed model cannot express
pp: replica_by_layerat all, since thesame token at two layers lives on two pipeline stages. This also matches the
semantic hash, which is per routing decision.
carriers()— the set of global ranks that musthold a decision. Each dimension either shrinks that set or leaves it alone,
so composition is an intersection and three rules cover every configuration:
a rank holding what it does not carry is
AMBIGUOUS_GLOBAL_TOKEN_MAPPING, acarrier missing what it owes is
INCOMPLETE_ARTIFACT, carriers disagreeingis
ROUTE_SEMANTIC_FINGERPRINT_MISMATCH.carriersis built by invertingexpected_decisionsover the worldrather than recomposing per-dimension rules, so the two directions cannot
drift apart.
epis kept separate fromtpalthough the carrier arithmetic isidentical: a fingerprint difference that tracks the EP coordinate means
expert placement leaked into the routing decision — a different bug with a
different owner — so the failure message names the implicated dimension.
check_cross_configis token-centric, never rank-against-rank: changing thelayout is precisely what makes rank N hold different tokens.
Two things are declared, not inferred, because guessing either yields a
validator that is stable in testing and wrong in production:
rank_order(how a global rank decomposes) and the CP-before-SP sequence split order.
spis a flag rather than a size, since sequence parallelism subdivides theexisting TP group; the resulting
tpmode is read frommocks.OWNERSHIPrather than restated, so that table stays the single source.
3. P7 binding
P3_ROUTER_KNOBSextendsV1_KNOBSadditively, exactly asadapters/knobs.pydid:V1_KNOBShas no training-side pipeline, data orexpert parallel size, so the configurations the router must be compared across
cannot be expressed in it. Nine paths are added; nothing is edited or deleted.
The parallel sizes are
PROCESS-scoped because a process cannot change its EPgroup mid-run.
P3RouterMaterializerimplementsRuntimeMaterializer. Two decisions worthreview attention:
UNOBSERVABLE, notAPPLIED.APPLIEDmeans the value was observed in the running system; claiming itfor
rollout.dtypewould make a router-only materialization look like awhole-system one.
UNOBSERVABLEis inrequire_executable's rejected set,so a case mixing engine knobs is refused rather than half-run, and the
reason names the owning adapter.
ERROR, neverFALLBACK. There is no"something else" that ran instead of a parallel configuration that does not
exist.
rank_token_manifestandmaterialize_rank_rowsconnect P7's rank ownershipmanifest to
mocks.materialize_tokens— the hook the start kit left forT07/T08. Row slicing is delegated rather than reimplemented so row selection
keeps one definition.
The tests drive the real
RuntimeTools, not a stand-in. The claim undertest is that P7's existing control plane accepts this adapter, and a mock
would prove only that the adapter satisfies a mock.
4. Gate-gradient collective provenance
P3 produces
dzand stops; the all-reduce that sumsdW_gateacross DP ranksbelongs to the training framework, and nothing here implements or wraps it.
It is recorded because a reduction has an order. Bucket composition, bucket
order, algorithm and accumulation dtype fix the summation order of the gate
weight gradient; move any of them and
dW_gateshifts in the last bits, whichshifts the next step's gate logits and router scores. The router being a
discrete decision layer, a last-bit move near the Top-K boundary flips a
selection — and by then it looks like a router defect. The record lets an
unexplained routing change be checked against the collective first.
configured_algorithmandalgorithmare separate fields; a differencewith no
fallback_reasonisSILENT_FALLBACK. An unsourced record is notan observation, so it is missing evidence rather than weaker evidence.
MISSING_PROVENANCE, never a default: a default wouldmake two unrelated runs compare equal.
none()is valid. Demanding evidence of acollective that does not run would be fail-closed theatre.
excludes
source/notes, so one collective observed two ways does not readas drift.
collective_deltadescribes differences rather than raising — two DP sizesmust differ — while
require_same_reductionis the stricter gate for acomparison that claims the collective is not its axis.
In the binding, a missing record and a malformed one take different paths.
DP>1 with no record is
UNOBSERVABLEontraining.data_parallel_size: nobodyfailed to build anything, the router was handed no observation of a reduction
P1 owns. A supplied record that does not hold together is
ERROR. SinceUNOBSERVABLEis inrequire_executable's rejected set, a DP case with nocollective evidence is not strictly executable — this is a deliberate
tightening and it is what the four existing DP tests now exercise.
Validation
Every negative case injects exactly one defect, so a correct verdict cannot be
a coincidence of two interacting faults. Gate precedence is pinned explicitly
throughout — version before structure before rows; stale rank set before
missing rank; unauthorized decision before gap; rank set before ownership;
structure before completeness before fallback.
Controls pin the inverse, and are as load-bearing as the negatives: an
Envelope
rankdiffering from everyep_rank(EP is not a token dimension),pipeline stages holding disjoint layers, two TP ranks holding the same token,
two configurations where rank 0 holds different tokens, an over-provisioned DP
rank that legitimately owns nothing, and the same collective read back by two
different mechanisms — none of these may be flagged as defects or read as
drift.
The two skips are the placement interop tests, which drive the real
assembleand the kit's ownchecker.validate_planto show that a generatedglobal placement needs no change to anything existing. They skip where the
native bitmath library cannot be loaded — see below. Please confirm they
pass in CI.
Related work on
dsv4-rocmTwo drafts under KLR #81 touch adjacent ground: #634 (item 41, CP
ownership and PP2 stage boundary) and #632 (item 40, sealed TP/SP/DP
deterministic collective schedule).
Those implement the transport and the collectives themselves — RCCL
AllReduce/AllGather, Megatron Core
P2PCommunicator, contiguous CP layout.This change stays on the P3 side of that line: it decides which rank must
hold which routing decision, and it records the gate-gradient collective
rather than wrapping it, because contract §2.1 and §2.7 put the collective
outside P3.
So the two should compose rather than collide, but the vocabulary is worth
reconciling while all of it is still draft. Happy to align naming in either
direction, and happy to consume
docs/contracts/dsv4-parallel-boundaries.md/dsv4-parallel-collectives.mdonce they land rather than keeping a second description of the same
boundaries.
Platform note (separate from this change)
On a Windows development host the start kit is not importable at all:
rl_engine/p3/provider.pydoesimport fcntl, which is Unix-onlyrl_engine/p3/bitmath.pyloads a Unix build product throughctypes, whichraises
OSError: [WinError 193]Consequently every module in
tests/p3except these four fails at collectionon Windows. All four new modules build their fixtures synthetically and run
anywhere; the interop tests skip rather than error. Flagging it in case
cross-platform development is in scope; happy to open a separate issue.
Dependency
Branches from #498. Base is
dsv4-p3-dev; please merge #498 first and I willrebase.
This does not touch #481 — that PR builds a parallel
rl_engine/moepackagewith its own verdict table and CLI, while #498 states the canonical namespace
is
rl_engine.p3. This change follows #498.Not in this PR
TokenLayoutis supplied by the caller. Deriving it from a live job's batchand model configuration is the remaining T08 item.
This is the contract and validation layer, not a closed WS2 Gate. The
rules are exercised against the start kit's recorded fixtures on CPU; the Gate
itself needs real multi-rank artifacts from TP=1/2/4/8 and the CP/DP/PP/EP
configurations on CUDA SM90, which contract §1.4 makes the only accepted
backend. Those runs have not happened. Flagging it so "four deliverables
implemented" is not read as "WS2 validated" — and happy to be pointed at
whichever named GPU environment T10 ends up standing up.
🤖 Generated with Claude Code