FZGPUModules 2.0
GPU-accelerated modular compression pipelines
Loading...
Searching...
No Matches
GpuANSStatistics.h
1
11#ifndef FZ_ANS_DIETGPU_ANS_GPUANSSTATISTICS_H
12#define FZ_ANS_DIETGPU_ANS_GPUANSSTATISTICS_H
13
14#pragma once
15
16#include "GpuANSCodec.h"
17#include "utils/DeviceUtils.h"
18#include "utils/PtxUtils.h"
19#include "utils/StaticUtils.h"
20
21#include <cmath>
22#include <cub/cub.cuh>
23#include <memory>
24
25namespace fz { namespace ans {
26
27// sum that allows passing in smem for usage, so as to avoid a trailing
28// syncthreads and associated latency
29template <int Threads>
30__device__ inline int
31blockSum(int warpId, int laneId, int valForSum, int* smem) {
32 static_assert(isEvenDivisor(Threads, kWarpSize), "");
33 constexpr int kWarps = Threads / kWarpSize;
34
35 auto allSum = warpReduceAllSum(valForSum);
36
37 if (laneId == 0) {
38 smem[warpId] = allSum;
39 }
40 __syncthreads();
41
42 if (warpId == 0) {
43 int v = laneId < kWarps ? smem[laneId] : 0;
44 v = warpReduceAllSum(v);
45
46 if (laneId == 0) {
47 smem[0] = v;
48 }
49 }
50
51 __syncthreads();
52
53 // trailing syncthreads is elsewhere
54 return smem[0];
55}
56
57// Function that allows normalization of symbol probabilities with a varying
58// (statically known) number of threads, to allow for kernel fusion as needed
59// Stand-alone normalization will use Threads == kNumSymbols (256)
60template <int Threads>
61__device__ void normalizeProbabilitiesFromHistogram(
62 // Size 256 histogram in gmem
63 const uint32_t* __restrict__ counts,
64 uint32_t totalNum,
65 int probBits,
66 uint4* __restrict__ table) {
67
68 static_assert(
69 kNumSymbols == Threads || isEvenDivisor(kNumSymbols, uint32_t(Threads)),
70 "");
71
72 constexpr int kNumSymPerThread =
73 kNumSymbols == Threads ? 1 : (kNumSymbols / Threads);
74
75 // There's nothing to do if the input array in the batch was of zero size
76 if (totalNum == 0) {
77 return;
78 }
79
80 constexpr int kWarps = Threads / kWarpSize;
81 uint32_t kProbWeight = 1 << probBits;
82 int tid = threadIdx.x;
83 int warpId = tid / kWarpSize;
84 int laneId = getLaneId();
85
86 // Load the current count and compute the min/max non-zero values, then
87 // perform an approximate quantization
88 uint32_t qProb[kNumSymPerThread];
89
90 int qProbSum = 0;
91
92#pragma unroll
93 for (int i = 0; i < kNumSymPerThread; ++i) {
94 int curSym = i * Threads + tid;
95 uint32_t count = counts[curSym];
96
97 // Rough initial quantization
98 qProb[i] = kProbWeight * ((float)count / (float)totalNum);
99
100 // All weights for symbols present must be > 0
101 qProb[i] = (count > 0 && qProb[i] == 0) ? 1 : qProb[i];
102
103 qProbSum += qProb[i];
104 }
105
106 // Sum qProbSym across all threads
107 __shared__ int smemSum[kWarps];
108 qProbSum = blockSum<Threads>(warpId, laneId, qProbSum, smemSum);
109
110 // In order to use radix sorting, and also in order to only sort a single
111 // word, pack both the weight and index into a single integer
112 uint32_t sortedPair[kNumSymPerThread];
113
114#pragma unroll
115 for (int i = 0; i < kNumSymPerThread; ++i) {
116 int curSym = i * Threads + tid;
117 sortedPair[i] = (qProb[i] << 16) | curSym;
118 }
119
120 using Sort = cub::BlockRadixSort<uint32_t, Threads, kNumSymPerThread>;
121 __shared__ typename Sort::TempStorage smemSort;
122 Sort(smemSort).SortDescending(sortedPair);
123
124 uint32_t tidSymbol[kNumSymPerThread];
125
126#pragma unroll
127 for (int i = 0; i < kNumSymPerThread; ++i) {
128 tidSymbol[i] = sortedPair[i] & 0xffffU;
129 qProb[i] = sortedPair[i] >> 16;
130 }
131
132 // How far below (positive) or above (negative) our current first-pass
133 // quantization is from our target sum 2^probBits
134 int diff = (int)kProbWeight - (int)qProbSum;
135
136 if (diff > 0) {
137 while (diff > 0) {
138 int iterToApply = diff < kNumSymbols ? diff : kNumSymbols;
139
140#pragma unroll
141 for (int i = 0; i < kNumSymPerThread; ++i) {
142 int curSym = tidSymbol[i];
143 if (curSym < iterToApply) {
144 qProb[i] += 1;
145 }
146 }
147
148 diff -= iterToApply;
149 }
150 } else if (diff < 0) {
151 diff = -diff;
152
153 while (diff > 0) {
154 int qNumGt1s = 0;
155
156#pragma unroll
157 for (int i = 0; i < kNumSymPerThread; ++i) {
158 qNumGt1s += (int)(qProb[i] > 1);
159 }
160
161 qNumGt1s = blockSum<Threads>(warpId, laneId, qNumGt1s, smemSum);
162 __syncthreads();
163
164 int iterToApply = diff < qNumGt1s ? diff : qNumGt1s;
165 assert(iterToApply > 0);
166 int startIndex = qNumGt1s - iterToApply;
167
168#pragma unroll
169 for (int i = 0; i < kNumSymPerThread; ++i) {
170 int curSym = tid * kNumSymPerThread + i;
171 if (curSym >= startIndex && curSym < qNumGt1s) {
172 qProb[i] -= 1;
173 }
174 }
175
176 diff -= iterToApply;
177
178 __syncthreads();
179 }
180 }
181
182 __shared__ uint32_t smemPdf[kNumSymbols];
183
184#pragma unroll
185 for (int i = 0; i < kNumSymPerThread; ++i) {
186 smemPdf[tidSymbol[i]] = qProb[i];
187 }
188
189 __syncthreads();
190
191 uint32_t symPdf[kNumSymPerThread];
192#pragma unroll
193 for (int i = 0; i < kNumSymPerThread; ++i) {
194 int curSym = tid * kNumSymPerThread + i;
195 symPdf[i] = smemPdf[curSym];
196 }
197
198 using Scan = cub::BlockScan<uint32_t, Threads>;
199 __shared__ typename Scan::TempStorage smemScan;
200
201 uint32_t symCdf[kNumSymPerThread];
202 Scan(smemScan).ExclusiveSum(symPdf, symCdf);
203
204 // Compute divisor information (constant division via integer
205 // multiplication + shift)
206 uint32_t shift[kNumSymPerThread];
207 uint32_t magic[kNumSymPerThread];
208
209#pragma unroll
210 for (int i = 0; i < kNumSymPerThread; ++i) {
211 shift[i] = 32 - __clz(symPdf[i] - 1);
212 constexpr uint64_t one = 1;
213 uint64_t magic64 = 0;
214 if (symPdf[i] != 0)
215 magic64 =
216 ((one << 32) * ((one << shift[i]) - symPdf[i])) / symPdf[i] + 1;
217 magic[i] = (uint32_t)magic64;
218 }
219
220#pragma unroll
221 for (int i = 0; i < kNumSymPerThread; ++i) {
222 int curSym = tid * kNumSymPerThread + i;
223 table[curSym] = uint4{symPdf[i], symCdf[i], magic[i], shift[i]};
224 }
225}
226
227template <int Threads>
228__global__ void quantizeWeights(
229 const uint32_t* __restrict__ counts,
230 uint32_t inSize_dev,
231 int probBits,
232 uint4* __restrict__ table) {
233 normalizeProbabilitiesFromHistogram<Threads>(
234 counts,
235 inSize_dev,
236 probBits,
237 table);
238}
239
240inline void ansCalcWeights(
241 int probBits,
242 uint32_t inSize_dev,
243 const uint32_t* histogram_dev,
244 uint4* table_dev,
245 cudaStream_t stream) {
246 constexpr int kThreads = kNumSymbols;
247 quantizeWeights<kThreads><<<1, kThreads, 0, stream>>>(
248 histogram_dev, inSize_dev, probBits, table_dev);
249}
250
251}} // namespace fz::ans
252
253#endif // FZ_ANS_DIETGPU_ANS_GPUANSSTATISTICS_H
Definition dag.h:24