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