Per-layer staged-image engine for the eleven translated denoiser Swin layers: descriptors (verified offsets per phase), cut recipes that splice an isolated native phase into NVIDIA's PTX, wave templates, epilogue notes and GPU probes. The isolated phase-8 splice produces a byte-identical final 4K frame but is slower than the translated body (14.90 vs 6.59 ms) - a correctness experiment, not a shipping improvement; docs/rr-engine.md records the design and the next direct-converter win (B operands built from transposed accumulators).
387 lines
20 KiB
Plaintext
387 lines
20 KiB
Plaintext
// Native replacement for the Ray Reconstruction denoiser's third Swin decoder,
|
|
// cuda_dldn_engine_swin_dec3_kernel (nvngx_dlssd.dll 310.7).
|
|
//
|
|
// ---------------------------------------------------------------------------
|
|
// STATE: phases 1 is implemented; phases 2-48 are not. Phase 1 does NOT yet reproduce
|
|
// the oracle's payload: against the dec3 `p1` stage the frame-1 payload differs in
|
|
// every one of the 96 accumulators of every lane of all 64 slots, so this layer's
|
|
// image does not match the translated reference (arg112 differs, arg120 is still ok).
|
|
// The store plumbing itself is proven correct -- the `a` stage, which carries the same
|
|
// 60 A fragments and 16 seeds the cut hands over, is byte-identical (md5
|
|
// 6711adc5517ea8d6ce16d908946189e4 both sides). What phase 1 still gets wrong is the
|
|
// mma's cross-lane structure: an m16n8k32 lane's B fragment holds one column's k
|
|
// slice, so D[row g][cols 2t,2t+1] needs a reduction across the eight column groups,
|
|
// not a butterfly within the four-lane quad. ZLUDA's fallback_mma_sync_aligned
|
|
// (zluda_ptx_impl.cpp:1226) does that with dpp and ds_bpermute; see the comment above
|
|
// phase1() for what has been established so far. Phases 2-48's accumulators are
|
|
// written back as lane-distinct placeholders, so the kernel runs and is served.
|
|
//
|
|
// The kernel is four waves per block (blockDim (32, 1, 4)); %tid.z is the slab index
|
|
// that scales the input stride, the weight-image stride and the bias stride. The PTX
|
|
// this file replaces is ~/.cache/d4r-rr-corpus/0020-PREPASS_ENTRYPOINT_NAME.ptx, whose
|
|
// entry opens at file line 1016 and whose mma region is statements s543-s19381 (file
|
|
// lines 1658-46565).
|
|
//
|
|
// kernels/rr/swin_dec3_recipe.py (D4R_RRSWIN_CUT, the default) keeps NVIDIA's texture
|
|
// prologue (file lines 1-1657) and surface epilogue (46566-end) and replaces the 1112
|
|
// mma of the 48 phases with the call below. The accumulators cross the cut through
|
|
// shared memory, because ZLUDA lowers a multi-value C++ return to LLVM's function return
|
|
// type and never forms a callee-side .param-space object.
|
|
//
|
|
// Linked into ZLUDA's compile of that PTX (D4R_ZLUDA_EXTRA_BC), so tex_common.h's
|
|
// helpers and ZLUDA's own device functions are available. Compiled with -nogpuinc,
|
|
// so only plain clang builtins and attributes are used.
|
|
// ---------------------------------------------------------------------------
|
|
#include "../tex/tex_common.h"
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Parameter block. Fields are addressed by their byte offset in the PTX, so the
|
|
// raw image is kept and read through a typed accessor.
|
|
struct DldnSwinDec3Params
|
|
{
|
|
uint8_t raw[144];
|
|
};
|
|
static_assert(sizeof(DldnSwinDec3Params) == 144, "parameter block");
|
|
|
|
enum : int
|
|
{
|
|
kWeights = 40, // NVIDIA's prepared weight image (arg040), read-only
|
|
kPlaneArg = 48, // plane arena pointer (arg048)
|
|
};
|
|
|
|
#define DEVF __attribute__((device)) static inline
|
|
typedef uint32_t lds_u32 __attribute__((address_space(3)));
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// e4m3 code -> f32, exact. The mma's e4m3 operands reach the FMA path through
|
|
// ZLUDA's exact e4m3 -> f16 widening, so this decode is the same value.
|
|
DEVF float e4m3_to_f32(uint32_t code)
|
|
{
|
|
const uint32_t mag = code & 0x7fu;
|
|
const uint32_t bits = mag >= 8u
|
|
? ((((mag >> 3) + 120u) << 23) | ((mag & 7u) << 20))
|
|
: __builtin_bit_cast(uint32_t, (float)mag * 0x1p-9f);
|
|
return __builtin_bit_cast(float, bits | ((code & 0x80u) << 24));
|
|
}
|
|
|
|
// warp butterfly over the four lanes that hold a row's 32 columns (laneid ^ 1 and
|
|
// laneid ^ 2): the two shfl.sync.bfly steps the PTX's reductions perform.
|
|
DEVF uint32_t bfly1_bits(uint32_t v)
|
|
{
|
|
return (uint32_t)__builtin_amdgcn_update_dpp(0, (int)v, 0xB1, 0xf, 0xf, false);
|
|
}
|
|
|
|
DEVF uint32_t bfly2_bits(uint32_t v)
|
|
{
|
|
return (uint32_t)__builtin_amdgcn_update_dpp(0, (int)v, 0x4E, 0xf, 0xf, false);
|
|
}
|
|
|
|
// word v as the lane t^m of its group of four holds it, m = 0..3
|
|
DEVF uint32_t bfly_m(uint32_t v, int m)
|
|
{
|
|
return m == 0 ? v : (m == 1 ? bfly1_bits(v) : (m == 2 ? bfly2_bits(v) : bfly1_bits(bfly2_bits(v))));
|
|
}
|
|
|
|
// the low f16 of a packed pair (column 2t)
|
|
DEVF half_t half_lo(uint32_t pair)
|
|
{
|
|
return __builtin_bit_cast(half_t, (uint16_t)pair);
|
|
}
|
|
|
|
// the high f16 of a packed pair (column 2t+1)
|
|
DEVF half_t half_hi(uint32_t pair)
|
|
{
|
|
return __builtin_bit_cast(half_t, (uint16_t)(pair >> 16));
|
|
}
|
|
|
|
// f16 bits -> f32
|
|
DEVF float h2f(uint32_t bits)
|
|
{
|
|
return (float)__builtin_bit_cast(half_t, (uint16_t)bits);
|
|
}
|
|
|
|
DEVF uint32_t pair_of(half_t a, half_t b)
|
|
{
|
|
return (uint32_t)__builtin_bit_cast(uint16_t, a)
|
|
| ((uint32_t)__builtin_bit_cast(uint16_t, b) << 16);
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// The mma's fragment layouts, which are fixed by the instruction (they are not
|
|
// NVIDIA's choice, so they are the same for every layer):
|
|
// A reg q, byte j -> row g + 8*(q&1), k = 16*(q>>1) + 2t + (j&1) + 8*((j>>1)&1)
|
|
// B byte b -> column g, k = 16*(b>>3) + 2t + (b&1) + 8*((b>>1)&1)
|
|
// C/D reg 0 -> row g, columns 2t, 2t+1; reg 1 -> row g+8, same columns
|
|
// with g = laneid >> 2 and t = laneid & 3. A lane therefore holds 8 of the 32 k's of
|
|
// each of two rows, and 8 of the 32 k's of one column of the B fragment; the four
|
|
// lanes t = 0..3 hold the four disjoint eighths, so the two butterfly steps bring the
|
|
// other three in. Because both operands' k index depends on t in the same way, one
|
|
// butterfly index pairs an A byte with the B byte that sits at the same k.
|
|
//
|
|
// The 16 and the 8 are not interchangeable. ZLUDA splits NVIDIA's m16n8k32 e4m3 mma
|
|
// into two m16n8k16 f16 mmas (`e4m3_a_k16_fragment`, zluda_ptx_impl.cpp:1390): the
|
|
// first takes A registers {x, y} and B register x, the second takes {z, w} and B
|
|
// register y. So register q covers the k range 16*(q>>1), and inside a register the
|
|
// byte pair j >> 1 selects the +8 block. An earlier revision of this comment had the
|
|
// two swapped, which permuted the k's of both operands.
|
|
//
|
|
// That split is also why the rounding is not a single one at the end. The two halves
|
|
// are separate mma instructions, so `pack_f16_accumulator` rounds to f16 after the
|
|
// first half and again after the second:
|
|
//
|
|
// t = f16(dot(A[0..1], B[0..3]) + C)
|
|
// D = f16(dot(A[2..3], B[4..7]) + t)
|
|
//
|
|
// Each dot runs in f32 (ZLUDA's `dot_product<float, f16x2>`), and every e4m3 product
|
|
// is exact in f32, so a plain f32 accumulation of one half's k's reproduces it.
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Phase 1 (file lines 1658-3334, 240 mma).
|
|
//
|
|
// The 240 mma are 24 groups of ten. Group g uses A fragments 5*(g/8) + i and B
|
|
// fragments 10*(g%8) + 2i + c, i = 0..4 (the k-slices, K = 160) and c in {0,1} (the
|
|
// two n-tiles of the group's B block), chained through C: the first step of each chain
|
|
// is seeded with bias[g%8][c], every later step with the previous step's f16 result.
|
|
// So the phase is 48 output tiles of 16x8 (3 m-blocks x 16 n-tiles) over K = 160.
|
|
//
|
|
// The weight image holds phase 1's B block at [0, 20480) relative to W + z*20480:
|
|
// forty 512-byte tiles, tile t = 5*(g%8) + i covering sixteen columns (n = 0..7 for
|
|
// c = 0, n = 8..15 for c = 1) and k = 0..31, one lane's 16 bytes at +16*laneid. That
|
|
// is the `ld.weak.global.ca.v4.u32 {..}, [rd9 + 512*t]` chain of the prologue with
|
|
// rd9 = W + z*20480 + 16*laneid (file line 1471).
|
|
|
|
// one output tile: the two f16x2 pairs the mma writes, [m-block][n-tile][row half]
|
|
DEVF void phase1(const uint32_t as[60], const uint8_t* w, const uint32_t seeds[16],
|
|
int z, uint32_t lane, uint32_t d[3][16][2])
|
|
{
|
|
#pragma unroll
|
|
for (int nb = 0; nb < 8; ++nb) {
|
|
#pragma unroll
|
|
for (int c = 0; c < 2; ++c) {
|
|
const uint32_t seed = seeds[2 * nb + c];
|
|
#pragma unroll
|
|
for (int mt = 0; mt < 3; ++mt) {
|
|
// C of the first k-step: the seed u32 duplicated into both f32 halves,
|
|
// i.e. the lane's two columns of both rows start at the same f16 pair
|
|
half_t prev[2][2];
|
|
for (int col = 0; col < 2; ++col) {
|
|
const half_t v = col == 0 ? half_lo(seed) : half_hi(seed);
|
|
prev[0][col] = v;
|
|
prev[1][col] = v;
|
|
}
|
|
#pragma unroll
|
|
for (int i = 0; i < 5; ++i) {
|
|
const int f = 10 * nb + 2 * i + c; // B fragment index
|
|
const uint32_t* a = as + 4 * (5 * mt + i);
|
|
// tile f>>1, half f&1 (bytes 8..15 of the lane's 16), at +16*laneid
|
|
const uint8_t* tile = w + (uint32_t)z * 20480u + 512u * (uint32_t)(f >> 1)
|
|
+ 16u * lane + 8u * (uint32_t)(f & 1);
|
|
const uint32_t b0 = *(const uint32_t*)(tile);
|
|
const uint32_t b1 = *(const uint32_t*)(tile + 4);
|
|
// ZLUDA runs this mma as two m16n8k16 f16 mmas, so each half's
|
|
// dot product is accumulated and then rounded to f16 on its own.
|
|
// A register q covers k + 16*(q>>1) and byte j within it selects
|
|
// the +8 block, so half kg uses A registers 2*kg+0 and 2*kg+1 and
|
|
// B bytes 4*kg..4*kg+3.
|
|
float acc[2][2];
|
|
#pragma unroll
|
|
for (int col = 0; col < 2; ++col) {
|
|
acc[0][col] = (float)prev[0][col];
|
|
acc[1][col] = (float)prev[1][col];
|
|
}
|
|
#pragma unroll
|
|
for (int kg = 0; kg < 2; ++kg) {
|
|
float dot[2][2] = {{0.0f, 0.0f}, {0.0f, 0.0f}};
|
|
// butterfly index m brings in the lane whose t is t^m, which
|
|
// holds the k's 2*(t^m), 2*(t^m)+1, 8+2*(t^m), 8+2*(t^m)+1
|
|
#pragma unroll
|
|
for (int m = 0; m < 4; ++m) {
|
|
const uint32_t a0 = bfly_m(a[2 * kg + 0], m); // row g
|
|
const uint32_t a1 = bfly_m(a[2 * kg + 1], m); // row g+8
|
|
const uint32_t bb = bfly_m(kg == 0 ? b0 : b1, m);
|
|
#pragma unroll
|
|
for (int j = 0; j < 4; ++j) {
|
|
const float bv = e4m3_to_f32((bb >> (8 * j)) & 0xffu);
|
|
dot[0][0] += e4m3_to_f32((a0 >> (8 * j)) & 0xffu) * bv;
|
|
dot[0][1] += e4m3_to_f32((a1 >> (8 * j)) & 0xffu) * bv;
|
|
}
|
|
}
|
|
// Both k16 halves accumulate into the same f32 accumulator and
|
|
// the pair rounds to f16 once. ZLUDA's scalar fallback rounds
|
|
// between the halves, but the gfx12 native-FP8 path the runtime
|
|
// actually takes does not -- it is two chained f32-accumulating
|
|
// WMMAs, exactly what the family's k32_e4m3() in tex_common.h
|
|
// reproduces.
|
|
#pragma unroll
|
|
for (int col = 0; col < 2; ++col) {
|
|
acc[0][col] += dot[0][col];
|
|
acc[1][col] += dot[1][col];
|
|
}
|
|
}
|
|
#pragma unroll
|
|
for (int col = 0; col < 2; ++col) {
|
|
prev[0][col] = (half_t)acc[0][col];
|
|
prev[1][col] = (half_t)acc[1][col];
|
|
}
|
|
}
|
|
d[mt][2 * nb + c][0] = pair_of(prev[0][0], prev[0][1]);
|
|
d[mt][2 * nb + c][1] = pair_of(prev[1][0], prev[1][1]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Ground truth for a stage comes from kernels/rr/swin_dec3_recipe.py's oracle recipe:
|
|
// with D4R_RRSWIN_DEBUG_DEC3=p1 the same splice keeps NVIDIA's whole body and stores
|
|
// that stage's registers into the plane arena at +D4R_RRSWIN_DEBUG_BASE, which the
|
|
// kernel never otherwise writes, and the launch dump then holds the translated values
|
|
// for a block/lane. Built with -DD4R_RRSWIN_DEBUG_STAGE=<n> (kernels/build.sh rrswin,
|
|
// D4R_TEX_CFLAGS) and the same -DD4R_RRSWIN_DEBUG_BASE the oracle build used, this body
|
|
// stores the same registers of its own phase n in the same order, so the two dumps
|
|
// compare byte for byte.
|
|
#ifndef D4R_RRSWIN_DEBUG_STAGE
|
|
#define D4R_RRSWIN_DEBUG_STAGE 0
|
|
#endif
|
|
|
|
#ifndef D4R_RRSWIN_DEBUG_BASE
|
|
#define D4R_RRSWIN_DEBUG_BASE 27000000
|
|
#endif
|
|
|
|
DEVF void debug_store(const uint8_t* prm, const uint32_t* regs, int n)
|
|
{
|
|
const uint32_t cx = __builtin_amdgcn_workgroup_id_x();
|
|
const uint32_t cy = __builtin_amdgcn_workgroup_id_y();
|
|
const uint32_t cz = __builtin_amdgcn_workitem_id_z();
|
|
// the oracle's slots cover ctaid.x < 8, ctaid.y < 2 and all four waves of the block
|
|
if (cx >= 8u || cy >= 2u || cz >= 4u) {
|
|
return;
|
|
}
|
|
const uint64_t arena = *(const uint64_t*)(prm + kPlaneArg);
|
|
// the oracle's slot, swin_dec3_recipe.py's debug_slot: 32 lanes x n x 4 bytes, at
|
|
// least 8192, rounded up to a power of two (its splice forms block*slot with a shift)
|
|
const uint64_t want = (uint64_t)n * 128ull > 8192ull ? (uint64_t)n * 128ull : 8192ull;
|
|
uint64_t slot = 1;
|
|
while (slot < want) {
|
|
slot <<= 1;
|
|
}
|
|
const uint64_t block = ((uint64_t)cy * 8ull + cx) * 4ull + cz;
|
|
// swin_dec3_recipe.py's DEBUG_BASE, which reads D4R_RRSWIN_DEBUG_BASE and defaults
|
|
// to the tail described there; pass the same value to both builds or the two sides
|
|
// store to different places. dec3's plane arena is the tail of the 65,945,600-byte
|
|
// scratch block all eleven denoiser layers share: arg048 is at block offset
|
|
// 35,799,040, so the arena is [35,799,040, 65,945,600) and the family default of
|
|
// 46,000,000 falls outside the block entirely. A payload must also clear the other
|
|
// layers' sub-buffers (the replay manifests list block offsets 0, 30146560,
|
|
// 35799040, 36387840, 36535040, 37683200, 39567360, 45219840 and 60293120) as well
|
|
// as dec3's own inputs at 37683200 and 39567360.
|
|
const uint64_t debug_base = D4R_RRSWIN_DEBUG_BASE;
|
|
uint32_t* dst = (uint32_t*)(arena + debug_base + block * slot
|
|
+ (uint64_t)lane_id() * (uint64_t)n * 4ull);
|
|
for (int i = 0; i < n; ++i) {
|
|
dst[i] = regs[i];
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Mirror cut. The fifteen 16-row A fragments arrive in the order the mma reads them
|
|
// (fragment f is the four consecutive registers %r<base>+0..3 of A_BASES in
|
|
// swin_dec3_recipe.py), then phase 1's sixteen bias seeds (%r2453 %r2463 ... %r3163,
|
|
// read from W + z*256 + ((laneid<<2)&12) + 81920 + 16*i at file lines 1633-1648), the
|
|
// weight image and the parameter block.
|
|
//
|
|
// The four accumulators phase 48's epilogue reads cross the cut through the shared
|
|
// scratch, not `call.uni`'s return parameters: `scratch` is the base of the entry's
|
|
// only shared array (12800 bytes, dead across the cut -- E48 re-materialises it), and
|
|
// the four values sit 16 bytes apart per lane.
|
|
//
|
|
// Address space 3 (the shared scratch) explicitly: a generic store lowers to flat,
|
|
// which faults.
|
|
extern "C" __attribute__((device)) void d4r_swin_dec3(
|
|
uint32_t a0, uint32_t a1, uint32_t a2, uint32_t a3, uint32_t a4, uint32_t a5, uint32_t a6,
|
|
uint32_t a7, uint32_t a8, uint32_t a9, uint32_t a10, uint32_t a11, uint32_t a12, uint32_t a13,
|
|
uint32_t a14, uint32_t a15, uint32_t a16, uint32_t a17, uint32_t a18, uint32_t a19, uint32_t a20,
|
|
uint32_t a21, uint32_t a22, uint32_t a23, uint32_t a24, uint32_t a25, uint32_t a26, uint32_t a27,
|
|
uint32_t a28, uint32_t a29, uint32_t a30, uint32_t a31, uint32_t a32, uint32_t a33, uint32_t a34,
|
|
uint32_t a35, uint32_t a36, uint32_t a37, uint32_t a38, uint32_t a39, uint32_t a40, uint32_t a41,
|
|
uint32_t a42, uint32_t a43, uint32_t a44, uint32_t a45, uint32_t a46, uint32_t a47, uint32_t a48,
|
|
uint32_t a49, uint32_t a50, uint32_t a51, uint32_t a52, uint32_t a53, uint32_t a54, uint32_t a55,
|
|
uint32_t a56, uint32_t a57, uint32_t a58, uint32_t a59, uint32_t c0, uint32_t c1, uint32_t c2,
|
|
uint32_t c3, uint32_t c4, uint32_t c5, uint32_t c6, uint32_t c7, uint32_t c8, uint32_t c9,
|
|
uint32_t c10, uint32_t c11, uint32_t c12, uint32_t c13, uint32_t c14, uint32_t c15,
|
|
const uint8_t* w, const uint8_t* prm, uint32_t scratch)
|
|
{
|
|
const uint32_t as1[60] = {a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11,
|
|
a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23,
|
|
a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35,
|
|
a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47,
|
|
a48, a49, a50, a51, a52, a53, a54, a55, a56, a57, a58, a59};
|
|
const uint32_t seeds[16] = {c0, c1, c2, c3, c4, c5, c6, c7,
|
|
c8, c9, c10, c11, c12, c13, c14, c15};
|
|
// The epilogue's four accumulators: %r26071, %r26072, %r26081 and %r26082 -- phase
|
|
// 48's two mma D pairs. Address space 3 explicitly: a generic store lowers to flat,
|
|
// which faults.
|
|
__attribute__((address_space(3))) uint32_t* out =
|
|
((__attribute__((address_space(3))) uint32_t*)(uintptr_t)scratch) + 4 * (uint32_t)lane_id();
|
|
const uint32_t lane = lane_id();
|
|
const int z = (int)__builtin_amdgcn_workitem_id_z();
|
|
|
|
// ---- phase 1 -----------------------------------------------------------
|
|
uint32_t d1[3][16][2]; // [m-block][n-tile][row half]: f16 pair
|
|
phase1(as1, w, seeds, z, lane, d1);
|
|
#if D4R_RRSWIN_DEBUG_STAGE == 1
|
|
{
|
|
// the oracle's stage p1: the 48 chain finals in mma order (group major, the two
|
|
// n-tiles of the group minor), each the (row g, row g+8) pair the mma writes
|
|
uint32_t regs[96];
|
|
int i = 0;
|
|
for (int g = 0; g < 24; ++g) {
|
|
for (int c = 0; c < 2; ++c) {
|
|
regs[i++] = d1[g / 8][2 * (g % 8) + c][0];
|
|
regs[i++] = d1[g / 8][2 * (g % 8) + c][1];
|
|
}
|
|
}
|
|
debug_store(prm, regs, 96);
|
|
}
|
|
#endif
|
|
#if D4R_RRSWIN_DEBUG_STAGE == 100
|
|
{
|
|
// the oracle's stage a: the A fragments and C seeds as the mma reads them
|
|
uint32_t regs[76];
|
|
for (int i = 0; i < 60; ++i) {
|
|
regs[i] = as1[i];
|
|
}
|
|
for (int i = 0; i < 16; ++i) {
|
|
regs[60 + i] = seeds[i];
|
|
}
|
|
debug_store(prm, regs, 76);
|
|
}
|
|
#endif
|
|
|
|
// ---- handing the accumulators back ------------------------------------
|
|
// %r26071/%r26072 and %r26081/%r26082 are phase 48's two mma D pairs (E48 stores
|
|
// them into the shared array at (48*y + 6*x) and softmaxes five taps of it).
|
|
// Phase 48 is not implemented yet; until it is, fill the four slots with
|
|
// lane-distinct values so the read-back path is observable without wild magnitudes.
|
|
for (int i = 0; i < 4; ++i) {
|
|
out[i] = (uint32_t)lane + 32u * (uint32_t)i;
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Weight image layout of phase 1 (byte offsets from the PTX's own loads; the phase
|
|
// reads [0, 20480) of W + z*20480, forty 512-byte tiles):
|
|
// tile t = 5*(g%8) + i, half 0 = n 0..7 (c = 0), half 1 = n 8..15 (c = 1), k = 0..31
|
|
// byte j of a lane's 16-byte tile -> (k, n) at
|
|
// base + 512*(n>>4) + 64*(n&7) + 16*((k>>1)&3) + 8*((n>>3)&1)
|
|
// + 4*((k>>3)&1) + 2*((k>>4)&1) + (k&1)
|
|
// with base = W + z*20480 + 512*t + 16*laneid + 8*(f&1). phase1() reads that layout
|
|
// directly through the lane's own two registers instead of recomputing the address.
|
|
//
|
|
// Fragment maps (fixed by the mma instruction, see the comment above phase 1):
|
|
// A reg r, byte j -> row g + 8*(r&1), k = 8*(r>>1) + 2t + (j&1) + 16*((j>>1)&1)
|
|
// B byte b -> column g, k = 8*(b>>3) + 2t + (b&1) + 16*((b>>1)&1)
|
|
// C/D reg 0 -> row g, cols 2t, 2t+1; reg 1 -> row g+8, same cols
|
|
// A plain f32 dot product over K plus C, rounded to f16 once, reproduces the
|
|
// translated kernel's output exactly, so the phases need no operand-layout gymnastics
|
|
// and no WMMA intrinsics: every GEMM here is exact in f32 before the f16 rounding. |