Skip to content

[DSv4][P3-T08] Placement, token ownership, P7 binding, and gate-gradient collective provenance - #639

Draft
AlanX-Yan wants to merge 4 commits into
RL-Align:dsv4-p3-devfrom
AlanX-Yan:t08-placement-p3
Draft

AlanX-Yan wants to merge 4 commits into
RL-Align:dsv4-p3-devfrom
AlanX-Yan:t08-placement-p3

Conversation

@AlanX-Yan

Copy link
Copy Markdown

Summary

The T08 WS2 contracts on top of the start kit, one commit each:

  1. rl_engine/p3/placement.py — the versioned logical <-> physical
    expert placement map, supporting both readings of physical_expert_id
    (see the open question below).
  2. rl_engine/p3/ownership.py — token ownership under combined
    parallelism, turning the mocks.OWNERSHIP table into behaviour.
  3. rl_engine/p3/p7_binding.py — the P7 cross_config materializer and
    consumer hooks.
  4. rl_engine/p3/collective_provenance.py — the DP gate parameter-gradient
    collective, 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_plan treats it as a global permutation:

require(len(mapping) == E and sorted(mapping) == list(range(E)),
        P3Verdict.INVALID_PLACEMENT_MAP, "archived placement not bijective")

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=8 and E=256, rank-local ids are 0..31
on each of the eight ranks, so sorted(mapping) is [0]*8 + [1]*8 + ... and
the current check rejects a correct placement.

A PlacementMap therefore 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. The reading is also
carried as a P7 knob (router.placement_semantics), so a cross-config
comparison 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.assemble and
checker.validate_plan each need their bijection check routed through
placement_problems — one line in each. I deliberately did not make that
change while the question is open.

1. Placement map

  • PlacementMap: version, semantics, ep_size, logical -> ep_rank,
    logical -> physical, strategy, and a provenance fingerprint.
  • Construction paths all returning the same type: identity() (the
    assembler's current default), from_logical_to_physical() (adopt a plan
    already recorded under the flat list), from_pairs() (irregular placements
    after rebalancing), from_ep_config() (block / interleave),
    from_plan_fragment().
  • A global physical id is defined as 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. For block
    it collapses to the identity permutation.
  • plan_fragment() extends the plan's placement_map additively:
    version and logical_to_physical keep their meaning and position, so
    consumers reading only those two are unaffected. No contract delta needed.
  • Gates validate_placement_map (64), check_placement_version (65),
    validate_plan_placement (version -> structure -> rows).

2. Token ownership

mocks.OWNERSHIP records what each dimension does. 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 is a 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 pipeline stages. This also matches the
    semantic hash, which is per routing decision.
  • 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.
  • ep is kept separate from tp although the carrier arithmetic is
    identical: 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_config is token-centric, never rank-against-rank: changing the
    layout 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.
sp is a flag rather than a size, since sequence parallelism subdivides the
existing TP group; the resulting tp mode is read from mocks.OWNERSHIP
rather than restated, so that table stays the single source.

3. P7 binding

P3_ROUTER_KNOBS extends V1_KNOBS additively, exactly as
adapters/knobs.py did: 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; nothing is edited or deleted.
The parallel sizes are PROCESS-scoped because a process cannot change its EP
group mid-run.

P3RouterMaterializer implements RuntimeMaterializer. Two decisions worth
review attention:

  • A knob the router does not model is UNOBSERVABLE, not APPLIED.
    APPLIED means the value was observed in the running system; claiming it
    for rollout.dtype 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, and the
    reason names the owning adapter.
  • An unbuildable configuration is ERROR, never FALLBACK. There is no
    "something else" that ran instead of a parallel configuration that does not
    exist.

rank_token_manifest and materialize_rank_rows connect P7's rank ownership
manifest to mocks.materialize_tokens — the hook the start kit left for
T07/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 under
test 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 dz and stops; the all-reduce that sums dW_gate across DP ranks
belongs 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_gate shifts in the last bits, which
shifts 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_algorithm and algorithm are separate fields; 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 is MISSING_PROVENANCE, never a default: 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 fail-closed theatre.
  • The fingerprint covers everything that moves the summation order and
    excludes source/notes, so one collective observed two ways does not read
    as drift.
  • collective_delta describes differences rather than raising — two DP sizes
    must differ — while require_same_reduction is the stricter gate for a
    comparison 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 UNOBSERVABLE on training.data_parallel_size: nobody
failed to build anything, the router was handed no observation of a reduction
P1 owns. A supplied record that does not hold together is ERROR. Since
UNOBSERVABLE is in require_executable's rejected set, a DP case with no
collective evidence is not strictly executable — this is a deliberate
tightening and it is what the four existing DP tests now exercise.

Validation

python -m pytest tests/p3/test_placement.py tests/p3/test_ownership.py \
                 tests/p3/test_p7_binding.py \
                 tests/p3/test_collective_provenance.py -q
-> 166 passed, 2 skipped

python -m ruff check  rl_engine/p3/ tests/p3/
python -m ruff format --check rl_engine/p3/ tests/p3/
-> clean

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 rank differing from every ep_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
assemble and the kit's own checker.validate_plan to show that a generated
global 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-rocm

Two 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.md
once 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.py does import fcntl, which is Unix-only
  • rl_engine/p3/bitmath.py loads a Unix build product through ctypes, which
    raises OSError: [WinError 193]

Consequently every module in tests/p3 except these four fails at collection
on 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 will
rebase.

This does not touch #481 — that PR builds a parallel rl_engine/moe package
with its own verdict table and CLI, while #498 states the canonical namespace
is rl_engine.p3. This change follows #498.

Not in this PR

TokenLayout is supplied by the caller. Deriving it from a live job's batch
and 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

AlanX-Yan and others added 4 commits October 15, 2026 00:22
…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>
@coderabbitai

coderabbitai Bot commented Oct 30, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

@Flink-ddd Flink-ddd added platform: cuda Specific optimizations or bugs in NVIDIA graphics cards (such as FlashInfer, TMA optimizations) DSv4 deepseek-P3 labels Oct 31, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deepseek-P3 DSv4 platform: cuda Specific optimizations or bugs in NVIDIA graphics cards (such as FlashInfer, TMA optimizations)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants