Repository navigation
Conversation
Code review范围:2 文件、无运行时数据流(dump 脚本 + fixture)。逐文件认领:
case 覆盖:两 case 仅在 token(batch)轴变化(S=6/10),确认逐 token 点处理、无跨 token 泄漏;该模块无 seq_len 分支,故无需 #1150 那种 6-case 截断矩阵。唯一未变的是几何(config 级、非 per-case),属单几何 scope 的已知边界,非缺陷。 最小加固:修 M1(在 |
修改措施Pushed
复验:fixture 重生成(22 张量,torch 2.14.1+cpu / transformers 5.16.1 / CPU),provenance |
Address the family-level finding from the pegainfer-project#1153/pegainfer-project#1155 reviews: RMS_EPS feeds Config.rms_norm_eps but was missing from the config_digest canon, so editing eps changed the golden while the stored provenance stayed green. Canon now carries eps like the sibling scripts. Fixture regenerated (66 tensors): config_sha256 b9fd359a -> 20cb29e2; 59 tensors bitwise stable; the six case*_scores carry bf16 ULP-level drift (<=4.8e-4) from re-dumping under torch 2.14.1+cpu / transformers 5.16.1 instead of the original torch 2.5.1+cu121 environment. Second same-environment run is byte-identical; all six cases pass mask_ok and the reload assertions. Signed-off-by: bohaotu <bohaotu@pony.ai>
* feat(accuracy): add Qwen3.8-Flash-Next QSA indexer golden Dump the QSA token indexer reference (transformers 5.16.1 `qwen4_exp`) into a fixture the Stage 1 kernel gate replays: per-block scores + selected token indices for 6 cases, covering short/medium inputs, the budget-boundary truncation (block_topk reached with a shrunk budget) and the always-kept tail. The re-derivation is validated against the reference module's own mask in-dump. Signed-off-by: bohaotu <bohaotu@pony.ai> * fix(accuracy): stamp QSA indexer golden provenance + explicit score validity Address PR review: add `format_version` / `config_sha256` / `ref_transformers` so a consumer can assert the oracle's origin, and an explicit `scores_valid` mask so the NaN-padded block scores cannot be mis-consumed (no silent nan_to_num pass). L1 (real-budget truncation) kept as a known bound. Signed-off-by: bohaotu <bohaotu@pony.ai> * docs(accuracy): trim QSA indexer golden dump comments Drop references to local-only planning docs and tighten the docstring/comments. No behavior change; fixture is unaffected. Signed-off-by: bohaotu <bohaotu@pony.ai> * fix(accuracy): include norm eps in the QSA indexer golden config digest Address the family-level finding from the #1153/#1155 reviews: RMS_EPS feeds Config.rms_norm_eps but was missing from the config_digest canon, so editing eps changed the golden while the stored provenance stayed green. Canon now carries eps like the sibling scripts. Fixture regenerated (66 tensors): config_sha256 b9fd359a -> 20cb29e2; 59 tensors bitwise stable; the six case*_scores carry bf16 ULP-level drift (<=4.8e-4) from re-dumping under torch 2.14.1+cpu / transformers 5.16.1 instead of the original torch 2.5.1+cu121 environment. Second same-environment run is byte-identical; all six cases pass mask_ok and the reload assertions. Signed-off-by: bohaotu <bohaotu@pony.ai> * fix(accuracy): record the QSA indexer golden from the reference itself Signed-off-by: Feathbow <feathbow@gmail.com> * fix(accuracy): cover the real QSA budget boundary in the indexer golden Signed-off-by: Feathbow <feathbow@gmail.com> --------- Signed-off-by: bohaotu <bohaotu@pony.ai> Signed-off-by: Feathbow <feathbow@gmail.com> Co-authored-by: Feathbow <feathbow@gmail.com>
The fixture this branch adds was a pure recorder with no consumer. This is the consumer: `pegainfer-kernels/tests/qwen38fn_hc_block_parity.rs` replays `Qwen4ExpTextGatedResidual` (transformers 5.16.1 `qwen4_exp`) from `test_data/qwen38fn-hc-golden.safetensors` through the shared GPU surface at the real frozen geometry - hc_count 4, hidden_size 2560 (a 10240-wide stream), hc_lowrank 320, S = 6 and 10 - and compares all four recorded tensors: `normed`, `mixed`, `inject`, and the `use_combine=False` readout `mixed_only`. The grouped `(1 + w)` RMSNorm runs as four `rms_norm_batch_offset_into` launches, one per branch, because `rms_norm_batched_offset_cuda` pins `stride_input` to `hidden_dim` and so cannot address a branch of a row-major stream; the row-major layout the three cuBLAS projections read is assembled on device with plain `memcpy_dtod`, which moves bytes and computes nothing. If/when pegainfer-project#1158 lands `rms_norm_grouped_offset_cuda`, production switches to that single-launch grouped entry point; this gate's four-call form is bit-identical on that degenerate path. Three elementwise steps had no entry point, so they are added behind a new `qwen4` cargo feature that gates `csrc/qwen38fn/` out of default builds (`cargo check -p pegainfer-kernels` compiles none of the new .cu): `hc_silu_scaled_into`, `hc_sigmoid_affine_into` and `hc_mul_branch_combine_into`. Each lands its bf16 roundings where the reference lands them - the contract is documented on the kernels. `silu_f32` uses the tree's `x / (1 + expf(-x))` form, not `x * sigmoid(x)`, so it stays bitwise with the five other silus under `csrc/` and with the default-feature two-launch equivalent (`scale_bf16_in_place` then `silu_mul_batch_into` against ones), which this op fuses into one launch. All three wrappers validate extents with `checked_extent` before launching, and all three launchers bound their element counts with `dims_fit_i32` and form the grid in 64 bits. Measured on an RTX 5060 (sm_120, CUDA 13.2), 6/6 tests green. Worst element per tensor over both cases: `normed` 9.77e-4 with case 0's 61440 elements bit-identical, `mixed` 9.77e-3, `inject` 7.81e-3, `mixed_only` 9.77e-3 - one to two bf16 ulps, against a bound of abs 4.0e-2 + rel 4.0e-2, which is 4.1x the worst observed element on ATOL alone and 8.2x it at |want| = 1. Four negative controls guard the bound itself rather than the arithmetic: a sum instead of a mean over branches, a missing /hc_count, the silu/sigmoid pair swapped or dropped on either mix stage, and plain `w` for `(1 + w)`. Each is checked against exactly the recorded tensors it can move, and each has to fail on a large fraction of them - the tightest (sigmoid where the reference puts the SiLU) puts 5676 of 15360 `mixed` elements outside, the coarsest (plain-w norm) 98934 of 102400 `normed`. A control that still passes is a test failure, because a bound that absorbs a wrong block proves nothing. Provenance is asserted, not assumed, and needs no device: format_version, the transformers version, and the fixture's `config_sha256` against `pegainfer-qwen4/tests/frozen_config.json`, then hc_count / hidden_size / hc_lowrank / rms_norm_eps read back out of those same pinned bytes. A fixture from another checkpoint or another semantics fails here instead of being skipped. Ungated: the scatter half of the block, `mixed + inject * sublayer` folded back into the four-branch stream - the golden carries no sublayer output. `inject` is checked here, so the scatter's only per-branch coefficient is already held. Refs pegainfer-project#1153 Signed-off-by: bohaotu <bohaotu@pony.ai>
Code review对本 PR 消费端提交(
修改措施F1 已修。 三个 wrapper 都加了 F2 已修。 F3 已修(选定方案:保留单发射 kernel,把文档改成实话)。 模块文档( F4 已修。 两处一起收口: F5 已修(选定方案:不改名,只加一行说明)。 F6 已修。 ATOL 文档改成可从引用数字复算的表述( F7 已修。 F8 / F9 明确留给 #1108(本次不实现)。 F8:combine 内层那次 bf16 rounding 目前只被 F10 已写进 PR body。 见 PR body 的 F11 已修。 两处 #1158 的措辞都改成条件式( F12 遵守。 全程逐文件 本次落地的验证
仍未验证的:CI 上没有 job 开 |
|
This pull request has merge conflicts with main. Please rebase it so it can be reviewed and merged. |
Qwen3.8-Flash-Next's hyper-connection `hc_norm` reduces four 2560-wide branches of a 10240-wide stream independently and then scales by `(1 + w)` with `w` indexed by the row's *global* column. `rms_norm_batched_offset_cuda` cannot express that: `GemmaRMSNorm` dispatches `RMSNormKernel`, which loads `weight + elem` with no row term, so a reshape to `4 x tokens` rows of 2560 hands every branch branch 0's slice. `d` sets the reduction width and the weight span together, and the row stride is not a weight index, so no parameterisation of that kernel reaches the reference. What the reshape does get right is the reduction, so the fix is one kernel rather than a new operator family: `rms_norm_grouped_offset_cuda` reduces each `group_size`-wide group on its own and advances the weight base by the row's group index. Everything else is the vendor kernel's, so a call whose slices are all equal reproduces `rms_norm_batched_offset_cuda` bit for bit and `group_size == hidden_dim` stays on the vendor template. The PLE norms (`norm_key` / `norm_query` / `norm_conv`, also 10240 in four 2560-wide groups) are the second call site. The gate answers the question rather than asserting the conclusion: two of its five gates are negative controls, and both are decisive — the reshaped call is off by 1.0x of the run's largest magnitude on branches 1..3, the single 10240-wide reduction by 1.0x. The grouped call is bit-identical to the f64 CPU oracle on a dyadic input where the arithmetic allows it, and matches the stored hyper-connection fixture (PR pegainfer-project#1153, optional) bit for bit on its 6-token case. Signed-off-by: bohaotu <bohaotu@pony.ai>
…l golden Signed-off-by: bohaotu <bohaotu@pony.ai>
…erivation Re-derive the grouped (1+w) RMSNorm independently instead of delegating to module.hc_norm, so ungrouping the reference now turns the dump-time assertion red (it was a same-source comparison that stayed green). Add rms_norm_eps to config_sha256 so the provenance digest pins the norm eps, and assert both modules were built with the digested eps. Re-derive and finite-check the use_combine=False mixer readout (mixed_only), previously stored unguarded. Fixture regenerated: only config_sha256 changes (e3f5d32e -> 7a8402e4); every data tensor is bit-identical. Signed-off-by: bohaotu <bohaotu@pony.ai>
The hyper-connection gated residual is the residual block of the line and the mix GEMMs *are* the component under test, so nothing is shrunk and nothing is dropped: every dimension now comes from the real frozen checkpoint config (hc_count 4 -> a 10240-wide stream, hidden_size 2560, hc_lowrank 320) and all four module weights per variant are stored (two modules x norm + down + up + inject, ~25 MiB). The dump keeps the traps covered: the grouped (1+w) RMSNorm (group size 2560 on a 10240-wide input), the silu -> sigmoid two-stage mix gate, /hc_count on both the mix and inject paths, the mean (not sum) branch combine and the (0, 2) injection range. It records use_combine=True (mixed stream, un-normed hyper echo, injection weights) and use_combine=False (the top-level mixer readout). Metadata is a single sorted-JSON key (format_version 2), so two runs are byte-identical. The mix/injection formulas are re-derived from the module's own submodules and asserted equal bit-for-bit; the norm is reconstructed independently too. After writing, the fixture is reloaded and every stored output is reproduced bit-for-bit from the stored inputs and weights. Signed-off-by: bohaotu <bohaotu@pony.ai>
`verify()` reloaded the fixture and reproduced every stored output by re-implementing the reference math in torch (`grouped_norm` plus raw `F.linear` over the stored weights), purely to show the fixture reproduces itself. The stored values are captured from the reference module (`combine(hyper)` / `mixer(hyper)`), so the oracle never depended on that pass: the fixture is byte-identical without it. `--verify-only`, the `load_file` import (its only user) and the docstring's reload claim go with it. The in-run invariants stay, since they guard the oracle's semantics: the independently reconstructed grouped `(1+w)` norm, the re-derived mix and injection, the hyper-echo identity, the finiteness checks, the `(0, 2)` injection range and the mean-not-sum combine. No stored tensor was inert: all 19 are exercised by the reference's own weights and inputs. The docstring's "cheap and allowed here because it is elementwise" is wrong for the mix path (it re-runs the module's own `Linear` submodules), so it now says where the cheapness comes from. Signed-off-by: bohaotu <bohaotu@pony.ai>
Drop every assert and the re-derivation helpers that fed them (grouped_norm/branch_mix/inject_weights), the eps structural pins, the dead F import, and compress the docstring. The stored `normed` activation is now the reference's own `hc_norm` output instead of an independent reconstruction, so every stored value is the reference itself. The tensor set, geometry, cases, seed and metadata are unchanged; the fixture is byte-identical. Signed-off-by: bohaotu <bohaotu@pony.ai>
The fixture this branch adds was a pure recorder with no consumer. This is the consumer: `pegainfer-kernels/tests/qwen4_hc_block_parity.rs` replays `Qwen4ExpTextGatedResidual` (transformers 5.16.1 `qwen4_exp`) from `test_data/qwen38fn-hc-golden.safetensors` through the shared GPU surface at the real frozen geometry - hc_count 4, hidden_size 2560 (a 10240-wide stream), hc_lowrank 320, S = 6 and 10 - and compares all four recorded tensors: `normed`, `mixed`, `inject`, and the `use_combine=False` readout `mixed_only`. The grouped `(1 + w)` RMSNorm runs as four `rms_norm_batch_offset_into` launches, one per branch, because `rms_norm_batched_offset_cuda` pins `stride_input` to `hidden_dim` and so cannot address a branch of a row-major stream; the row-major layout the three cuBLAS projections read is assembled on device with plain `memcpy_dtod`, which moves bytes and computes nothing. If/when single-launch grouped entry point; this gate's four-call form is bit-identical on that degenerate path. Three elementwise steps had no entry point, so they are added behind a new `qwen4` cargo feature that gates `csrc/qwen4/` out of default builds (`cargo check -p pegainfer-kernels` compiles none of the new .cu): `hc_silu_scaled_into`, `hc_sigmoid_affine_into` and `hc_mul_branch_combine_into`. Each lands its bf16 roundings where the reference lands them - the contract is documented on the kernels. `silu_f32` uses the tree's `x / (1 + expf(-x))` form, not `x * sigmoid(x)`, so it stays bitwise with the five other silus under `csrc/` and with the default-feature two-launch equivalent (`scale_bf16_in_place` then `silu_mul_batch_into` against ones), which this op fuses into one launch. All three wrappers validate extents with `checked_extent` before launching, and all three launchers bound their element counts with `dims_fit_i32` and form the grid in 64 bits. Measured on an RTX 5060 (sm_120, CUDA 13.2), 6/6 tests green. Worst element per tensor over both cases: `normed` 9.77e-4 with case 0's 61440 elements bit-identical, `mixed` 9.77e-3, `inject` 7.81e-3, `mixed_only` 9.77e-3 - one to two bf16 ulps, against a bound of abs 4.0e-2 + rel 4.0e-2, which is 4.1x the worst observed element on ATOL alone and 8.2x it at |want| = 1. Four negative controls guard the bound itself rather than the arithmetic: a sum instead of a mean over branches, a missing /hc_count, the silu/sigmoid pair swapped or dropped on either mix stage, and plain `w` for `(1 + w)`. Each is checked against exactly the recorded tensors it can move, and each has to fail on a large fraction of them - the tightest (sigmoid where the reference puts the SiLU) puts 5676 of 15360 `mixed` elements outside, the coarsest (plain-w norm) 98934 of 102400 `normed`. A control that still passes is a test failure, because a bound that absorbs a wrong block proves nothing. Provenance is asserted, not assumed, and needs no device: format_version, the transformers version, and the fixture's `config_sha256` against `pegainfer-qwen4/tests/frozen_config.json`, then hc_count / hidden_size / hc_lowrank / rms_norm_eps read back out of those same pinned bytes. A fixture from another checkpoint or another semantics fails here instead of being skipped. Ungated: the scatter half of the block, `mixed + inject * sublayer` folded back into the four-branch stream - the golden carries no sublayer output. `inject` is checked here, so the scatter's only per-branch coefficient is already held. Refs pegainfer-project#1153 Signed-off-by: bohaotu <bohaotu@pony.ai>
e5fe036 to
4b6cf5d
Compare
Rebase已 rebase 到
源文件里的“兄弟 PR 取并集”段落已按现状改写: 重跑:gate 6 passed(0.27s),默认 feature 的 |
Summary
Reference golden for the hyper-connection gated residual of Qwen3.8-Flash-Next (#1103), and the on-device gate that consumes it.
tools/accuracy/dump_qwen38fn_hc_golden.py— recordsQwen4ExpTextGatedResidual(transformers 5.16.1qwen4_exp) at the real frozen-config geometry: for the combine block the grouped(1+w)normed stream, the mixed stream, the un-normed hyper echo and the injection weights; for the top-level readout theuse_combine=Falsemixer output (the readout — there is no final-norm tensor).test_data/qwen38fn-hc-golden.safetensors— 2 cases (S=6, S=10), 19 tensors.hidden_size2560,hc_count4 (10240-wide stream),hc_lowrank320 — with no weights dropped: all four weights of theuse_combine=Trueblock (norm + down + up + inject) and all three of theuse_combine=Falsemixer (norm + down + up; that variant builds no injection head) are stored, because the mix GEMMs are the component under test. The captured activations cover the grouped(1+w)RMSNorm (group size 2560 on a 10240-wide input), thesilu->sigmoidmix gate,/hc_counton both the mix and inject paths, the mean (not sum) combine and the(0, 2)injection range.pegainfer-kernels/tests/qwen4_hc_block_parity.rs— the consumer: replays the block on device against every recorded tensor, plus four negative controls that guard the tolerance itself. See Consumer below.Validation
The dump asserts nothing; it is a pure recorder. Everything it stores is the reference's own output (
Qwen4ExpTextGatedResidualforwards) at the real geometry — nothing is re-derived or re-checked here. The consumer gate is what replays these tensors against the kernel and asserts agreement.The dump side is fixture + script only. The consumer commit adds three feature-gated kernels, their FFI and their
opswrappers behind a newqwen4cargo feature; no default-feature code path changes (cargo check -p pegainfer-kernelscompiles none of the new.cu) and no model crate reads the new ops yet.Consumer
pegainfer-kernels/tests/qwen4_hc_block_parity.rsis the only reader oftest_data/qwen38fn-hc-golden.safetensors. It replaysQwen4ExpTextGatedResidual.forward—modeling_qwen4_exp.py:959-968in transformers 5.16.1 — on device through the shared GPU surface, at the real frozen geometry and both recorded sequence lengths, and compares every tensor the dump stored.Replay semantics against
modeling_qwen4_exp.py:959-968:959hc_norm(hyper_input)— grouped(1+w)RMSNorm,group_size = hidden_size2560 over a 10240-wide streamrms_norm_batch_offset_into, one per branch with that branch's own 2560-wide slice of the norm weight, then an on-devicememcpy_dtodassembly into the row-major streamnormed:960F.silu(input_mix_weight_down(n) / hc_count)gemm_into_checked(down,[320, 10240]) thenhc_silu_scaled_into(scale = 1/4)— the division is folded into the scale and is exact, becausehc_countis a power of two:961torch.sigmoid(input_mix_weight_up(g))gemm_into_checked(up,[10240, 320]) thenhc_sigmoid_affine_into(scale = 1, gain = 1):962-965unflatten to[S, 4, 2560],gate * normed,.mean(dim=-2)hc_mul_branch_combine_into(branches = 4, combine_scale = 1/4)— bf16 product per branch, f32 accumulate over branches, one cast outmixed:966-967use_combine=Falsebuilds no injection head and returnsmixed_inputalonemixer_norm/mixer_down/mixer_up), assertinginjectis absentmixed_only:9682 * torch.sigmoid(block_inject_weight(n) / hc_count)gemm_into_checked([4, 10240]) thenhc_sigmoid_affine_into(scale = 1/4, gain = 2)— the divisor and the gain are both exact powers of two, so the sigmoid's cast is the only roundinginjectThe three elementwise steps had no entry point, so they are added under the new
qwen4cargo feature:hc_silu_scaled_into,hc_sigmoid_affine_into,hc_mul_branch_combine_into, over the kernels inpegainfer-kernels/csrc/qwen4/qwen4_hc_block.cu. Each lands its bf16 roundings where the reference lands them — the contract is documented on the kernel bodies.silu_f32is the tree'sx / (1 + expf(-x)), notx * sigmoid(x), which keeps it bitwise with the five other silus undercsrc/and with the default-feature two-launch equivalent (scale_bf16_in_placethensilu_mul_batch_intoagainst a buffer of ones) that this op fuses into one launch. All three wrappers validate extents withchecked_extentbefore launching; all three launchers bound their element counts and form the grid in 64 bits.Measured
RTX 5060 (sm_120), CUDA 12.9.1 (dev image
pegainfer-dev:cu132) —test result: ok. 6 passed; 0 failed. Worst element per tensor over both cases:normedmixedinjectmixed_onlyThat is one to two bf16 ulps: one flipped rounding in a chain of seven (norm, down GEMM, SiLU, up GEMM, sigmoid, branch product, branch mean). Only two reductions can move it — the grouped norm's f32 sum of squares runs in FlashInfer's vector order where torch runs in its own, and cuBLAS accumulates the three projections in a different order than the torch 2.14.1+cpu bf16 matmul the fixture was dumped with.
Tolerance and its derivation. The bound is
abs(got - want) <= ATOL + RTOL * abs(want)withATOL = RTOL = 4.0e-2. Against the worst element any of the four tensors shows (9.77e-3):ATOLalone is 4.1x it (4.0e-2 / 9.77e-3), and at|want| = 1the whole bound is 8.0e-2, i.e. 8.2x it. Below|want| = 1theRTOLterm shrinks towardsATOL, so the ratio never falls under 4.1; above it the ratio only grows. Four-to-eight times the disagreement actually observed leaves room for a machine whose cuBLAS picks a different accumulation order and flips once more at every stage — and, per the controls below, no room for a wrong block.The 6 tests
fixture_provenance_is_the_frozen_confighyper_connection_block_matches_goldennegative_sum_over_branches_fails.mean(-2)is a mean, not a sumnegative_missing_hc_divisor_fails/hc_countis present on the mix path and on the injection weightsnegative_swapped_or_dropped_mix_activations_failnegative_plain_weight_norm_fails(1+w), not plainwThe four negative controls guard the tolerance itself, not the arithmetic. Each is checked against exactly the recorded tensors it can move — a control that also "failed" on a tensor it never touches would say nothing about whether the bound is tight, and one that failed on nothing would mean the control never reached the arithmetic — and a control that still passes is a test failure. Measured spread: the tightest, sigmoid where the reference puts the SiLU on the down output, moves
mixedby 4.96e-1 at its worst and puts 5676 of 15360 elements (37%) outside the bound; the coarsest, plainwfor(1+w), movesnormedby 4.84 and puts 98934 of 102400 outside. Every control fails on a large fraction of its elements rather than on a marginal few, so the bound discriminates rather than absorbs.Provenance
Asserted, not assumed, and it needs no device:
format_version == 2,transformers_version == 5.16.1, the recorded config path, and the fixture'sconfig_sha256held against the bytes ofpegainfer-qwen4/tests/frozen_config.json.hc_count,hidden_size,hc_lowrankandrms_norm_epsare then read back out of those same pinned bytes and held against the geometry the gate replays, so a config that moved under the fixture cannot pass on the hash alone. A fixture from another checkpoint or another semantics fails here instead of being skipped. The replay tests skip without a device;PEGAINFER_REQUIRE_GPU=1turns that into a failure.What remains ungated
The scatter half of the block.
modeling_qwen4_exp.py:1236-1237—hyper_input + (sublayer * injection_weights).flatten(-2), the fold of a sublayer output back into the 4-branch stream — is not replayed. The golden records this block's own inputs and outputs, not a layer's, so it carries no sublayer tensor.injectis checked here, so the scatter's only per-branch coefficient is already held; the fold itself waits on the composition work in [Model] Qwen3.8-Flash-Next: gated residual #1108.No CI job enables the
qwen4feature yet. The clippy/test matrix carries"",gemma4,qwen35andk3, so nothing in CI buildscsrc/qwen4/and the gate is manually run:Merge notes
qwen4 = []inpegainfer-kernels/Cargo.toml,is_qwen4_sourceinbuild.rs, thecsrc/qwen4/directory and thesrc/ffi/qwen4.rsmodule slot are added verbatim by the in-flight QSA PRs too. Whichever branch lands second takes the union: the feature flag, the gating arm and themod/pub uselines are identical, and the per-file.cu/.rsbodies do not overlap.rms_norm_batch_offset_intolaunches becauserms_norm_batched_offset_cudapinsstride_inputtohidden_dimand cannot address a branch of a row-major stream. If/when feat(kernels): add Qwen4's grouped (1+w) RMSNorm #1158 landsrms_norm_grouped_offset_cuda, production switches to that single-launch grouped entry point and the on-device assembly disappears with it; this gate's four-call form is bit-identical on that degenerate path, so the numbers above do not move when the switch happens.Refs #1103