Skip to content

Commit 772cf98

Browse files
committed
merge: pr/343 -- GDN gated-delta recurrence (kGdnPrefill/kGdnDecode), family 4 (#343)
rocm_gdn_scan.hip: the gated-delta recurrence itself, the op the whole slice exists to reach, in both its prefill and decode forms. Fourth of the stacked gfx1100 GDN slice. New ROCm-only TU under VLLM_CPP_HIP, additive registrations, additive cross-device cases; no existing backend's path changes. Conflicts resolved by union (additive vs additive). Refs #343, #41. FOLLOWING_AGENTS_PROTOCOL Following-Agents-Protocol: true AI-Assisted: true Assisted-by: Claude:claude-opus-5 [ClaudeCode]
2 parents d6f7876 + b6ab938 commit 772cf98

6 files changed

Lines changed: 342 additions & 3 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1328,6 +1328,7 @@ if(VLLM_CPP_HIP)
13281328
src/vt/rocm/rocm_gdn_state.hip
13291329
src/vt/rocm/rocm_gdn_conv.hip
13301330
src/vt/rocm/rocm_gdn_postconv.hip
1331+
src/vt/rocm/rocm_gdn_scan.hip
13311332
src/vt/rocm/rocm_ops.hip)
13321333
if(VLLM_CPP_HIP_ARCHITECTURES)
13331334
set_source_files_properties(
@@ -1346,6 +1347,7 @@ if(VLLM_CPP_HIP)
13461347
src/vt/rocm/rocm_gdn_state.hip
13471348
src/vt/rocm/rocm_gdn_conv.hip
13481349
src/vt/rocm/rocm_gdn_postconv.hip
1350+
src/vt/rocm/rocm_gdn_scan.hip
13491351
src/vt/rocm/rocm_ops.hip
13501352
PROPERTIES HIP_ARCHITECTURES "${VLLM_CPP_HIP_ARCHITECTURES}")
13511353
endif()

