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
53
69 PerBlock,
70 Fixed,
71 Adaptive,
72};
73
75enum class HuffmanBookModel {
76 Gaussian,
77 Laplace,
79 Uniform,
80};
81
94 double center = -1.0;
97 double scale = 32.0;
99 double shape = 2.0;
100};
101
118template <typename T>
119class HuffmanStage : public Stage {
121 static_assert(
122 std::is_same_v<T, uint8_t> ||
123 std::is_same_v<T, uint16_t> ||
124 std::is_same_v<T, uint32_t>,
125 "HuffmanStage: T must be uint8_t, uint16_t, or uint32_t.");
127
128public:
129 // __host__-only: HIP's clang infers __host__ __device__ for defaulted
130 // special members by default, but ~unique_ptr<phf::Buf<T>> (the type this
131 // destructor implicitly invokes, defined in huffman_stage.cu) is
132 // host-only, so that inference fails to compile under HIP. Neither
133 // function touches device code at all — pin them host-only explicitly.
134 __host__ HuffmanStage();
135 __host__ ~HuffmanStage() override;
136
137 // ── Configuration ─────────────────────────────────────────────────────────
138
170 void setBklen(uint32_t bklen) {
171 constexpr uint32_t kMul = (4u / sizeof(T)) ? (4u / sizeof(T)) : 1u;
172 uint32_t rounded = ((bklen + kMul - 1u) / kMul) * kMul;
173 // bklen_ is stored uint16_t in the serialized header (see the file
174 // comment above) and threaded as uint16_t through the histogram
175 // kernel API end to end. 65536 itself would silently wrap to 0 at
176 // that boundary — validate here instead of producing a corrupt or
177 // crashing stage.
178 if (rounded > 0xFFFFu)
179 throw std::invalid_argument(
180 "HuffmanStage::setBklen: bklen (after rounding, " + std::to_string(rounded) +
181 ") exceeds 65535 — bklen_ is a uint16_t end to end (serialized header and "
182 "histogram kernel API), so the full symbol range of a 16+ bit type cannot be "
183 "histogrammed in one pass. Reduce the upstream quant_radius/code range, or "
184 "split the input across multiple Huffman stages.");
185 bklen_ = rounded;
186 }
187 uint32_t getBklen() const { return bklen_; }
188
197 void setExecutionMode(HuffmanExecutionMode mode) { execution_mode_ = mode; }
198 HuffmanExecutionMode getExecutionMode() const { return execution_mode_; }
199
200 // ── Pre-built codebooks ───────────────────────────────────────────────────
201
213 void setBookSource(HuffmanBookSource src) { book_source_ = src; }
214 HuffmanBookSource getBookSource() const { return book_source_; }
215
233 void setFixedBookFromFreq(const uint32_t* h_freq, uint32_t n);
234
244
266 void setValidateSymbolRange(bool on) { validate_symbol_range_ = on; }
267 bool getValidateSymbolRange() const { return validate_symbol_range_; }
268
270 const std::vector<uint32_t>& getFixedBookFreq() const { return fixed_freq_; }
271
288 void setAdaptiveFloorShift(uint8_t shift) { adaptive_floor_shift_ = shift; }
289 uint8_t getAdaptiveFloorShift() const { return adaptive_floor_shift_; }
290
293 uint8_t getAdaptiveFloorShiftUsed() const { return adaptive_shift_used_; }
294
306 bool getAdaptiveFallbackUsed() const { return adaptive_fallback_; }
307
311 std::vector<std::string> getRunNotes() const override {
312 if (adaptive_fallback_) return {"huffman_adaptive_fallback"};
313 return {};
314 }
315
333 void setRefitThreshold(float ratio) { refit_threshold_ = ratio; }
334 float getRefitThreshold() const { return refit_threshold_; }
335
354 void setRefitInterval(uint32_t n) { refit_interval_ = n; }
355 uint32_t getRefitInterval() const { return refit_interval_; }
356
360 uint32_t getRefitCount() const { return refit_count_; }
361
365 double getFitBitsPerSymbol() const { return fit_bits_per_sym_; }
366
370 bool hasBookSpec() const { return has_book_spec_; }
371 const HuffmanBookSpec& getBookSpec() const { return book_spec_; }
372
373 // ── Stage control ─────────────────────────────────────────────────────────
374 void setInverse(bool inv) override { is_inverse_ = inv; }
375 bool isInverse() const override { return is_inverse_; }
376
377 bool isGraphCompatible() const override {
378 return !is_inverse_ && execution_mode_ == HuffmanExecutionMode::DeviceResident
379 && book_source_ == HuffmanBookSource::Fixed && is_terminal_output_;
380 }
381
382 void setTerminalOutput(bool terminal) override { is_terminal_output_ = terminal; }
383
384 void postStreamSync(fz::stream_t stream) override;
385
386 // ── Pool lifecycle ────────────────────────────────────────────────────────
387
396 void onFinalize(size_t estimated_inlen, MemoryPool* pool) override;
397
398 size_t estimateDeviceFootprintBytes(size_t inlen) const override;
399 size_t estimatePinnedFootprintBytes(size_t inlen) const override;
400
401 // ── Execution ─────────────────────────────────────────────────────────────
403 fz::stream_t stream,
404 MemoryPool* pool,
405 const std::vector<void*>& inputs,
406 const std::vector<void*>& outputs,
407 const std::vector<size_t>& sizes
408 ) override;
409
410 // ── Metadata ──────────────────────────────────────────────────────────────
411 std::string getName() const override { return "Huffman"; }
412 size_t getNumInputs() const override { return 1; }
413 size_t getNumOutputs()const override { return 1; }
414
415 std::vector<size_t> estimateOutputSizes(
416 const std::vector<size_t>& input_sizes
417 ) const override {
418 if (input_sizes.empty()) return {0};
419 if (!is_inverse_) {
420 const size_t n = input_sizes[0] / sizeof(T);
421 if (n == 0) return {0};
422 const size_t sublen = capi_phf_coarse_tune_sublen(n);
423 const size_t pardeg = (n - 1) / sublen + 1;
424 constexpr size_t kMaxCodeBits = 27;
425 constexpr size_t kCellBits = 32;
426 const size_t cells_per_partition =
427 (sublen * kMaxCodeBits + kCellBits - 1) / kCellBits;
428 const size_t bitstream_bytes = pardeg * cells_per_partition * sizeof(uint32_t);
429 const size_t reverse_book_bytes =
430 phf_reverse_book_bytes(static_cast<uint16_t>(bklen_), 4, sizeof(T));
431 return {PHFHEADER_FORCED_ALIGN + reverse_book_bytes
432 + 2 * pardeg * sizeof(PHF_METADATA) + bitstream_bytes};
433 }
434 // Inverse: exact decoded size restored from the serialized header.
435 return {original_len_ * sizeof(T)};
436 }
437
438 std::unordered_map<std::string, size_t>
439 getActualOutputSizesByName() const override {
440 return {{"output", actual_output_size_}};
441 }
442
443 size_t getActualOutputSize(int index) const override {
444 return (index == 0) ? actual_output_size_ : 0;
445 }
446
447 // ── Type system ───────────────────────────────────────────────────────────
448 uint16_t getStageTypeId() const override {
449 return static_cast<uint16_t>(StageType::HUFFMAN);
450 }
451
452 // Byte-transparent output: opt out of pipeline type-compatibility checking.
453 uint8_t getOutputDataType(size_t /*output_index*/) const override {
454 return static_cast<uint8_t>(DataType::UNKNOWN);
455 }
456 uint8_t getInputDataType(size_t /*input_index*/) const override {
457 return static_cast<uint8_t>(DataType::UNKNOWN);
458 }
459
460 // ── Serialization ─────────────────────────────────────────────────────────
462 size_t /*output_index*/, uint8_t* buf, size_t max_size
463 ) const override {
464 if (max_size < 11) return 0;
465 buf[0] = static_cast<uint8_t>(dataTypeOf<T>());
466 uint16_t bk = static_cast<uint16_t>(bklen_);
467 std::memcpy(buf + 1, &bk, sizeof(uint16_t));
468 std::memcpy(buf + 3, &original_len_, sizeof(uint64_t));
469 return 11;
470 }
471
472 void deserializeHeader(const uint8_t* buf, size_t size) override {
473 if (size >= 3) {
474 uint16_t bk;
475 std::memcpy(&bk, buf + 1, sizeof(uint16_t));
476 // Take the archive's bklen verbatim — NOT through setBklen(). Decode has
477 // to reproduce the layout the encoder actually used, so rounding here
478 // would describe a different reverse codebook than the one in the stream.
479 //
480 // But an archive written before setBklen enforced 4-byte alignment can
481 // carry a bklen that puts the bitstream at a non-4 offset, and the decode
482 // kernel reads it as uint32*. That does not merely decode wrong: it
483 // poisons the CUDA context, and every later allocation in the process
484 // fails with "misaligned address" — a failure that points nowhere near
485 // this stage. Refuse it here, where the cause is still legible.
486 if ((sizeof(T) * static_cast<size_t>(bk)) % 4u != 0)
487 throw std::runtime_error(
488 "HuffmanStage: archive declares bklen=" + std::to_string(bk) +
489 " with a " + std::to_string(sizeof(T)) + "-byte symbol, which puts "
490 "the bitstream at a non-4-byte offset. Such archives were written "
491 "by a build predating the alignment fix and cannot be decoded "
492 "correctly — the stream itself is malformed, not just this reader.");
493 bklen_ = bk;
494 }
495 if (size >= 11)
496 std::memcpy(&original_len_, buf + 3, sizeof(uint64_t));
497 }
498
499 size_t getMaxHeaderSize(size_t /*output_index*/) const override { return 11; }
500
501 void saveState() override {
502 saved_bklen_ = bklen_;
503 saved_original_len_ = original_len_;
504 saved_output_size_ = actual_output_size_;
505 }
506
507 void restoreState() override {
508 bklen_ = saved_bklen_;
509 original_len_ = saved_original_len_;
510 actual_output_size_ = saved_output_size_;
511 }
512
513private:
514 bool is_inverse_ = false;
515 uint32_t bklen_ = defaultBklen();
518
519 // Frequency table defining the fixed codebook (host-only; see setFixedBookFromFreq).
520 std::vector<uint32_t> fixed_freq_;
521 // Model that generated fixed_freq_, when it came from setFixedBookFromModel().
522 HuffmanBookSpec book_spec_ {};
523 bool has_book_spec_ = false;
524 uint8_t adaptive_floor_shift_ = 24;
525 uint8_t adaptive_shift_used_ = 24;
530 bool adaptive_fallback_ = false;
531
532 // Refit bookkeeping (Adaptive only).
533 float refit_threshold_ = 1.2f;
534 bool validate_symbol_range_ = true;
535 uint32_t refit_interval_ = 0; // 0 = only the bit-rate trigger
536 uint32_t calls_since_fit_ = 0;
537 double fit_bits_per_sym_ = 0.0; // rate the resident book achieved when fitted
538 bool just_fitted_ = false; // next encode establishes fit_bits_per_sym_
539 uint32_t refit_count_ = 0;
540 // True when buf_->d_bk4 / d_revbk4 currently hold the fixed book. Cleared by
541 // initBuf(), which allocates fresh (uninitialized) codebook buffers.
542 bool fixed_book_resident_ = false;
543
544 uint64_t original_len_ = 0; // element count set by forward execute
545 size_t actual_output_size_ = 0;
546 size_t cap_inlen_ = 0; // allocated capacity (elements); grow-only
547 uint32_t last_bklen_ = 0; // bklen_ when buf_ was last allocated
548
549 // Histogram launch params — computed once in initBuf(), reused every execute()
550 int hist_grid_dim_ = 0;
551 int hist_block_dim_ = 0;
552 int hist_shmem_use_ = 0;
553 int hist_r_per_block_ = 0;
554
555 // cuSZ Huffman working buffers — allocated on first execute() or in onFinalize()
556 std::unique_ptr<phf::Buf<T>> buf_;
557 phf_header header_ {};
558 uint8_t* pending_device_output_ = nullptr;
559 bool pending_device_readback_ = false;
560 size_t pending_device_inlen_ = 0;
561 bool is_terminal_output_ = true;
562
563 // Pool used for buf_ allocations. Set by onFinalize() or captured from the
564 // pool parameter on the first execute() call. Raw non-owning pointer; the
565 // pool outlives the stage when used inside a Pipeline.
566 MemoryPool* pool_ = nullptr;
567
568 // saveState / restoreState snapshots
569 uint32_t saved_bklen_ = defaultBklen();
570 uint64_t saved_original_len_ = 0;
571 size_t saved_output_size_ = 0;
572
573 static constexpr uint32_t defaultBklen() {
574 if constexpr (std::is_same_v<T, uint8_t>) return 256;
575 return 1024;
576 }
577
578 template<typename U>
579 static constexpr DataType dataTypeOf() {
580 if constexpr (std::is_same_v<U, uint8_t>) return DataType::UINT8;
581 if constexpr (std::is_same_v<U, uint16_t>) return DataType::UINT16;
582 return DataType::UINT32;
583 }
584
585 // Allocates buf_ from pool and computes histogram launch params for the given
586 // element count. Must be in huffman_stage.cu: calls cudaFuncSetAttribute with
587 // a __global__ pointer. If buf_ already exists, destroys it first (returning
588 // its allocations to the pool) before creating the new one.
589 void initBuf(size_t inlen, MemoryPool* pool);
590
591 // Builds the canonical codebook from fixed_freq_ into buf_ and H2Ds it.
592 // Requires buf_ to exist. Must be in huffman_stage.cu (needs phf::Buf<T>).
593 void buildFixedBook(fz::stream_t stream);
594
595 // Builds a reusable codebook from a sampled histogram, flooring frequencies and
596 // retrying with a flatter floor until the book fits the 27-bit code field.
597 void buildAdaptiveBook(const uint32_t* h_hist, fz::stream_t stream);
598
599 // Builds d_bk4 and d_revbk4 directly from buf_->d_freq. DeviceResident uses
600 // this for PerBlock/Adaptive histograms and for a Fixed table copied H2D.
601 void buildDeviceBook(fz::stream_t stream, MemoryPool* pool,
602 size_t expected_symbols, uint32_t source_mode);
603
604 // Records the achieved bit rate and applies Adaptive refit policy. Called
605 // immediately for HostCoordinated and from postStreamSync for DeviceResident.
606 void updateAdaptiveRate(size_t inlen, size_t total_nbit);
607
608 // Index of the first symbol with freq > 0 whose built code is unusable, or -1.
609 int findUnusableCode(const uint32_t* freq) const;
610};
611
612extern template class HuffmanStage<uint8_t>;
613extern template class HuffmanStage<uint16_t>;
614extern template class HuffmanStage<uint32_t>;
615
616} // namespace fz
Definition huffman_stage.h:119
size_t estimateDeviceFootprintBytes(size_t inlen) const override
void setRefitThreshold(float ratio)
Definition huffman_stage.h:333
size_t getMaxHeaderSize(size_t) const override
Definition huffman_stage.h:499
size_t serializeHeader(size_t, uint8_t *buf, size_t max_size) const override
Definition huffman_stage.h:461
uint16_t getStageTypeId() const override
Definition huffman_stage.h:448
bool isGraphCompatible() const override
Definition huffman_stage.h:377
double getFitBitsPerSymbol() const
Definition huffman_stage.h:365
std::vector< std::string > getRunNotes() const override
Definition huffman_stage.h:311
void onFinalize(size_t estimated_inlen, MemoryPool *pool) override
size_t getActualOutputSize(int index) const override
Definition huffman_stage.h:443
uint32_t getRefitCount() const
Definition huffman_stage.h:360
void saveState() override
Definition huffman_stage.h:501
bool getAdaptiveFallbackUsed() const
Definition huffman_stage.h:306
bool hasBookSpec() const
Definition huffman_stage.h:370
uint8_t getAdaptiveFloorShiftUsed() const
Definition huffman_stage.h:293
void setInverse(bool inv) override
Definition huffman_stage.h:374
std::vector< size_t > estimateOutputSizes(const std::vector< size_t > &input_sizes) const override
Definition huffman_stage.h:415
void deserializeHeader(const uint8_t *buf, size_t size) override
Definition huffman_stage.h:472
void setFixedBookFromFreq(const uint32_t *h_freq, uint32_t n)
void setAdaptiveFloorShift(uint8_t shift)
Definition huffman_stage.h:288
size_t estimatePinnedFootprintBytes(size_t inlen) const override
std::string getName() const override
Definition huffman_stage.h:411
const std::vector< uint32_t > & getFixedBookFreq() const
Frequency table backing the fixed codebook; empty when none has been set.
Definition huffman_stage.h:270
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:213
void setExecutionMode(HuffmanExecutionMode mode)
Definition huffman_stage.h:197
void setTerminalOutput(bool terminal) override
Definition huffman_stage.h:382
uint8_t getOutputDataType(size_t) const override
Definition huffman_stage.h:453
std::unordered_map< std::string, size_t > getActualOutputSizesByName() const override
Definition huffman_stage.h:439
void setRefitInterval(uint32_t n)
Definition huffman_stage.h:354
void setFixedBookFromModel(const HuffmanBookSpec &spec)
void postStreamSync(fz::stream_t stream) override
void setBklen(uint32_t bklen)
Definition huffman_stage.h:170
uint8_t getInputDataType(size_t) const override
Definition huffman_stage.h:456
void setValidateSymbolRange(bool on)
Definition huffman_stage.h:266
Definition mempool.h:82
Definition stage.h:31
FZM binary file format definitions — structs, enums, and helpers.
Definition dag.h:24
HuffmanBookSource
Definition huffman_stage.h:68
@ 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:75
@ Laplace
exp(-|i-center|/scale)
@ GeneralizedNormal
exp(-(|i-center|/scale)^shape)
@ Uniform
flat; every symbol equally likely
@ Gaussian
exp(-((i-center)/scale)^2 / 2)
HuffmanExecutionMode
Definition huffman_stage.h:49
@ HostCoordinated
cuSZ coarse path with a host partition-prefix scan (default).
@ DeviceResident
Device scan/header assembly; book construction follows the selected source.
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.
Definition huffman_stage.h:90
double shape
Exponent for GeneralizedNormal only (2.0 == Gaussian, 1.0 == Laplace).
Definition huffman_stage.h:99
double scale
Definition huffman_stage.h:97
double center
Definition huffman_stage.h:94
Backend-neutral GPU type aliases.