FZGPUModules 2.0
GPU-accelerated modular compression pipelines
Loading...
Searching...
No Matches
bitpack_stage.h
Go to the documentation of this file.
1#pragma once
2
44#include "stage/stage.h"
45#include "fzm_format.h"
46#include "backend/types.h"
47#include <cstdint>
48#include <cstring>
49#include <stdexcept>
50#include <string>
51#include <type_traits>
52#include <unordered_map>
53#include <vector>
54
55namespace fz {
56
65template<typename T>
66class BitpackStage : public Stage {
67 static_assert(
68 std::is_same_v<T, uint8_t> ||
69 std::is_same_v<T, uint16_t> ||
70 std::is_same_v<T, uint32_t>,
71 "BitpackStage: T must be uint8_t, uint16_t, or uint32_t.");
72
73public:
74 BitpackStage() = default;
75
76 // ── Stage control ──────────────────────────────────────────────────────────
77 void setInverse(bool inv) override { is_inverse_ = inv; }
78 bool isInverse() const override { return is_inverse_; }
79
80 // ── Configuration ──────────────────────────────────────────────────────────
81
93 void setNBits(uint8_t nbits) {
94 if (nbits == 0 || nbits > 8 * sizeof(T) || (nbits & (nbits - 1)) != 0)
95 throw std::invalid_argument(
96 "BitpackStage::setNBits: nbits must be a power of two "
97 "in [1, " + std::to_string(8 * sizeof(T)) + "], got "
98 + std::to_string(nbits));
99 nbits_ = nbits;
100 }
101 uint8_t getNBits() const { return nbits_; }
102
110 void setBase(T base) { base_ = base; }
111 T getBase() const { return base_; }
112
124 void setShift(uint8_t shift) {
125 if (shift >= 8 * sizeof(T))
126 throw std::invalid_argument(
127 "BitpackStage::setShift: shift must be in [0, "
128 + std::to_string(8 * sizeof(T) - 1) + "], got "
129 + std::to_string(shift));
130 shift_ = shift;
131 }
132 uint8_t getShift() const { return shift_; }
133
147 void setAutoDetect(bool enable) { auto_detect_ = enable; }
148 bool isAutoDetect() const { return auto_detect_; }
149
155 void setAutoBase(bool enable) { auto_base_ = enable; }
156 bool isAutoBase() const { return auto_base_; }
157
165 void setAutoShift(bool enable) { auto_shift_ = enable; }
166 bool isAutoShift() const { return auto_shift_; }
167
174 void setAdaptive(bool enable) {
175 auto_base_ = auto_shift_ = auto_detect_ = enable;
176 }
177
178 // ── Execution ──────────────────────────────────────────────────────────────
180 fz::stream_t stream,
181 MemoryPool* pool,
182 const std::vector<void*>& inputs,
183 const std::vector<void*>& outputs,
184 const std::vector<size_t>& sizes
185 ) override;
186
187 // ── Metadata ───────────────────────────────────────────────────────────────
188 std::string getName() const override { return "Bitpack"; }
189 size_t getNumInputs() const override { return 1; }
190 size_t getNumOutputs() const override { return 1; }
191
192 std::vector<size_t> estimateOutputSizes(
193 const std::vector<size_t>& input_sizes
194 ) const override {
195 if (input_sizes.empty()) return {0};
196 if (!is_inverse_) {
197 if (auto_detect_) {
198 // nbits is unknown until execute() scans the data; return worst
199 // case (full-width, no compression) so PREALLOCATE has enough room.
200 return {input_sizes[0]};
201 }
202 // Forward: packed output is ceil(n * nbits / 8) bytes.
203 const size_t n = input_sizes[0] / sizeof(T);
204 return {(n * nbits_ + 7) / 8};
205 } else {
206 // Inverse: worst case — every packed bit expands to a full element.
207 // input_sizes[0] is the packed byte count; max elements = bytes * (8/nbits).
208 const size_t max_elems = (input_sizes[0] * 8 + nbits_ - 1) / nbits_;
209 return {max_elems * sizeof(T)};
210 }
211 }
212
213 std::unordered_map<std::string, size_t>
214 getActualOutputSizesByName() const override {
215 return {{"output", actual_output_size_}};
216 }
217
218 size_t getActualOutputSize(int index) const override {
219 return (index == 0) ? actual_output_size_ : 0;
220 }
221
222 // ── Type system ────────────────────────────────────────────────────────────
223
224 uint16_t getStageTypeId() const override {
225 return static_cast<uint16_t>(StageType::BITPACK);
226 }
227
228 // Packed byte stream has no meaningful element type; opt out of type checking.
229 uint8_t getOutputDataType(size_t /*output_index*/) const override {
230 return static_cast<uint8_t>(DataType::UNKNOWN);
231 }
232 uint8_t getInputDataType(size_t /*input_index*/) const override {
233 return static_cast<uint8_t>(DataType::UNKNOWN);
234 }
235
236 // ── Serialization ──────────────────────────────────────────────────────────
237
239 size_t /*output_index*/, uint8_t* buf, size_t max_size
240 ) const override {
241 if (max_size < 15) return 0;
242 buf[0] = static_cast<uint8_t>(dataTypeOf<T>());
243 buf[1] = nbits_;
244 std::memcpy(buf + 2, &num_elements_, sizeof(uint64_t));
245 buf[10] = shift_;
246 const uint32_t base32 = static_cast<uint32_t>(base_);
247 std::memcpy(buf + 11, &base32, sizeof(uint32_t));
248 return 15;
249 }
250
251 void deserializeHeader(const uint8_t* buf, size_t size) override {
252 // buf[0] (DataType) is used by the factory to pick the right instantiation.
253 // We only need nbits, num_elements, shift, and base here.
254 if (size >= 2) nbits_ = buf[1];
255 if (size >= 10) std::memcpy(&num_elements_, buf + 2, sizeof(uint64_t));
256 // Pre-shift archives stop at 10 bytes; leave shift/base at their defaults.
257 if (size >= 15) {
258 shift_ = buf[10];
259 uint32_t base32 = 0;
260 std::memcpy(&base32, buf + 11, sizeof(uint32_t));
261 base_ = static_cast<T>(base32);
262 }
263 }
264
265 size_t getMaxHeaderSize(size_t /*output_index*/) const override { return 15; }
266
267 // saveState/restoreState: deserializeHeader (called during decompression
268 // setup) overwrites num_elements with the value from the file header.
269 // Save the forward-pass values so they can be restored afterward.
270 void saveState() override {
271 saved_nbits_ = nbits_;
272 saved_num_elements_ = num_elements_;
273 saved_output_size_ = actual_output_size_;
274 saved_shift_ = shift_;
275 saved_base_ = base_;
276 }
277
278 void restoreState() override {
279 nbits_ = saved_nbits_;
280 num_elements_ = saved_num_elements_;
281 actual_output_size_ = saved_output_size_;
282 shift_ = saved_shift_;
283 base_ = saved_base_;
284 }
285
286 // Auto-detect requires a D2H sync to read the scanned min/max/OR, so it
287 // cannot be recorded inside a CUDA Graph.
288 bool isGraphCompatible() const override {
289 return !(auto_detect_ || auto_base_ || auto_shift_);
290 }
291
292private:
293 bool is_inverse_ = false;
294 bool auto_detect_ = false;
295 bool auto_base_ = false;
296 bool auto_shift_ = false;
297 uint8_t nbits_ = 8 * sizeof(T); // default: keep all bits (identity)
298 uint8_t shift_ = 0; // low bits dropped before packing
299 T base_ = T(0); // frame-of-reference offset
300 uint64_t num_elements_ = 0; // set by forward execute; used by inverse
301 size_t actual_output_size_ = 0;
302
303 // saveState snapshots
304 uint8_t saved_nbits_ = 8 * sizeof(T);
305 uint8_t saved_shift_ = 0;
306 T saved_base_ = T(0);
307 uint64_t saved_num_elements_ = 0;
308 size_t saved_output_size_ = 0;
309
310 template<typename U>
311 static constexpr DataType dataTypeOf() {
312 if (std::is_same_v<U, uint8_t>) return DataType::UINT8;
313 if (std::is_same_v<U, uint16_t>) return DataType::UINT16;
314 if (std::is_same_v<U, uint32_t>) return DataType::UINT32;
315 return DataType::UINT8; // unreachable
316 }
317};
318
319extern template class BitpackStage<uint8_t>;
320extern template class BitpackStage<uint16_t>;
321extern template class BitpackStage<uint32_t>;
322
323} // namespace fz
Definition bitpack_stage.h:66
void setAdaptive(bool enable)
Definition bitpack_stage.h:174
void setAutoShift(bool enable)
Definition bitpack_stage.h:165
std::vector< size_t > estimateOutputSizes(const std::vector< size_t > &input_sizes) const override
Definition bitpack_stage.h:192
void saveState() override
Definition bitpack_stage.h:270
bool isGraphCompatible() const override
Definition bitpack_stage.h:288
void execute(fz::stream_t stream, MemoryPool *pool, const std::vector< void * > &inputs, const std::vector< void * > &outputs, const std::vector< size_t > &sizes) override
size_t getActualOutputSize(int index) const override
Definition bitpack_stage.h:218
uint8_t getInputDataType(size_t) const override
Definition bitpack_stage.h:232
void setShift(uint8_t shift)
Definition bitpack_stage.h:124
std::string getName() const override
Definition bitpack_stage.h:188
void setAutoDetect(bool enable)
Definition bitpack_stage.h:147
uint16_t getStageTypeId() const override
Definition bitpack_stage.h:224
std::unordered_map< std::string, size_t > getActualOutputSizesByName() const override
Definition bitpack_stage.h:214
size_t getMaxHeaderSize(size_t) const override
Definition bitpack_stage.h:265
void setInverse(bool inv) override
Definition bitpack_stage.h:77
uint8_t getOutputDataType(size_t) const override
Definition bitpack_stage.h:229
void deserializeHeader(const uint8_t *buf, size_t size) override
Definition bitpack_stage.h:251
void setAutoBase(bool enable)
Definition bitpack_stage.h:155
size_t serializeHeader(size_t, uint8_t *buf, size_t max_size) const override
Definition bitpack_stage.h:238
void setNBits(uint8_t nbits)
Definition bitpack_stage.h:93
void setBase(T base)
Definition bitpack_stage.h:110
Definition mempool.h:82
Definition stage.h:30
FZM binary file format definitions — structs, enums, and helpers.
Definition algorithms.h:48
DataType
Element data type identifiers used in buffer and stage descriptors.
Definition fzm_format.h:117
@ UNKNOWN
Byte-transparent stages: skip type checking at finalize()
Base class interface for all compression stages.
Backend-neutral GPU type aliases.