docs/FEATURES.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ CPU elementwise GEMM (f32/f16/bf16) runs AVX2 and AVX-512 tiers on x86 where the
307307
| LoRA end to end | CPU brick landed | Unwired standalone; not usable through the server |
308308
| Multimodal over HTTP | Image request path wired; forward + codec pending | `ROAD-V1-MM` W1-W3 landed (`server_main.cpp:826`). Open: no mm-forward consuming `Request.mm_features`; no image codec vendored (raw RGB only); video/audio/multi-image not started |
309309
| Reranking / classify models | Engine side only | Embeddings are LIVE (`LlamaModel`, `vllm_embed`, `/v1/embeddings`); the classify/score heads are landed ops with no registered arch |
310-
| ROCm | W0-W1 community-verified on 5 gfx archs; classic-dense e2e runs all-native (near-tie regime, #269); GDN-hybrid blocked on remaining GDN kernels | Backend + platform + #140 op set + GDN state-I/O + conv1d + post-conv, ctest-green on gfx1151/1103/1100/1201/1200 ([#41](https://github.com/mudler/vllm.cpp/issues/41)). APU UnifiedMemory fix verified. [ROCM.md](ROCM.md) |
310+
| ROCm | W0-W1 community-verified on 5 gfx archs; classic-dense e2e runs all-native (near-tie regime, #269); GDN-hybrid blocked on remaining GDN kernels | Backend + platform + #140 ops + GDN state/conv/postconv/recurrence, ctest-green gfx1151/1103/1100/1201/1200 ([#41](https://github.com/mudler/vllm.cpp/issues/41)). APU UnifiedMemory fix verified. [ROCM.md](ROCM.md) |
311311
| XPU, TPU | Not started | CUDA, CPU, Metal and Vulkan are the built backends |
312312
| Custom logits processors on CUDA | Open, not root-caused | Segfaults in a CUDA build, 232/232 green on CPU |
313313
| Memory budgeting (`ROAD-V1-MEM`, #83) | M1+M2 landed (absolute bytes) | `--kv-cache-memory` sizes the KV pool from an absolute byte budget (ABI v16, group-aware divisor); `--num-blocks` overrides; `--gpu-memory-utilization` needs the M3 profile run (dgx-gated). See `specs/kv-sizing.md` |

docs/USAGE.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,9 @@ The ROCm backend registers native ops family by family
4343
([#41](https://github.com/mudler/vllm.cpp/issues/41)); landed GDN slices so far:
4444
the indexed state I/O pair (`kGdnStateGather`/`kGdnStateScatter`), the causal
4545
conv1d pair (`kCausalConv1dFwd`/`kCausalConv1dUpdate`, incl. the exact-chunks
46-
descriptor form Qwen3.5 prefill passes), and the fused post-conv glue
47-
(`kGdnPostConv`). On a
46+
descriptor form Qwen3.5 prefill passes), the fused post-conv glue
47+
(`kGdnPostConv`), and the gated-delta recurrence (`kGdnPrefill`/`kGdnDecode`,
48+
portable scan). On a
4849
discrete card there is no CPU fallback tier, so a model whose layers call an op
4950
that is not registered yet fails loudly with `vt: no kernel for op N on device
5051
type 5` — that is the memory-safety design working, not a crash. Run with

src/vt/rocm/rocm_gdn_scan.hip

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
// ROCm GDN recurrence core (BACKEND-ROCM-GDN-KERNELS family 4; issue #41).
2+
// kGdnPrefill + kGdnDecode over the PORTABLE scan — hand-translation of
3+
// cuda_gdn.cu GdnScanKernel (:1856) + its launchers, readable side by side
4+
// against the donor. CPU oracle: src/vt/cpu/cpu_ops.cpp:1331/1368
5+
// (GdnHeadTokenStep); formula reference gdn-semantics.md §7/§8. One kernel
6+
// serves both ops (qsl != null => varlen prefill; qsl == null => single-step
7+
// decode with optional state_idx + NULL-block zero-out).
8+
//
9+
// Deliberately NOT ported (docs/ROCM.md §6): the WMMA chunked prefill
10+
// (`#if __CUDA_ARCH__ >= 800`), the fused/packed/Triton-AOT decode variants —
11+
// NVIDIA-only machinery; the scan is the correctness baseline and the perf
12+
// variants are M5 levers (upstream's ROCm answer, Triton/CK, is a later row).
13+
//
14+
// Gate: the recurrence case in test_backend_cross_device.cpp — NMSE <= 5e-4
15+
// vs the CPU oracle on out AND in-place state; decode NULL-slot zero-out
16+
// EXACT.
17+
18+
#include <hip/hip_bf16.h>
19+
#include <hip/hip_fp16.h>
20+
#include <hip/hip_runtime.h>
21+
22+
#include <cstdint>
23+
#include <cstddef>
24+
#include <stdexcept>
25+
#include <string>
26+
27+
#include "vt/ops.h"
28+
29+
namespace vt::rocm {
30+
namespace {
31+
32+
constexpr int kBlock = 256;
33+
34+
inline void Check(hipError_t err, const char* what) {
35+
if (err != hipSuccess) {
36+
throw std::runtime_error(std::string("vt rocm gdn: ") + what + ": " +
37+
hipGetErrorString(err));
38+
}
39+
}
40+
inline hipStream_t AsStream(const Queue& q) {
41+
return static_cast<hipStream_t>(q.handle);
42+
}
43+
inline unsigned GridFor(int64_t n) {
44+
if (n <= 0) return 1;
45+
const int64_t g = (n + kBlock - 1) / kBlock;
46+
return static_cast<unsigned>(g > 65535 ? 65535 : g);
47+
}
48+
49+
__device__ inline float Ld(const float* p, int64_t i) { return p[i]; }
50+
__device__ inline float Ld(const __half* p, int64_t i) { return __half2float(p[i]); }
51+
__device__ inline float Ld(const __hip_bfloat16* p, int64_t i) {
52+
return __bfloat162float(p[i]);
53+
}
54+
__device__ inline void St(float* p, int64_t i, float v) { p[i] = v; }
55+
__device__ inline void St(__half* p, int64_t i, float v) { p[i] = __float2half_rn(v); }
56+
__device__ inline void St(__hip_bfloat16* p, int64_t i, float v) {
57+
p[i] = __float2bfloat16(v);
58+
}
59+
60+
// ── Recurrence scan (donor cuda_gdn.cu:1856; serves prefill AND decode) ─────
61+
template <typename Tin, typename Tout, typename TState>
62+
__global__ void GdnScanK(Tout* out, const Tin* q, const Tin* k, const Tin* v,
63+
const float* g, const float* beta, TState* state,
64+
const int32_t* qsl, const int32_t* state_idx,
65+
int64_t state_slots, int64_t hk_n, int64_t dk,
66+
int64_t hv_n, int64_t dv, float scale) {
67+
const int64_t s = blockIdx.y;
68+
const int64_t hv = blockIdx.x;
69+
const int64_t hk = hv / (hv_n / hk_n);
70+
const int64_t state_slot = state_idx != nullptr ? state_idx[s] : s;
71+
if (state_slot < 0 || state_slot >= state_slots) {
72+
const int64_t begin = qsl != nullptr ? qsl[s] : s;
73+
const int64_t end = qsl != nullptr ? qsl[s + 1] : s + 1;
74+
for (int64_t t = begin; t < end; ++t)
75+
for (int64_t vi = threadIdx.x; vi < dv; vi += blockDim.x)
76+
St(out, (t * hv_n + hv) * dv + vi, 0.0f);
77+
return;
78+
}
79+
extern __shared__ float smem[]; // [dk] q' then [dk] k
80+
float* q_sh = smem;
81+
float* k_sh = smem + dk;
82+
TState* s_head = state + (state_slot * hv_n + hv) * dv * dk;
83+
const int64_t begin = qsl != nullptr ? qsl[s] : s;
84+
const int64_t end = qsl != nullptr ? qsl[s + 1] : s + 1;
85+
for (int64_t t = begin; t < end; ++t) {
86+
for (int64_t i = threadIdx.x; i < dk; i += blockDim.x) {
87+
q_sh[i] = Ld(q, (t * hk_n + hk) * dk + i) * scale;
88+
k_sh[i] = Ld(k, (t * hk_n + hk) * dk + i);
89+
}
90+
__syncthreads();
91+
const float decay = expf(g[t * hv_n + hv]);
92+
const float beta_t = beta[t * hv_n + hv];
93+
for (int64_t vi = threadIdx.x; vi < dv; vi += blockDim.x) {
94+
TState* s_row = s_head + vi * dk;
95+
float dot = 0.0f;
96+
for (int64_t ki = 0; ki < dk; ++ki) {
97+
const float decayed = Ld(s_row, ki) * decay;
98+
dot += decayed * k_sh[ki];
99+
}
100+
const float vp = (Ld(v, (t * hv_n + hv) * dv + vi) - dot) * beta_t;
101+
float o = 0.0f;
102+
for (int64_t ki = 0; ki < dk; ++ki) {
103+
const float updated = Ld(s_row, ki) * decay + vp * k_sh[ki];
104+
St(s_row, ki, updated);
105+
o += updated * q_sh[ki];
106+
}
107+
St(out, (t * hv_n + hv) * dv + vi, o);
108+
}
109+
__syncthreads();
110+
}
111+
}
112+
113+
template <typename Tin, typename Tout>
114+
void LaunchGdnScanState(hipStream_t s, Tensor& out, const Tensor& q_in,
115+
const Tensor& k, const Tensor& v, const Tensor& g,
116+
const Tensor& beta, Tensor& state, const int32_t* qsl,
117+
const int32_t* state_idx, int64_t n,
118+
const GdnArgs& args) {
119+
const int64_t hk_n = q_in.shape[1], dk = q_in.shape[2];
120+
const int64_t hv_n = v.shape[1], dv = v.shape[2];
121+
const dim3 grid(static_cast<unsigned>(hv_n), static_cast<unsigned>(n));
122+
const size_t shmem = 2 * static_cast<size_t>(dk) * sizeof(float);
123+
if (state.dtype == DType::kF16) {
124+
GdnScanK<Tin, Tout, __half><<<grid, kBlock, shmem, s>>>(
125+
out.Ptr<Tout>(), q_in.Ptr<Tin>(), k.Ptr<Tin>(), v.Ptr<Tin>(),
126+
g.Ptr<float>(), beta.Ptr<float>(), state.Ptr<__half>(), qsl, state_idx,
127+
state.shape[0], hk_n, dk, hv_n, dv, args.scale);
128+
} else if (state.dtype == DType::kBF16) {
129+
GdnScanK<Tin, Tout, __hip_bfloat16><<<grid, kBlock, shmem, s>>>(
130+
out.Ptr<Tout>(), q_in.Ptr<Tin>(), k.Ptr<Tin>(), v.Ptr<Tin>(),
131+
g.Ptr<float>(), beta.Ptr<float>(), state.Ptr<__hip_bfloat16>(), qsl,
132+
state_idx, state.shape[0], hk_n, dk, hv_n, dv, args.scale);
133+
} else {
134+
GdnScanK<Tin, Tout, float><<<grid, kBlock, shmem, s>>>(
135+
out.Ptr<Tout>(), q_in.Ptr<Tin>(), k.Ptr<Tin>(), v.Ptr<Tin>(),
136+
g.Ptr<float>(), beta.Ptr<float>(), state.Ptr<float>(), qsl, state_idx,
137+
state.shape[0], hk_n, dk, hv_n, dv, args.scale);
138+
}
139+
Check(hipGetLastError(), "gdn scan launch");
140+
}
141+
142+
template <typename Tin>
143+
void LaunchGdnScanIn(hipStream_t s, Tensor& out, const Tensor& q_in,
144+
const Tensor& k, const Tensor& v, const Tensor& g,
145+
const Tensor& beta, Tensor& state, const int32_t* qsl,
146+
const int32_t* state_idx, int64_t n, const GdnArgs& args) {
147+
if (out.dtype == DType::kBF16)
148+
LaunchGdnScanState<Tin, __hip_bfloat16>(s, out, q_in, k, v, g, beta, state, qsl,
149+
state_idx, n, args);
150+
else
151+
LaunchGdnScanState<Tin, float>(s, out, q_in, k, v, g, beta, state, qsl,
152+
state_idx, n, args);
153+
}
154+
155+
} // namespace
156+
157+
void GdnPrefillKernelRocm(Queue& q, Tensor& out, const Tensor& q_in, const Tensor& k,
158+
const Tensor& v, const Tensor& g, const Tensor& beta,
159+
Tensor& state, const Tensor& qsl, const GdnArgs& args) {
160+
const int64_t n = state.shape[0];
161+
if (n == 0) return;
162+
hipStream_t s = AsStream(q);
163+
if (q_in.dtype == DType::kBF16) {
164+
LaunchGdnScanIn<__hip_bfloat16>(s, out, q_in, k, v, g, beta, state,
165+
qsl.Ptr<int32_t>(), nullptr, n, args);
166+
} else {
167+
LaunchGdnScanIn<float>(s, out, q_in, k, v, g, beta, state, qsl.Ptr<int32_t>(),
168+
nullptr, n, args);
169+
}
170+
}
171+
172+
void GdnDecodeKernelRocm(Queue& q, Tensor& out, const Tensor& q_in, const Tensor& k,
173+
const Tensor& v, const Tensor& g, const Tensor& beta,
174+
Tensor& state, const Tensor* state_idx, const GdnArgs& args) {
175+
const int64_t n = q_in.shape[0];
176+
if (n == 0) return;
177+
const int32_t* si = state_idx != nullptr ? state_idx->Ptr<int32_t>() : nullptr;
178+
hipStream_t s = AsStream(q);
179+
if (q_in.dtype == DType::kBF16) {
180+
LaunchGdnScanIn<__hip_bfloat16>(s, out, q_in, k, v, g, beta, state, nullptr, si, n,
181+
args);
182+
} else {
183+
LaunchGdnScanIn<float>(s, out, q_in, k, v, g, beta, state, nullptr, si, n, args);
184+
}
185+
}
186+
187+
} // namespace vt::rocm

src/vt/rocm/rocm_ops.hip

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,14 @@ void GdnPostConvKernelRocm(Queue& q, Tensor& q_out, Tensor& k_out, Tensor& v_out
7373
Tensor& g_out, Tensor& beta_out, const Tensor& conv,
7474
const Tensor& araw, const Tensor& braw, const Tensor& a_log,
7575
const Tensor& dt_bias, const L2NormArgs& args);
76+
// BACKEND-ROCM-GDN-KERNELS family 4 (rocm_gdn_scan.hip): the gated-delta
77+
// recurrence over the portable scan (gdn-semantics §7/§8).
78+
void GdnPrefillKernelRocm(Queue& q, Tensor& out, const Tensor& q_in, const Tensor& k,
79+
const Tensor& v, const Tensor& g, const Tensor& beta,
80+
Tensor& state, const Tensor& qsl, const GdnArgs& args);
81+
void GdnDecodeKernelRocm(Queue& q, Tensor& out, const Tensor& q_in, const Tensor& k,
82+
const Tensor& v, const Tensor& g, const Tensor& beta,
83+
Tensor& state, const Tensor* state_idx, const GdnArgs& args);
7684

7785
namespace {
7886

@@ -173,6 +181,10 @@ struct Registrar {
173181
RegisterOp(OpId::kGdnPostConv, DeviceType::kROCM,
174182
reinterpret_cast<void*>(
175183
static_cast<GdnPostConvFn>(&GdnPostConvKernelRocm)));
184+
RegisterOp(OpId::kGdnPrefill, DeviceType::kROCM,
185+
reinterpret_cast<void*>(static_cast<GdnPrefillFn>(&GdnPrefillKernelRocm)));
186+
RegisterOp(OpId::kGdnDecode, DeviceType::kROCM,
187+
reinterpret_cast<void*>(static_cast<GdnDecodeFn>(&GdnDecodeKernelRocm)));
176188
}
177189
} registrar;
178190

tests/vt/test_backend_cross_device.cpp

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,6 +1461,143 @@ TEST_CASE("GdnPostConv matches the CPU oracle within NMSE <= 5e-4") {
14611461
}
14621462
}
14631463

1464+
TEST_CASE("GDN prefill/decode recurrence matches the CPU oracle within NMSE <= 5e-4") {
1465+
// §7/§8. All f32. NMSE on out AND on the in-place state (the recurrence is
1466+
// arithmetic end to end). Decode covers the compact arm, the indexed arm,
1467+
// and the NULL-slot zero-out.
1468+
const int64_t HK = 2, HV = 4, DK = 16, DV = 24; // HV = ratio*HK
1469+
const float scale = 0.25f;
1470+
vt::GdnArgs ga;
1471+
ga.scale = scale;
1472+
1473+
// ---- prefill: two sequences, lens 4 and 1, fresh zero state.
1474+
const std::vector<int32_t> qsl = {0, 4, 5};
1475+
const int64_t N = 2, T = 5;
1476+
const size_t qkn = static_cast<size_t>(T * HK * DK), vn = static_cast<size_t>(T * HV * DV);
1477+
const size_t gbn = static_cast<size_t>(T * HV), stn = static_cast<size_t>(N * HV * DV * DK);
1478+
const std::vector<float> qin = RandomVec(qkn, 851, -0.5f, 0.5f);
1479+
const std::vector<float> kin = RandomVec(qkn, 852, -0.5f, 0.5f);
1480+
const std::vector<float> vin = RandomVec(vn, 853, -0.5f, 0.5f);
1481+
const std::vector<float> gin = RandomVec(gbn, 854, -0.3f, -0.01f); // log-decay < 0
1482+
const std::vector<float> bin = RandomVec(gbn, 855, 0.0f, 0.5f);
1483+
1484+
std::vector<float> ref_out(vn, 0.0f), ref_st(stn, 0.0f);
1485+
{
1486+
vt::Backend& cpu = vt::GetBackend(DeviceType::kCPU);
1487+
Queue cq = cpu.CreateQueue();
1488+
const Device cd{DeviceType::kCPU, 0};
1489+
std::vector<float> hq = qin, hk_ = kin, hv_ = vin, hg = gin, hb = bin;
1490+
std::vector<int32_t> cqsl = qsl;
1491+
Tensor tq = Tensor::Contiguous(hq.data(), DType::kF32, cd, {T, HK, DK});
1492+
Tensor tk = Tensor::Contiguous(hk_.data(), DType::kF32, cd, {T, HK, DK});
1493+
Tensor tv = Tensor::Contiguous(hv_.data(), DType::kF32, cd, {T, HV, DV});
1494+
Tensor tg = T2(hg.data(), cd, T, HV);
1495+
Tensor tb = T2(hb.data(), cd, T, HV);
1496+
Tensor tst = Tensor::Contiguous(ref_st.data(), DType::kF32, cd, {N, HV, DV, DK});
1497+
Tensor tqsl = TI32(cqsl.data(), cd, N + 1);
1498+
Tensor tout = Tensor::Contiguous(ref_out.data(), DType::kF32, cd, {T, HV, DV});
1499+
vt::GdnPrefill(cq, tout, tq, tk, tv, tg, tb, tst, tqsl, ga);
1500+
cpu.DestroyQueue(cq);
1501+
}
1502+
for (DeviceType dt : RegisteredDevices()) {
1503+
if (!OpAvailable(vt::OpId::kGdnPrefill, dt)) continue;
1504+
CAPTURE(DeviceName(dt));
1505+
vt::Backend& dev = vt::GetBackend(dt);
1506+
Queue q = dev.CreateQueue();
1507+
const Device d{dt, 0};
1508+
DevBuf dq(dev, q, qkn), dk(dev, q, qkn), dv(dev, q, vn), dg(dev, q, gbn),
1509+
db(dev, q, gbn), dout(dev, q, vn), dst(dev, q, stn);
1510+
DevBufI32 dqsl(dev, q, N + 1);
1511+
dq.Upload(qin);
1512+
dk.Upload(kin);
1513+
dv.Upload(vin);
1514+
dg.Upload(gin);
1515+
db.Upload(bin);
1516+
dst.Upload(std::vector<float>(stn, 0.0f));
1517+
dqsl.Upload(qsl);
1518+
Tensor tq = Tensor::Contiguous(dq.ptr(), DType::kF32, d, {T, HK, DK});
1519+
Tensor tk = Tensor::Contiguous(dk.ptr(), DType::kF32, d, {T, HK, DK});
1520+
Tensor tv = Tensor::Contiguous(dv.ptr(), DType::kF32, d, {T, HV, DV});
1521+
Tensor tg = T2(dg.ptr(), d, T, HV);
1522+
Tensor tb = T2(db.ptr(), d, T, HV);
1523+
Tensor tst = Tensor::Contiguous(dst.ptr(), DType::kF32, d, {N, HV, DV, DK});
1524+
Tensor tqsl = TI32(dqsl.ptr(), d, N + 1);
1525+
Tensor tout = Tensor::Contiguous(dout.ptr(), DType::kF32, d, {T, HV, DV});
1526+
vt::GdnPrefill(q, tout, tq, tk, tv, tg, tb, tst, tqsl, ga);
1527+
CHECK(Nmse(ref_out, dout.Download()) <= kNmseTol);
1528+
CHECK(Nmse(ref_st, dst.Download()) <= kNmseTol);
1529+
dev.DestroyQueue(q);
1530+
}
1531+
1532+
// ---- decode: B=3 tokens over a 4-slot cache; slot -1 => zero out row,
1533+
// state untouched. Compact arm (no indices) alongside.
1534+
const int64_t B = 3, SLOTS = 4;
1535+
const size_t dqkn = static_cast<size_t>(B * HK * DK), dvn = static_cast<size_t>(B * HV * DV);
1536+
const size_t dgbn = static_cast<size_t>(B * HV);
1537+
const std::vector<int32_t> sidx = {2, -1, 0};
1538+
const std::vector<float> dq_in = RandomVec(dqkn, 861, -0.5f, 0.5f);
1539+
const std::vector<float> dk_in = RandomVec(dqkn, 862, -0.5f, 0.5f);
1540+
const std::vector<float> dv_in = RandomVec(dvn, 863, -0.5f, 0.5f);
1541+
const std::vector<float> dg_in = RandomVec(dgbn, 864, -0.3f, -0.01f);
1542+
const std::vector<float> db_in = RandomVec(dgbn, 865, 0.0f, 0.5f);
1543+
const size_t dstn = static_cast<size_t>(SLOTS * HV * DV * DK);
1544+
const std::vector<float> dst0 = RandomVec(dstn, 866, -0.4f, 0.4f);
1545+
for (bool indexed : {false, true}) {
1546+
CAPTURE(indexed);
1547+
const int64_t st_rows = indexed ? SLOTS : B;
1548+
const std::vector<float> dst_arm(dst0.begin(), dst0.begin() + st_rows * HV * DV * DK);
1549+
std::vector<float> ref_dout(dvn, -7.0f), ref_dst = dst_arm;
1550+
{
1551+
vt::Backend& cpu = vt::GetBackend(DeviceType::kCPU);
1552+
Queue cq = cpu.CreateQueue();
1553+
const Device cd{DeviceType::kCPU, 0};
1554+
std::vector<float> hq = dq_in, hk_ = dk_in, hv_ = dv_in, hg = dg_in, hb = db_in;
1555+
std::vector<int32_t> csi = sidx;
1556+
Tensor tq = Tensor::Contiguous(hq.data(), DType::kF32, cd, {B, HK, DK});
1557+
Tensor tk = Tensor::Contiguous(hk_.data(), DType::kF32, cd, {B, HK, DK});
1558+
Tensor tv = Tensor::Contiguous(hv_.data(), DType::kF32, cd, {B, HV, DV});
1559+
Tensor tg = T2(hg.data(), cd, B, HV);
1560+
Tensor tb = T2(hb.data(), cd, B, HV);
1561+
Tensor tst = Tensor::Contiguous(ref_dst.data(), DType::kF32, cd, {st_rows, HV, DV, DK});
1562+
Tensor tsi = TI32(csi.data(), cd, B);
1563+
Tensor tout = Tensor::Contiguous(ref_dout.data(), DType::kF32, cd, {B, HV, DV});
1564+
vt::GdnDecode(cq, tout, tq, tk, tv, tg, tb, tst, ga, indexed ? &tsi : nullptr);
1565+
cpu.DestroyQueue(cq);
1566+
}
1567+
for (DeviceType dt : RegisteredDevices()) {
1568+
if (!OpAvailable(vt::OpId::kGdnDecode, dt)) continue;
1569+
CAPTURE(DeviceName(dt));
1570+
vt::Backend& dev = vt::GetBackend(dt);
1571+
Queue q = dev.CreateQueue();
1572+
const Device d{dt, 0};
1573+
DevBuf dq(dev, q, dqkn), dk(dev, q, dqkn), dv(dev, q, dvn), dg(dev, q, dgbn),
1574+
db(dev, q, dgbn), dout(dev, q, dvn), dst(dev, q, static_cast<size_t>(st_rows * HV * DV * DK));
1575+
DevBufI32 dsi(dev, q, B);
1576+
dq.Upload(dq_in);
1577+
dk.Upload(dk_in);
1578+
dv.Upload(dv_in);
1579+
dg.Upload(dg_in);
1580+
db.Upload(db_in);
1581+
dst.Upload(dst_arm);
1582+
dsi.Upload(sidx);
1583+
dout.Upload(std::vector<float>(dvn, -7.0f)); // untouched-row sentinel
1584+
Tensor tq = Tensor::Contiguous(dq.ptr(), DType::kF32, d, {B, HK, DK});
1585+
Tensor tk = Tensor::Contiguous(dk.ptr(), DType::kF32, d, {B, HK, DK});
1586+
Tensor tv = Tensor::Contiguous(dv.ptr(), DType::kF32, d, {B, HV, DV});
1587+
Tensor tg = T2(dg.ptr(), d, B, HV);
1588+
Tensor tb = T2(db.ptr(), d, B, HV);
1589+
Tensor tst = Tensor::Contiguous(dst.ptr(), DType::kF32, d, {st_rows, HV, DV, DK});
1590+
Tensor tsi = TI32(dsi.ptr(), d, B);
1591+
Tensor tout = Tensor::Contiguous(dout.ptr(), DType::kF32, d, {B, HV, DV});
1592+
vt::GdnDecode(q, tout, tq, tk, tv, tg, tb, tst, ga, indexed ? &tsi : nullptr);
1593+
CHECK(Nmse(ref_dout, dout.Download()) <= kNmseTol);
1594+
CHECK(Nmse(ref_dst, dst.Download()) <= kNmseTol);
1595+
dev.DestroyQueue(q);
1596+
}
1597+
}
1598+
}
1599+
1600+
14641601
TEST_CASE("reference tier: an op with no native kernel matches the CPU oracle (unified only)") {
14651602
constexpr int64_t kRows = 7, kCols = 48;
14661603
constexpr size_t kN = kRows * kCols;

0 commit comments

Comments
 (0)