FZGPUModules 2.0
GPU-accelerated modular compression pipelines
Loading...
Searching...
No Matches
huffman_stage.h
Go to the documentation of this file.
1#pragma once
2
28#include "stage/stage.h"
29#include "fzm_format.h"
30#include "coders/huffman/phf/hf.h" // phf_header, phf_stream_t
31
32#include "backend/types.h"
33#include <cstdint>
34#include <cstring>
35#include <memory>
36#include <stdexcept>
37#include <string>
38#include <type_traits>
39#include <unordered_map>
40#include <vector>
41
42// Forward-declare phf::Buf<T> to avoid pulling hf_buf.h (CUDA-only) into this header.
43// HuffmanStage<T>::~HuffmanStage() is defined in huffman_stage.cu where the type is complete.
44namespace phf { template<typename E> struct Buf; }
45
46namespace fz {
47
50 Coarse,
51 Fine,
54};
55
71 PerBlock,
72 Fixed,
73 Adaptive,
74};
75
77enum class HuffmanBookModel {
78 Gaussian,
79 Laplace,
81 Uniform,
82};
83
96 double center = -1.0;
99 double scale = 32.0;
101 double shape = 2.0;
102};
103
118template <typename T>
119class HuffmanStage : public Stage {
120 static_assert(
121 std::is_same_v<T, uint8_t> ||
122 std::is_same_v<T, uint16_t> ||
123 std::is_same_v<T, uint32_t>,
124 "HuffmanStage: T must be uint8_t, uint16_t, or uint32_t.");
125
126public:
127 // __host__-only: HIP's clang infers __host__ __device__ for defaulted
128 // special members by default, but ~unique_ptr<phf::Buf<T>> (the type this
129 // destructor implicitly invokes, defined in huffman_stage.cu) is
130 // host-only, so that inference fails to compile under HIP. Neither
131 // function touches device code at all — pin them host-only explicitly.
132 __host__ HuffmanStage();
133 __host__ ~HuffmanStage() override;
134
135 // ── Configuration ─────────────────────────────────────────────────────────
136
168 void setBklen(uint32_t bklen) {
169 constexpr uint32_t kMul = (4u / sizeof(T)) ? (4u / sizeof(T)) : 1u;
170 bklen_ = ((bklen + kMul - 1u) / kMul) * kMul;
171 }
172 uint32_t getBklen() const { return bklen_; }
173
189 void setEncodeMode(HuffmanEncodeMode mode) { encode_mode_ = mode; }
190 HuffmanEncodeMode getEncodeMode() const { return encode_mode_; }
191
192 // ── Pre-built codebooks ───────────────────────────────────────────────────
193
205 void setBookSource(HuffmanBookSource src) { book_source_ = src; }
206 HuffmanBookSource getBookSource() const { return book_source_; }
207
225 void setFixedBookFromFreq(const uint32_t* h_freq, uint32_t n);
226
236
261 void setValidateSymbolRange(bool on) { validate_symbol_range_ = on; }
262 bool getValidateSymbolRange() const { return validate_symbol_range_; }
263
265 const std::vector<uint32_t>& getFixedBookFreq() const { return fixed_freq_; }
266
283 void setAdaptiveFloorShift(uint8_t shift) { adaptive_floor_shift_ = shift; }
284 uint8_t getAdaptiveFloorShift() const { return adaptive_floor_shift_; }
285
288 uint8_t getAdaptiveFloorShiftUsed() const { return adaptive_shift_used_; }
289
301 bool getAdaptiveFallbackUsed() const { return adaptive_fallback_; }
302
306 std::vector<std::string> getRunNotes() const override {
307 if (adaptive_fallback_) return {"huffman_adaptive_fallback"};
308 return {};
309 }
310
328 void setRefitThreshold(float ratio) { refit_threshold_ = ratio; }
329 float getRefitThreshold() const { return refit_threshold_; }
330
349 void setRefitInterval(uint32_t n) { refit_interval_ = n; }
350 uint32_t getRefitInterval() const { return refit_interval_; }
351
355 uint32_t getRefitCount() const { return refit_count_; }
356
360 double getFitBitsPerSymbol() const { return fit_bits_per_sym_; }
361
374 bool getLastUsedFineEncode() const { return last_used_fine_; }
375
385 uint8_t getLastMaxCodeLen() const { return last_max_codelen_; }
386
390 bool hasBookSpec() const { return has_book_spec_; }
391 const HuffmanBookSpec& getBookSpec() const { return book_spec_; }
392
393 // ── Stage control ─────────────────────────────────────────────────────────
394 void setInverse(bool inv) override { is_inverse_ = inv; }
395 bool isInverse() const override { return is_inverse_; }
396
397 // Not graph-compatible in any configuration, and not planned. Fixed/Adaptive
398 // remove the histogram D2H, but encode still returns total_nbit/total_ncell to
399 // the host to assemble phf_header before the H2D merge. Closing that would need
400 // device-side header assembly and a device-side merge; graph capture has not
401 // shown a measurable win elsewhere in the library, so it is not worth that.
402 bool isGraphCompatible() const override { return false; }
403
404 // ── Pool lifecycle ────────────────────────────────────────────────────────
405
414 void onFinalize(size_t estimated_inlen, MemoryPool* pool) override;
415
416 size_t estimateDeviceFootprintBytes(size_t inlen) const override;
417 size_t estimatePinnedFootprintBytes(size_t inlen) const override;
418
419 // ── Execution ─────────────────────────────────────────────────────────────
421 fz::stream_t stream,
422 MemoryPool* pool,
423 const std::vector<void*>& inputs,
424 const std::vector<void*>& outputs,
425 const std::vector<size_t>& sizes
426 ) override;
427
428 // ── Metadata ──────────────────────────────────────────────────────────────
429 std::string getName() const override { return "Huffman"; }
430 size_t getNumInputs() const override { return 1; }
431 size_t getNumOutputs()const override { return 1; }
432
433 std::vector<size_t> estimateOutputSizes(
434 const std::vector<size_t>& input_sizes
435 ) const override {
436 if (input_sizes.empty()) return {0};
437 if (!is_inverse_) {
438 // Upper bound: generous 2× input for worst-case bitstream + header overhead.
439 return {input_sizes[0] * 2 + 4096};
440 }
441 // Inverse: exact decoded size restored from the serialized header.
442 return {original_len_ * sizeof(T)};
443 }
444
445 std::unordered_map<std::string, size_t>
446 getActualOutputSizesByName() const override {
447 return {{"output", actual_output_size_}};
448 }
449
450 size_t getActualOutputSize(int index) const override {
451 return (index == 0) ? actual_output_size_ : 0;
452 }
453
454 // ── Type system ───────────────────────────────────────────────────────────
455 uint16_t getStageTypeId() const override {
456 return static_cast<uint16_t>(StageType::HUFFMAN);
457 }
458
459 // Byte-transparent output: opt out of pipeline type-compatibility checking.
460 uint8_t getOutputDataType(size_t /*output_index*/) const override {
461 return static_cast<uint8_t>(DataType::UNKNOWN);
462 }
463 uint8_t getInputDataType(size_t /*input_index*/) const override {
464 return static_cast<uint8_t>(DataType::UNKNOWN);
465 }
466
467 // ── Serialization ─────────────────────────────────────────────────────────
469 size_t /*output_index*/, uint8_t* buf, size_t max_size
470 ) const override {
471 if (max_size < 11) return 0;
472 buf[0] = static_cast<uint8_t>(dataTypeOf<T>());
473 uint16_t bk = static_cast<uint16_t>(bklen_);
474 std::memcpy(buf + 1, &bk, sizeof(uint16_t));
475 std::memcpy(buf + 3, &original_len_, sizeof(uint64_t));
476 return 11;
477 }
478
479 void deserializeHeader(const uint8_t* buf, size_t size) override {
480 if (size >= 3) {
481 uint16_t bk;
482 std::memcpy(&bk, buf + 1, sizeof(uint16_t));
483 // Take the archive's bklen verbatim — NOT through setBklen(). Decode has
484 // to reproduce the layout the encoder actually used, so rounding here
485 // would describe a different reverse codebook than the one in the stream.
486 //
487 // But an archive written before setBklen enforced 4-byte alignment can
488 // carry a bklen that puts the bitstream at a non-4 offset, and the decode
489 // kernel reads it as uint32*. That does not merely decode wrong: it
490 // poisons the CUDA context, and every later allocation in the process
491 // fails with "misaligned address" — a failure that points nowhere near
492 // this stage. Refuse it here, where the cause is still legible.
493 if ((sizeof(T) * static_cast<size_t>(bk)) % 4u != 0)
494 throw std::runtime_error(
495 "HuffmanStage: archive declares bklen=" + std::to_string(bk) +
496 " with a " + std::to_string(sizeof(T)) + "-byte symbol, which puts "
497 "the bitstream at a non-4-byte offset. Such archives were written "
498 "by a build predating the alignment fix and cannot be decoded "
499 "correctly — the stream itself is malformed, not just this reader.");
500 bklen_ = bk;
501 }
502 if (size >= 11)
503 std::memcpy(&original_len_, buf + 3, sizeof(uint64_t));
504 }
505
506 size_t getMaxHeaderSize(size_t /*output_index*/) const override { return 11; }
507
508 void saveState() override {
509 saved_bklen_ = bklen_;
510 saved_original_len_ = original_len_;
511 saved_output_size_ = actual_output_size_;
512 }
513
514 void restoreState() override {
515 bklen_ = saved_bklen_;
516 original_len_ = saved_original_len_;
517 actual_output_size_ = saved_output_size_;
518 }
519
520private:
521 bool is_inverse_ = false;
522 uint32_t bklen_ = defaultBklen();
525
526 // Frequency table defining the fixed codebook (host-only; see setFixedBookFromFreq).
527 std::vector<uint32_t> fixed_freq_;
528 // Model that generated fixed_freq_, when it came from setFixedBookFromModel().
529 HuffmanBookSpec book_spec_ {};
530 bool has_book_spec_ = false;
531 uint8_t adaptive_floor_shift_ = 24;
532 uint8_t adaptive_shift_used_ = 24;
537 bool adaptive_fallback_ = false;
538
539 // Refit bookkeeping (Adaptive only).
540 float refit_threshold_ = 1.2f;
541 bool validate_symbol_range_ = true;
542 uint32_t refit_interval_ = 0; // 0 = only the bit-rate trigger
543 uint32_t calls_since_fit_ = 0;
544 double fit_bits_per_sym_ = 0.0; // rate the resident book achieved when fitted
545 bool just_fitted_ = false; // next encode establishes fit_bits_per_sym_
546 uint32_t refit_count_ = 0;
547 // True when buf_->d_bk4 / d_revbk4 currently hold the fixed book. Cleared by
548 // initBuf(), which allocates fresh (uninitialized) codebook buffers.
549 bool fixed_book_resident_ = false;
550
551 // Mirrored out of phf::Buf after each forward encode so callers can see which
552 // encode path actually ran (Fine silently degrades to Coarse for long codes).
553 bool last_used_fine_ = false;
554 uint8_t last_max_codelen_ = 0;
555 // Max code length the Fine-fallback warning last fired for; suppresses one warning
556 // per compress call when a Fixed/Adaptive book stays resident.
557 uint8_t warned_max_codelen_ = 0;
558
559 uint64_t original_len_ = 0; // element count set by forward execute
560 size_t actual_output_size_ = 0;
561 size_t cap_inlen_ = 0; // allocated capacity (elements); grow-only
562 uint32_t last_bklen_ = 0; // bklen_ when buf_ was last allocated
563 HuffmanEncodeMode last_encode_mode_ = HuffmanEncodeMode::Coarse; // encode_mode_ when buf_ was last allocated
564
565 // Histogram launch params — computed once in initBuf(), reused every execute()
566 int hist_grid_dim_ = 0;
567 int hist_block_dim_ = 0;
568 int hist_shmem_use_ = 0;
569 int hist_r_per_block_ = 0;
570
571 // PHF working buffers — allocated from pool_ on first execute() or in onFinalize()
572 std::unique_ptr<phf::Buf<T>> buf_;
573 phf_header header_ {};
574
575 // Pool used for buf_ allocations. Set by onFinalize() or captured from the
576 // pool parameter on the first execute() call. Raw non-owning pointer; the
577 // pool outlives the stage when used inside a Pipeline.
578 MemoryPool* pool_ = nullptr;
579
580 // saveState / restoreState snapshots
581 uint32_t saved_bklen_ = defaultBklen();
582 uint64_t saved_original_len_ = 0;
583 size_t saved_output_size_ = 0;
584
585 static constexpr uint32_t defaultBklen() {
586 if constexpr (std::is_same_v<T, uint8_t>) return 256;
587 return 1024;
588 }
589
590 template<typename U>
591 static constexpr DataType dataTypeOf() {
592 if constexpr (std::is_same_v<U, uint8_t>) return DataType::UINT8;
593 if constexpr (std::is_same_v<U, uint16_t>) return DataType::UINT16;
594 return DataType::UINT32;
595 }
596
597 // Allocates buf_ from pool and computes histogram launch params for the given
598 // element count. Must be in huffman_stage.cu: calls cudaFuncSetAttribute with
599 // a __global__ pointer. If buf_ already exists, destroys it first (returning
600 // its allocations to the pool) before creating the new one.
601 void initBuf(size_t inlen, MemoryPool* pool);
602
603 // Builds the canonical codebook from fixed_freq_ into buf_ and H2Ds it.
604 // Requires buf_ to exist. Must be in huffman_stage.cu (needs phf::Buf<T>).
605 void buildFixedBook(fz::stream_t stream);
606
607 // Builds a reusable codebook from a sampled histogram, flooring frequencies and
608 // retrying with a flatter floor until the book fits the 27-bit code field.
609 void buildAdaptiveBook(const uint32_t* h_hist, fz::stream_t stream);
610
611 // Index of the first symbol with freq > 0 whose built code is unusable, or -1.
612 int findUnusableCode(const uint32_t* freq) const;
613};
614
615extern template class HuffmanStage<uint8_t>;
616extern template class HuffmanStage<uint16_t>;
617extern template class HuffmanStage<uint32_t>;
618
619} // namespace fz
Definition huffman_stage.h:119
size_t estimateDeviceFootprintBytes(size_t inlen) const override
void setRefitThreshold(float ratio)
Definition huffman_stage.h:328
size_t getMaxHeaderSize(size_t) const override
Definition huffman_stage.h:506
size_t serializeHeader(size_t, uint8_t *buf, size_t max_size) const override
Definition huffman_stage.h:468
uint16_t getStageTypeId() const override
Definition huffman_stage.h:455
uint8_t getLastMaxCodeLen() const
Definition huffman_stage.h:385
bool isGraphCompatible() const override
Definition huffman_stage.h:402
double getFitBitsPerSymbol() const
Definition huffman_stage.h:360
std::vector< std::string > getRunNotes() const override
Definition huffman_stage.h:306
void onFinalize(size_t estimated_inlen, MemoryPool *pool) override
size_t getActualOutputSize(int index) const override
Definition huffman_stage.h:450
uint32_t getRefitCount() const
Definition huffman_stage.h:355
void saveState() override
Definition huffman_stage.h:508
bool getAdaptiveFallbackUsed() const
Definition huffman_stage.h:301
bool hasBookSpec() const
Definition huffman_stage.h:390
uint8_t getAdaptiveFloorShiftUsed() const
Definition huffman_stage.h:288
void setInverse(bool inv) override
Definition huffman_stage.h:394
std::vector< size_t > estimateOutputSizes(const std::vector< size_t > &input_sizes) const override
Definition huffman_stage.h:433
void deserializeHeader(const uint8_t *buf, size_t size) override
Definition huffman_stage.h:479
void setFixedBookFromFreq(const uint32_t *h_freq, uint32_t n)
void setAdaptiveFloorShift(uint8_t shift)
Definition huffman_stage.h:283
bool getLastUsedFineEncode() const
Definition huffman_stage.h:374
size_t estimatePinnedFootprintBytes(size_t inlen) const override
std::string getName() const override
Definition huffman_stage.h:429
const std::vector< uint32_t > & getFixedBookFreq() const
Frequency table backing the fixed codebook; empty when none has been set.
Definition huffman_stage.h:265
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
void setBookSource(HuffmanBookSource src)
Definition huffman_stage.h:205
uint8_t getOutputDataType(size_t) const override
Definition huffman_stage.h:460
std::unordered_map< std::string, size_t > getActualOutputSizesByName() const override
Definition huffman_stage.h:446
void setRefitInterval(uint32_t n)
Definition huffman_stage.h:349
void setFixedBookFromModel(const HuffmanBookSpec &spec)
void setBklen(uint32_t bklen)
Definition huffman_stage.h:168
void setEncodeMode(HuffmanEncodeMode mode)
Definition huffman_stage.h:189
uint8_t getInputDataType(size_t) const override
Definition huffman_stage.h:463
void setValidateSymbolRange(bool on)
Definition huffman_stage.h:261
Definition mempool.h:82
Definition stage.h:30
FZM binary file format definitions — structs, enums, and helpers.
Definition algorithms.h:48
HuffmanBookSource
Definition huffman_stage.h:70
@ Adaptive
Histogram the first call only, then reuse that codebook forever.
@ PerBlock
Histogram + build a fresh codebook on every forward call (default).
@ Fixed
Build one codebook up front and reuse it for every forward call.
HuffmanBookModel
Definition huffman_stage.h:77
@ Laplace
exp(-|i-center|/scale)
@ GeneralizedNormal
exp(-(|i-center|/scale)^shape)
@ Uniform
flat; every symbol equally likely
@ Gaussian
exp(-((i-center)/scale)^2 / 2)
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()
HuffmanEncodeMode
Definition huffman_stage.h:49
@ Coarse
Multi-kernel coarse path; CPU prefix-sum sync in phase 3 (default).
Base class interface for all compression stages.
Definition huffman_stage.h:92
double shape
Exponent for GeneralizedNormal only (2.0 == Gaussian, 1.0 == Laplace).
Definition huffman_stage.h:101
double scale
Definition huffman_stage.h:99
double center
Definition huffman_stage.h:96
Backend-neutral GPU type aliases.