feat(MUSIC3-DEPTH-DEVICE): the RVQ depth decoder reaches the device at bf16, and the measurement moves the blocker off the dtype (#672, #1309) - #1330
Merged
Conversation
…e arm was blocked on, and record what the measurement refuted (#672, #1309, #1322) `ar.depth_forward` is 48.4% of a MiniMax-Music3 run on `thor:gpu0` and the last large stage still on the host, so a 0.646B decoder costs 6.3x the 8.6B language model beside it. This is §11.4's last owed device row; the vocoder closed in §13 and the DiT in §14. §14.5 blocked it on a dtype rather than on the work: routing an `ArCompute::kBFloat16` decoder through an f32 `vt::MatmulBT` would silently drop the rounding every gated number was taken with. That reasoning is right and its conclusion has expired, because the row needs the bf16 `vt::MatmulBT`, which the op has always been. The oracle settles the dtype by declaring none. `MiniMaxMusic3RVQDepthDecoder` takes no `dtype` parameter and carries no `torch.float32` literal and no `.float()` call; its single cast is the down-cast `.to(query.dtype)`. The dtype comes from `load_components`, and `tools/oracle/music3_oracle.py` resolves the depth decoder to bf16 under both of its policies. The two fp32 promotions in the depth loop sit after the decoder, on logits, and already run on the host. §17.4 then predicted three sources for the arm's divergence and led with the accumulator. §17.4a records that the measurement puts it LAST: ~97% is rounding POLARITY inside two shared ops, and with those aligned the composed stage is BIT-IDENTICAL. So the blocker was never the dtype, and it is not the dtype now — it is that the seam cannot express the reference's rounding, which is #1322 and its own row. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5-1m [claude-code]
…t bf16, on five shared ops and no new kernel (#672, #1309) The arm §17 specifies. Weights and activations bf16 with f32 accumulation, which is `vt::MatmulBT`'s contract unmodified. The narrowing is exact rather than tolerated: `AtRuntimeDtype` already rounds every AR-half weight through bf16 into its f32 carrier and `Store(..., kBFloat16)` does the same to every activation crossing this boundary, so `F32ToBF16` here is the inverse of the loader's widening, value for value. No new kernel. `vt::MatmulBT`, `vt::RmsNorm`, `vt::AttentionCross`, `vt::SiluAndMul` and `vt::Add` all carry a CPU AND a CUDA provider, and the MLP's gate/up pair routes through `layers::UnquantizedMlpGateUpMethod` — the shared merged-GEMM seam — rather than a hand-rolled equivalent, so a quantized arm can inherit a fused scheme. `to_q|to_k|to_v` stage as one [3H, H] sweep. `vt::AttentionCross` is the right op by an identity: one query row against a cache of positions all at or before it, so a causal mask masks nothing. Because every op has a CPU provider, the gate drives the PRODUCTION `Music3DepthStage` on a CPU queue with no GPU and no checkpoint, and asserts WHICH ARM RAN through `Music3DepthDeviceForwardCount()` rather than inferring it from numbers the two arms agree on by design. Deleting the production device branch reds 1 case / 1 assertion. That is the leg #1131 records as missing for the DiT arm, and not repeating it is why the counter exists. What is NOT claimed: parity, and any throughput number. The numeric bound bounds the divergence §17.4a attributes; it does not prove agreement with the reference. The decisive gate is `test_minimax_music3_ar_real` with the device arm, it needs the 28.5 GB checkpoint, and it has not been run — so the Thor A/B is blocked on correctness rather than on the box. `docs/USAGE.md` moves the depth decoder from host to device in its stage table and says what the dtype is, why the narrowing is lossless, what does not move with it, and that no throughput number is quoted for it. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5-1m [claude-code]
The row branch was cut from f06b9e9. origin/main has since taken the CUDA-graph persistent step input and the record-anchor ratchet repair, neither of which touches any MiniMax-Music3 file, so the merge is clean and the row's gate is unaffected. Merged rather than rebased because the reviewed head 9dd5199 is published on #1330, and rewriting it would reset the pull request's CI approval. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5-1m [claude-code]
…re-place the tolerance on six seeds, and correct the RmsNorm divergence (#1309, #1131, #1351) A fresh review returned FAIL on four things. The port's algebra was not one of them -- the reviewer independently confirmed it exact -- and these are the repairs, plus one correction that is larger than the row. THE SELECTION. `Music3SelectDepthArm` replaces the engine's `if (queue_.device.type != kCPU) { ... }`. That condition is false on every runner CI owns, so a branch written at that line is the one line no gate can enter, which is #1131's shape reproduced by the change that names #1131 as its reason for existing. The review proved it: deleting the whole block left `test_minimax_music3_ar` 35/35 and `test_minimax_music3_speech` 9/9 green. The rule now lives in a function that runs on BOTH sides of the condition, so the gate drives it with a CPU queue -- which must stage nothing, keep the host arm and release nothing even at `release_host=true` -- and with a fabricated queue on each of five non-CPU device types, where the invariant is that the selection ENGAGES or REFUSES BY NAME. The third outcome, a quiet fall-back to the host loop, is the defect. Gutting the selector reds 1 case / 6 assertions. `kCUDA` is left out of that list deliberately: on a CUDA build without a device the staging would fail inside the CUDA runtime, and a call designed to fail latches a sticky error the next unrelated kernel reports as its own. What this does NOT close is stated in the spec rather than implied: deleting the engine's two-line CALL still leaves both suites green, because the engine needs the 28.5 GB checkpoint and a real device. §17.7 carries that as owed, naming row `MUSIC3-DEPTH-DEVICE` and #1131. THE TOLERANCE was fitted to one draw. Changing only the RNG seed of the reference weights reds the shipped `mean <= 4.0` on three of six equally valid seeds. Worse, `worst <= 512` cannot discriminate at all: a CORRECT arm reads worst 7340 at `0xDEADBEEF` while the gate/up half swap reads 6641 and the wrong attention scale 6865, so any `worst` bound admitting a correct implementation admits two structural defects. The case now measures six seeds and gates the MEDIAN, which is exactly 1 bf16 ULP at every seed against a tightest defect of 3. The mean stays gated at 15, inside a (9.904, 19.6) window that the spec states rather than hides. Five structural mutations red at 2 cases / 13-15 assertions each. Six seeds also exposed a defect in the metric. A reference value of exactly zero has no ULP, so one seed read mean 8.8e32 with the arm correct. Zero references now go in their own bucket, measured absolutely, and the two counts are asserted to SUM. THE RESIDENT DTYPE. #1131 offers two instruments, "invocation count OR resident dtype", and this row had taken only the first. The review widened one activation buffer to `kF32` and every case stayed green while the path moved twice the bytes. `Music3DepthDeviceResidentDtypes()` now reports a bit per dtype over every buffer the forward makes resident, read back by the gate rather than restated there. That mutation and the same widening of the K/V cache each red 1 case / 1 assertion. THE RmsNorm DIVERGENCE WAS MISDESCRIBED, and the correction outlives this row. §17.4 said `vt::RmsNorm` "drops a rounding" and is "WIDER than the reference". Verified at the parity pin `555967922`: `csrc/cpu/layernorm.cpp` computes `fp32_out = fp32_x * fp32_s_variance * fp32_w` and `csrc/libtorch_stable/layernorm_kernels.cu:93` computes `static_cast<scalar_t>(x * s_variance * w)`. Upstream landed the weight-dtype multiply as vllm#42379 and REVERTED it as vllm#46070, an ancestor of the pin. So `vt::RmsNorm` mirrors its reference exactly. The mistake was generalising a diffusers observation onto a shared op: AGENTS.md makes vLLM the only reference wherever it implements the behaviour, and it implements RMSNorm, so diffusers was never the mirror source for the op -- only for the depth decoder MODULE, which is what the HOST arm mirrors. Both arms are correct against the reference each answers to, and this term therefore does not close. #1322's surviving half is `vt::SiluAndMul`. Two smaller things. The K/V cache now draws from the device pool through `DBuf` rather than `backend.Alloc`/`Free`: it is a local of `Music3DepthStage`, so it is built and destroyed once per FRAME, and 16 raw `cudaMalloc` plus 16 synchronizing `cudaFree` per frame is what the pool exists to avoid. No speed number is quoted -- it is unmeasured, and the A/B is blocked on correctness. And #1351 is filed rather than fixed: `check-fusion-consistency.py` Check 2 runs its regex over `path.read_text()`, so a COMMENT mentioning `MlpGateUpMethod` exempts a TU from the check; a checker change needs its own spec and red-before evidence. The upstream anchor `normalization.py:600-606` was wrong by ~46 lines and landed on `GlobalResponseNorm`. It is `normalization.py::RMSNorm.forward:557-561`, in the class the decoder constructs. The copy in the append-only issue index stays. `docs/USAGE.md` drops the single-seed numbers it quoted as if they were the arm's deviation, states what is and is not gated, and stops calling the RmsNorm term a shortcoming of the shared op. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5-1m [claude-code]
`origin/main` took the ENG-EXPERT-STREAM-DEVICE scoping spec while this row's review repairs were in flight. It touches no MiniMax-Music3 file and no file this row's gate reads. The only shared path is the append-only `.agents/issue-index.md`, where both sides append and neither edits, which is what the `merge=union` driver on that path exists for; the appended rows are verified present exactly once below the merge. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5-1m [claude-code]
…-> 19 Two rows claimed `## 17` of `.agents/specs/minimax-music3.md` concurrently, which the append-at-the-tail shape of that file cannot detect until they meet. #1315's request-key contract LANDED there first, so it keeps the number, and this row's depth-decoder section renumbers. It becomes 19 rather than 18 because the open #1356 (`MUSIC3-VOCODER-CONV-SPEED`) already claims 18 on its branch; taking 18 here would have produced exactly the same collision one merge later. Resolved by taking main's section whole and re-applying this row's edit beside it, never by a three-way merge of the two. Main's own §17.4/§17.7 back-references at :3526, :3606 and :3670 point INSIDE its section and are untouched. The 20 references to this row's section in the header, the two model TUs, the test and the three not-yet-landed issue-index rows all move to §19; the index rows for #1315, #1336 and #1337 are main's and are untouched, as is every §17 in `multimodal-speed.md`, `kimi-linear.md` and `benchmark-record.md`, which are those specs' own section 17. Nothing else in the incoming range touches a MiniMax-Music3 file, so the row's measured evidence stands as taken. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: AGENT:claude-opus-5-1m [claude-code]
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.
Row
MUSIC3-DEPTH-DEVICE, issues #1309and #672; spec
.agents/specs/minimax-music3.md§19.MiniMax-Music3's 0.646 B RVQ depth decoder was 48.4 % of a run on
thor:gpu0-- 78.1 s of 161 s, the largest single term -- so a 0.646 B modelcost 6.3x the 8.6 B language model beside it on the same box, and the reason
was which processor each ran on. §16.4 measured the host kernel at ~4 bytes of
f32 weight traffic per multiply-accumulate: 2.28 GB per call, 8 calls a frame.
This row moves that sweep to the device and halves it. It is §11.4's last owed
device row, and §14.5 blocked it on a dtype rather than on the work.
The dtype, which is what the row was blocked on
The oracle settles it by declaring nothing.
MiniMaxMusic3RVQDepthDecodertakesno
dtypeparameter and contains notorch.float32literal and no.float()call (
diffusers@c6da9936,minimax_music3_rvq_depth_decoder.py:101-125); its single cast is the down-cast:51.to(query.dtype). The dtype is imposed byload_components(dtype=...),and
tools/oracle/music3_oracle.pyresolvesrvq_depth_decoder: torch.bfloat16under both policies. So: bf16 storage, f32 accumulation, which is exactly the
contract
vt::MatmulBThas carried all along. §14.5's objection -- that an f32vt::MatmulBTwould drop the bf16 rounding every gated number was taken with --is answered by using the bf16 one.
The narrowing is lossless rather than a rounding we tolerate:
AtRuntimeDtypealready rounds every AR-half tensor through bf16 into an f32 carrier, so
vt::F32ToBF16on those values is the exact inverse of the widening the loaderdid. A finding falls out that no gate here can see, and it is recorded rather
than fixed: the host arm keeps bf16-exact weights in
std::vector<float>, soevery golden, token gate and WAV hash passes while that path moves twice the
oracle's bytes (§19.2a).
No new kernel
Five shared ops, each already carrying a CPU and a CUDA provider:
vt::RmsNorm,vt::MatmulBT,vt::AttentionCross,vt::SiluAndMul,vt::Add.Two merges the seam asks for:
to_q | to_k | to_vas one[3H, H], andgate_proj | up_projas one[2I, H]consumed throughlayers::UnquantizedMlpGateUpMethodrather than a hand-rolled equivalent.The attention is causal upstream and
vt::AttentionCrossis still the right op,by an identity rather than a shortcut: the call presents one query row against
a cache of positions that are all at or before it, so a causal mask masks nothing.
What is measured, and what is not claimed
No parity claim and no speed number. The decisive gate is
test_minimax_music3_ar_realrun with the device arm, and it needs the 28.5 GBcheckpoint; until it runs,
AGENTS.mdforbids quoting a Thor A/B. The A/B isblocked on correctness, not on the box.
The four review findings, repaired
A fresh review returned FAIL. The port's algebra was not one of the findings --
the reviewer independently confirmed it exact.
The reachability leg this row cites #1131 for was open one level up
Two selections exist. The inner one, the
appendlambda, was gated and red. Theouter one, the engine's
if (queue_.device.type != kCPU) { ... }, was not:deleting the whole block -- the only thing
--speech-device 1reaches -- lefttest_minimax_music3_ar35/35 andtest_minimax_music3_speech9/9 green. That is#1131's shape reproduced by the change that names #1131 as its reason for
existing, and it is structural rather than careless: on a CPU-only runner that
condition can never be true.
The rule now lives in
Music3SelectDepthArm, which runs on both sides of thecondition and is therefore drivable from a CPU gate. The gate enters it with a CPU
queue -- which must stage nothing, keep the host arm, and release nothing even at
release_host=true-- and with a fabricated queue on each of five non-CPU devicetypes, where the invariant is that the selection engages or refuses by name.
The third outcome, a quiet fall-back to the host loop, is the defect.
kCUDAisexcluded from that list deliberately: on a CUDA build without a device the staging
would fail inside the CUDA runtime, and a call designed to fail latches a sticky
error that the next unrelated kernel reports as its own.
What this does not close is stated rather than implied. Deleting the engine's
two-line call still leaves both suites green, because the engine needs the
28.5 GB checkpoint and a real device. §19.7 carries that as owed, naming row
MUSIC3-DEPTH-DEVICEand #1131, and §19.5 records the mutation with the binaryhashes that prove it was not a stale-binary artefact.
The tolerance was fitted to one seed
Changing only the RNG seed of the reference weights -- same distribution, same
geometry, no defect -- reds the shipped
mean <= 4.0on three of six equallyvalid draws. Worse,
worst <= 512could not discriminate at all: a correct armreads worst 7340 at
0xDEADBEEFwhile the gate/up half swap reads 6641 and thewrong attention scale 6865, so any
worstbound loose enough to admit a correctimplementation admits two structural defects.
The case now measures six seeds and gates the median, which is exactly
1 bf16 ULP at every seed for the correct arm against a tightest defect of 3 --
a constant, not a distribution, so a redraw cannot move it. The mean stays gated
at 15 inside a
(9.904, 19.6)window that §19.4b states rather than hides.worstis reported and carries only a non-finite canary.Six seeds also exposed a defect in the metric itself: a reference value of exactly
zero has no ULP -- bf16's spacing there is the denormal floor -- so one seed read
mean 8.8e32 with the arm correct. Zero references now go in their own bucket,
measured absolutely, and the two counts are asserted to sum, so a defect
cannot hide by growing the un-gated bucket.
The tolerance was blind to a too-wide dtype, on a row whose thesis is a dtype
#1131 offers two instruments -- "invocation count or resident dtype" -- and
this row had taken only the first. Widening one activation buffer to
kF32leftevery case green while the path moved twice the bytes:
AGENTS.md's "a token gatecannot detect a dtype that is too wide" landing on this row exactly, and §19.2a's
own finding about the host arm arriving inside the arm that exists to fix it.
Music3DepthDeviceResidentDtypes()now reports a bit per dtype over every bufferthe forward makes resident, read back by the gate rather than restated there.
A stale upstream anchor
normalization.py:600-606was wrong by ~46 lines and landed onGlobalResponseNorm. At the pinneddiffusersc6da9936the two roundings arenormalization.py::RMSNorm.forward:557-561, in the class the decoder actuallyconstructs (
minimax_music3_rvq_depth_decoder.py:78,80,122). Corrected in theheader and both spec sites, with the symbol named. The copy in the append-only
.agents/issue-index.mdstays.A correction that outlives this row
While repairing the above, §19.4's own reason 3 turned out to be wrong. It said
vt::RmsNorm"deliberately drops a rounding" and that "the shared op is widerthan the reference". Verified directly at the parity pin
555967922:csrc/cpu/layernorm.cppcomputesfp32_out = fp32_x * fp32_s_variance * fp32_wand narrows once at
scalar_vec_t out(fp32_out);csrc/libtorch_stable/layernorm_kernels.cu:93computesstatic_cast<scalar_t>(x * s_variance * w);vllm#46070, which is an ancestor of the pin.
So
vt::RmsNormmirrors its reference exactly. The mistake was generalising adiffusersobservation onto a shared op:AGENTS.mdmakes vLLM the onlyreference wherever it implements the behaviour, and it implements RMSNorm, so
diffusers was never the mirror source for the op -- only for the depth decoder
module, which is what the host arm mirrors. Both arms are correct against the
reference each answers to, and this term of the band therefore does not close.
#1322's surviving half is
vt::SiluAndMul, whereF.silu(gate)really doesproduce a bf16 tensor before the multiply.
The mutation battery
Every run on a clean tree, with the compiler exit status,
git diff --statandthe binary
sha256printed, and the source restored and verified afterwards.q|k|vmerge order swappedMusic3SelectDepthArmguttedGate at the head of this branch:
test_minimax_music3_ar37 cases / 640assertions SUCCESS,
test_minimax_music3_speech9 / 223 SUCCESS,check-fusion-consistency.pyrc=0 over 16 glue TUs and 12 gated-MLP TUs.scripts/agent-preflight.sh --stagedis green excepttest_cpu_x86_llamacpp_floor, which is the load-dependent #618 red -- this difftouches no
scripts/file and the box was at loadavg 251.Two smaller things, and one filed
The K/V cache now draws from the device pool through
DBufrather thanbackend.Alloc/Free. It is a local ofMusic3DepthStage, so it is built anddestroyed once per frame, and 16 raw
cudaMallocplus 16 synchronizingcudaFreeper frame is what the pool exists to avoid. No speed number isquoted: it is unmeasured, and the A/B is blocked on correctness.
#1351 is filed rather than fixed:
check-fusion-consistency.pyCheck 2 runs_MERGED_GEMM_SEAMoverpath.read_text(), so a comment mentioningMlpGateUpMethodexempts a TU from the check. Reproduced by calling the checker'sown functions -- the hand-rolled variant with comments kept reads
uses_merged_gemm_seam = True, and flips toFalseonly once comments arestripped. A checker change needs its own spec, a red-before test and green-after
evidence, and widening the regex is what
AGENTS.mdforbids.One record collision, resolved and worth knowing about
Two rows claimed
## 17of.agents/specs/minimax-music3.mdat the same time.#1315's request-key contract landed there first, so it keeps the number and this
row's section renumbers to §19 -- not 18, because the open #1356
(
MUSIC3-VOCODER-CONV-SPEED) already claims 18 on its branch. Resolved by takingmain's section whole and re-applying this row's beside it, never by a three-way
merge; main's own back-references and every
§17inmultimodal-speed.md,kimi-linear.mdandbenchmark-record.mdare untouched, as are the index rowsfor #1315, #1336 and #1337.
The append-at-the-tail shape of that spec cannot detect this collision until the
two branches meet, which is worth recording: a section number is a shared key in
a file that has no lock.
What is still owed
The projection, audio embeddings, audio heads, CFG mix and top-k draw stay on the
host -- ~1.6 % of the stage today, and the stage's remainder once the forward
moves. The CUDA kernels themselves are unreached by any gate here, and so is the
engine's own call to
Music3SelectDepthArm: both needthor:gpu0and the 28.5 GBcheckpoint, and §19.6 records them with #1131 owning the switch leg. The DiT arm
one block below still carries in full the untestable shape this row just removed
from the depth arm, and #1131 still owns that too.
FOLLOWING_AGENTS_PROTOCOL
Following-Agents-Protocol: true
AI-Assisted: true
Assisted-by: AGENT:claude-opus-5-1m [claude-code]