Download src/kernels/kv_q8_parity.cpp from WineryLabs/Winery-Strata: direct link, hf CLI and curl.
- Browser
- Download file 8.62 kB
-
https://huggingface.co/WineryLabs/Winery-Strata/resolve/main/src/kernels/kv_q8_parity.cpp
- Command line
-
hf download hf://WineryLabs/Winery-Strata/src/kernels/kv_q8_parity.cpp
-
curl -L -o kv_q8_parity.cpp https://huggingface.co/WineryLabs/Winery-Strata/resolve/main/src/kernels/kv_q8_parity.cpp
8.62 kB
| // src/kernels/kv_q8_parity.cpp - plan v0.3 P7: INT8 KV append/gather against a host reference (GPU, no model). | |
| // | |
| // Appends random K/V cells at scattered positions through a non-identity page table into both the INT8 pools | |
| // (kv_append_q8_step) and the FP16 pools (kv_append_step), gathers random cell sets from both, and checks: | |
| // 1. INT8 codes and FP16 scales are BITWISE equal to the host reference (same f16_from_f32 rounding); | |
| // 2. the INT8 gather is BITWISE equal to the host dequantization of those codes; | |
| // 3. against the FP16 path, the INT8 values differ by at most half a quantization step of their group plus the | |
| // fp16 rounding of both sides (0.624 steps). | |
| namespace k = strata::kernels; | |
| namespace { | |
| int g_fail = 0; | |
| void ck(cudaError_t e, const char* w) { | |
| if (e != cudaSuccess) { std::fprintf(stderr, "%s: %s\n", w, cudaGetErrorString(e)); std::exit(2); } | |
| } | |
| template <typename T> T* dalloc(size_t n) { T* p = nullptr; ck(cudaMalloc(&p, n * sizeof(T) + 64), "malloc"); ck(cudaMemset(p, 0, n * sizeof(T) + 64), "memset"); return p; } | |
| } // namespace | |
| int main() { | |
| k::QsaShapes s = k::qsa_real_shapes(); | |
| s.page_size = 64; // several pages in a small pool | |
| const int H = (int) s.n_head_kv, D = (int) s.head_dim, P = (int) s.page_size, G = D / k::KV_Q8_GROUP; | |
| const int pages = 8, cells = pages * P; | |
| std::mt19937 rng(17); | |
| std::vector<int32_t> table(pages); | |
| for (int i = 0; i < pages; ++i) table[i] = (i * 5 + 3) % pages; // a non-identity permutation | |
| int32_t* d_table = dalloc<int32_t>(pages); | |
| ck(cudaMemcpy(d_table, table.data(), pages * 4, cudaMemcpyHostToDevice), "table"); | |
| int8_t *kq = dalloc<int8_t>((size_t) cells * H * D), *vq = dalloc<int8_t>((size_t) cells * H * D); | |
| uint16_t *ks = dalloc<uint16_t>((size_t) cells * H * G), *vs = dalloc<uint16_t>((size_t) cells * H * G); | |
| uint16_t *kp = dalloc<uint16_t>((size_t) cells * H * D), *vp = dalloc<uint16_t>((size_t) cells * H * D); | |
| float *kcur = dalloc<float>(H * D), *vcur = dalloc<float>(H * D); | |
| int32_t* step = dalloc<int32_t>(k::kStepCount); | |
| std::vector<std::vector<float>> hk(cells), hv(cells); | |
| std::normal_distribution<float> nd(0.f, 1.f); | |
| std::vector<int> positions(cells); | |
| for (int i = 0; i < cells; ++i) positions[i] = i; | |
| std::shuffle(positions.begin(), positions.end(), rng); | |
| const int n_fill = cells - 37; // leave some cells empty | |
| for (int n = 0; n < n_fill; ++n) { | |
| const int pos = positions[n]; | |
| std::vector<float> kv(H * D), vv(H * D); | |
| const float scale = (n % 7 == 0) ? 1e-3f : (n % 11 == 0 ? 40.f : 1.f); // small and large groups | |
| for (auto& x : kv) x = nd(rng) * scale; | |
| for (auto& x : vv) x = nd(rng) * scale; | |
| if (n % 13 == 0) std::fill(kv.begin(), kv.begin() + 64, 0.f); // an all-zero group | |
| hk[pos] = kv; hv[pos] = vv; | |
| int32_t hstep[k::kStepCount] = {pos, pos + 1, 0, 0}; | |
| ck(cudaMemcpy(step, hstep, sizeof hstep, cudaMemcpyHostToDevice), "step"); | |
| ck(cudaMemcpy(kcur, kv.data(), kv.size() * 4, cudaMemcpyHostToDevice), "k"); | |
| ck(cudaMemcpy(vcur, vv.data(), vv.size() * 4, cudaMemcpyHostToDevice), "v"); | |
| k::kv_append_q8_step(kq, vq, ks, vs, d_table, step, kcur, vcur, s, nullptr); | |
| k::kv_append_step(kp, vp, d_table, step, kcur, vcur, s, nullptr); | |
| ck(cudaDeviceSynchronize(), "append"); | |
| } | |
| // 1. codes and scales bitwise vs the host reference | |
| std::vector<int8_t> hkq((size_t) cells * H * D), hvq(hkq.size()); | |
| std::vector<uint16_t> hks((size_t) cells * H * G), hvs(hks.size()); | |
| ck(cudaMemcpy(hkq.data(), kq, hkq.size(), cudaMemcpyDeviceToHost), "d2h"); | |
| ck(cudaMemcpy(hvq.data(), vq, hvq.size(), cudaMemcpyDeviceToHost), "d2h"); | |
| ck(cudaMemcpy(hks.data(), ks, hks.size() * 2, cudaMemcpyDeviceToHost), "d2h"); | |
| ck(cudaMemcpy(hvs.data(), vs, hvs.size() * 2, cudaMemcpyDeviceToHost), "d2h"); | |
| long bad_codes = 0; | |
| for (int n = 0; n < n_fill; ++n) { | |
| const int pos = positions[n]; | |
| for (int kvsel = 0; kvsel < 2; ++kvsel) | |
| for (int h = 0; h < H; ++h) | |
| for (int g = 0; g < G; ++g) { | |
| const float* x = (kvsel ? hv[pos] : hk[pos]).data() + h * D + g * 64; | |
| float amax = 0.f; | |
| for (int t = 0; t < 64; ++t) amax = std::fmax(amax, std::fabs(x[t])); | |
| const uint16_t sb = k::f16_from_f32(amax / 127.0f); | |
| const float sf = k::f32_from_f16(sb); | |
| const long long row = ((long long) table[pos / P] * H + h) * P + pos % P; | |
| if ((kvsel ? hvs : hks)[row * G + g] != sb) ++bad_codes; | |
| for (int t = 0; t < 64; ++t) { | |
| int q = sf > 0.f ? (int) std::nearbyint(x[t] / sf) : 0; | |
| q = std::clamp(q, -127, 127); | |
| if ((kvsel ? hvq : hkq)[row * D + g * 64 + t] != (int8_t) q) ++bad_codes; | |
| } | |
| } | |
| } | |
| if (bad_codes) { std::fprintf(stderr, "FAIL: %ld codes/scales differ from the host reference\n", bad_codes); ++g_fail; } | |
| // 2 + 3. gather random selections from both paths | |
| const int max_ids = 200; | |
| int32_t* d_ids = dalloc<int32_t>(max_ids); | |
| uint16_t *k8 = dalloc<uint16_t>((size_t) max_ids * H * D), *v8 = dalloc<uint16_t>((size_t) max_ids * H * D); | |
| uint16_t *k16 = dalloc<uint16_t>((size_t) max_ids * H * D), *v16 = dalloc<uint16_t>((size_t) max_ids * H * D); | |
| double worst = 0.0; | |
| for (int trial = 0; trial < 20; ++trial) { | |
| const int n_ids = 1 + (int) (rng() % max_ids); | |
| std::vector<int32_t> ids(n_ids); | |
| for (auto& id : ids) id = positions[rng() % n_fill]; | |
| ck(cudaMemcpy(d_ids, ids.data(), n_ids * 4, cudaMemcpyHostToDevice), "ids"); | |
| int32_t hstep[k::kStepCount] = {0, 0, 0, n_ids}; | |
| ck(cudaMemcpy(step, hstep, sizeof hstep, cudaMemcpyHostToDevice), "step"); | |
| k::kv_gather_q8_step(kq, vq, ks, vs, d_table, d_ids, step, max_ids, s, k8, v8, nullptr); | |
| k::kv_gather_step(kp, vp, d_table, d_ids, step, max_ids, s, k16, v16, nullptr); | |
| ck(cudaDeviceSynchronize(), "gather"); | |
| std::vector<uint16_t> a8((size_t) n_ids * H * D), b8(a8.size()), a16(a8.size()), b16(a8.size()); | |
| ck(cudaMemcpy(a8.data(), k8, a8.size() * 2, cudaMemcpyDeviceToHost), "d2h"); | |
| ck(cudaMemcpy(b8.data(), v8, b8.size() * 2, cudaMemcpyDeviceToHost), "d2h"); | |
| ck(cudaMemcpy(a16.data(), k16, a16.size() * 2, cudaMemcpyDeviceToHost), "d2h"); | |
| ck(cudaMemcpy(b16.data(), v16, b16.size() * 2, cudaMemcpyDeviceToHost), "d2h"); | |
| for (int j = 0; j < n_ids; ++j) | |
| for (int h = 0; h < H; ++h) { | |
| const long long row = ((long long) table[ids[j] / P] * H + h) * P + ids[j] % P; | |
| for (int d = 0; d < D; ++d) { | |
| const size_t o = ((size_t) j * H + h) * D + d; | |
| for (int kvsel = 0; kvsel < 2; ++kvsel) { | |
| const float sf = k::f32_from_f16((kvsel ? hvs : hks)[row * G + d / 64]); | |
| const int8_t q = (kvsel ? hvq : hkq)[row * D + d]; | |
| const uint16_t want = k::f16_from_f32((float) q * sf); | |
| const uint16_t got = (kvsel ? b8 : a8)[o]; | |
| if (got != want) { if (g_fail < 5) std::fprintf(stderr, "FAIL: gather id %d h %d d %d\n", j, h, d); ++g_fail; } | |
| const float ref = k::f32_from_f16((kvsel ? b16 : a16)[o]); | |
| const double err = std::fabs(k::f32_from_f16(got) - ref) / (sf > 0 ? sf : 1.0); | |
| worst = std::max(worst, err); | |
| } | |
| } | |
| } | |
| } | |
| // |x - q*sf| <= sf/2 (the stored scale keeps |x|/sf <= 127.06, so no clamping), plus the fp16 rounding of each | |
| // side: at most 2^-11 relative of a value up to 127*sf, i.e. 127/2048 = 0.062 steps each -> 0.5 + 0.124 | |
| if (!(worst <= 0.5 + 2.0 * 127.0 / 2048.0 + 1e-3)) { std::fprintf(stderr, "FAIL: INT8 vs FP16 error %.3f steps\n", worst); ++g_fail; } | |
| std::printf("kv_q8_parity: %s (worst INT8-vs-FP16 error %.3f quantization steps)\n", g_fail ? "FAILED" : "OK", worst); | |
| return g_fail ? 1 : 0; | |
| } | |