Fix and support scalar and vector scale/bias symmetrically in Activation#8
Open
RuifengHua wants to merge 1 commit into
Open
Fix and support scalar and vector scale/bias symmetrically in Activation#8RuifengHua wants to merge 1 commit into
RuifengHua wants to merge 1 commit into
Conversation
Commit c38d790 ("NKI Lib 2026-04-13") added Activation with an under-developed, asymmetric scale/bias path: a raw (P, 1) tensor / TensorView scale was forwarded to nisa.activation as its 3D alloc_logical container (pdim, n_p_tiles, F) rather than the 2D (P, 1) view the instruction expects; a vector scale was resolved once outside the tile loop and asserted single-tile; and scale could be a scalar while bias could not. This resulted in a raw (P, 1) tensor scale crashing with a cryptic nisa shape error, multi-tile (P > pmax) vector scale being rejected outright, and no scalar-bias support -- even though nisa.activation supports all of these on NeuronCore-v3. Fix by handling scale and bias symmetrically through one path: normalize a raw tensor / TensorView into a TileStream (which resolves the correct per-tile (P, 1) view), resolve each term per tile inside the loop so a (P, 1) vector advances in lockstep with dst/src (multi-tile now works), accept a scalar for either term, and validate a vector term is a (P, 1) vector whose partition size and tile count match dst (a clear boundary error instead of a cryptic downstream one). Add integration tests covering the scale-kind x bias-kind grid, multi-tile vector scale/bias across partial last tiles, and the shape guard.
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
blas.Activation(and the compactblas.activation) computesdst = op(src * scale + bias).scaleandbiasare both per-partition terms thatnisa.activationbroadcasts(P, 1) -> (P, F)(each partition's single value is applied across that partition's whole row). The instruction supports, for either term, a scalar or a(P, 1)vector — and on NeuronCore-v3 a(P, 1)vector may span multiple partition tiles. The primitive, however, handled scale/bias with an under-developed, asymmetric path that under-delivered against the hardware in three ways:1. A raw
(P, 1)tensor /TensorViewscale crashed.Scale of type
nl.ndarray/TensorViewwas passed tonisa.activationunchanged. But a tensor allocated viaalloc_logical((P, 1), ...)has the container layout(pdim, n_p_tiles, F)=(P, 1, 1)— 3D, carrying the internaln_p_tilesdimension — whereasnisa.activationexpects a 2D(P, 1)view. Handed the 3D container, the broadcast produces the wrong element count and raisesValueError: shape mismatch. Only aTileStreamscale worked, becauseTileStream.get_tile().get_view()strips then_p_tilesdimension down to the 2D(P, 1)view. So the natural thing a caller has (a rawalloc_logicaltensor) was exactly the thing that failed — despite the type hint advertisingnl.ndarray/TensorViewas accepted.2. A multi-tile (
P > pmax) vector scale was rejected.The scale was resolved once, before the tile loop, and
__init__asserted the scaleTileStreamwas a single tile. So a(P, 1)scale withP > pmax(which must tile along the partition loop likedst/srcdo) hit the assert and was refused — even thoughbias, an identically-shaped(P, 1)term, was fetched inside the loop and tiled correctly. The scale path simply hadn't been generalized to the multi-tile case the way bias had.3.
biascould not be a scalar.scaleaccepted a scalarfloat, butbiaswas typed and handled as a tensor/TileStreamonly.nisa.activationaccepts a scalar bias on NeuronCore-v3, so this was a gratuitous asymmetry — a caller wantingop(src * scale + c)for a constantccould not express it directly.This PR makes
scaleandbiasfirst-class and symmetric — each may beNone, a scalarfloat, or a(P, 1)vector (rawnl.ndarray/TensorView/TileStream), single- or multi-tile — by handling both through one path: normalize a raw tensor /TensorViewinto aTileStream(resolving the correct 2D(P, 1)per-tile view), resolve each term per tile inside the loop (so a(P, 1)vector advances in lockstep withdst/src, enablingP > pmax), accept a scalar for either term, and validate a vector term is a(P, 1)vector whose partition size and tile count matchdst(a clear boundary error instead of a cryptic downstream one).A
(P, 1)per-partition scale/bias is a common ML operation — per-row / per-channel dequantization and RMSNorm-style scaling — andProutinely exceedspmax(long sequences, large hidden dims), which is exactly the multi-tile case (2) unblocks.One hardware asymmetry remains and is left to
nisa.activationto enforce (documented, not guarded): a vectorscalemust befloat32, whereas a vectorbiasmay be any supported dtype.Repro (before fix)
A
(P, 1)tensor scale — the natural thing a caller has fromalloc_logical— crashed:Test plan
Rewrote
test/integration/nkilib/experimental/primitives/blas/test_activation.py, verified in simulation mode (--test-mode simulation):rsqrt), and in-place(P, 1)vector scale and/or bias tiled in lockstep with the data; verified each row (including the partial last tile) gets its own scale/bias value(P, 1)vector matching the data partition dim is rejected at the primitive boundaryNote: validated in simulation only. Tolerances:
1e-4(fp32) /1e-2(bf16).By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.