FZGPUModules 2.0
GPU-accelerated modular compression pipelines
Loading...
Searching...
No Matches
rle.h
Go to the documentation of this file.
1#pragma once
2
8#include "stage/stage.h"
9#include "fzm_format.h"
10#include "log.h"
11#include "backend/types.h"
12#include <cstdint>
13#include <cstring>
14#include <memory>
15#include <type_traits>
16
17namespace fz {
18
27template<typename T>
28constexpr size_t rleValuesOffset() {
29 return (alignof(T) > sizeof(uint32_t)) ? alignof(T) : sizeof(uint32_t);
30}
31
38template<typename T>
39constexpr size_t rleChunkedValuesOffset(size_t num_chunks) {
40 const size_t hdr = (num_chunks + 2) * sizeof(uint32_t);
41 return (alignof(T) > sizeof(uint32_t))
42 ? ((hdr + alignof(T) - 1) & ~(alignof(T) - 1))
43 : hdr;
44}
45
82template<typename T = uint16_t>
83class RLEStage : public Stage {
84public:
85 RLEStage() : is_inverse_(false) {}
86 ~RLEStage() override;
87
88 void setInverse(bool inverse) override { is_inverse_ = inverse; }
89 bool isInverse() const override { return is_inverse_; }
90
91 void execute(
92 fz::stream_t stream,
93 MemoryPool* pool,
94 const std::vector<void*>& inputs,
95 const std::vector<void*>& outputs,
96 const std::vector<size_t>& sizes
97 ) override;
98
104 void postStreamSync(fz::stream_t stream) override;
105
106 std::string getName() const override { return "RLE"; }
107 size_t getNumInputs() const override { return 1; }
108 size_t getNumOutputs() const override { return 1; }
109
115 void setChunkSize(size_t bytes) {
116 chunk_size_ = static_cast<uint32_t>(bytes - (bytes % sizeof(T)));
117 }
118 size_t getChunkSize() const { return chunk_size_; }
119 bool isChunked() const { return chunk_size_ >= sizeof(T); }
120
122 size_t getRequiredInputAlignment() const override {
123 return isChunked() ? chunk_size_ : 1;
124 }
125
137 const std::vector<size_t>& input_sizes
138 ) const override {
139 if (is_inverse_ || input_sizes.empty()) return 0;
140 const size_t n = input_sizes[0] / sizeof(T);
141 // is_boundary(1B) + boundary_scan(4B) + boundary_positions(4B)
142 // + values_scratch(sizeof(T)) + lengths_scratch(4B)
143 // Chunked mode reuses the same arrays: boundary_positions holds the
144 // per-chunk run start positions and boundary_scan holds the per-chunk
145 // run counts, so the bound is unchanged (and is_boundary goes unused).
146 return n * (1 + 4 + 4 + sizeof(T) + 4);
147 }
148
149 std::vector<size_t> estimateOutputSizes(
150 const std::vector<size_t>& input_sizes
151 ) const override {
152 if (is_inverse_) {
153 // Use the element count cached from the forward pass (or deserialized
154 // from the file header) for an exact estimate. Falls back to a
155 // conservative 2× bound only when no prior forward pass has run.
156 if (cached_num_elements_ > 0)
157 return {static_cast<size_t>(cached_num_elements_) * sizeof(T)};
158 return {input_sizes[0] * 2};
159 } else {
160 // Compression: worst case is every element is unique.
161 // Wire format: [num_runs:u32][values:T×n, 4B-aligned][lengths:u32×n]
162 // The values section is padded to a 4-byte boundary (matching
163 // rle_pack_kernel), so the estimate must include that padding or
164 // the allocated buffer will be too small and the lengths write OOBs.
165 size_t n = input_sizes[0] / sizeof(T);
166 size_t values_bytes = n * sizeof(T);
167 size_t values_aligned = (values_bytes + 3u) & ~3u;
168 if (isChunked()) {
169 // Same worst case (every element unique) plus the offset table.
170 const size_t nc = numChunks(input_sizes[0]);
171 return {rleChunkedValuesOffset<T>(nc) + values_aligned
172 + n * sizeof(uint32_t)};
173 }
174 return {rleValuesOffset<T>() + values_aligned + n * sizeof(uint32_t)};
175 }
176 }
177
178 std::unordered_map<std::string, size_t> getActualOutputSizesByName() const override {
179 completePendingSync();
180 completePendingDecodeSync();
181 return {{"output", actual_output_sizes_.empty() ? 0 : actual_output_sizes_[0]}};
182 }
183 size_t getActualOutputSize(int index) const override {
184 completePendingSync();
185 completePendingDecodeSync();
186 return (index == 0 && !actual_output_sizes_.empty()) ? actual_output_sizes_[0] : 0;
187 }
188
189 uint16_t getStageTypeId() const override {
190 return static_cast<uint16_t>(StageType::RLE);
191 }
192
193 uint8_t getOutputDataType(size_t output_index) const override {
194 (void)output_index;
195 return static_cast<uint8_t>(getDataTypeEnum());
196 }
197
198 uint8_t getInputDataType(size_t /*input_index*/) const override {
199 return static_cast<uint8_t>(getDataTypeEnum());
200 }
201
202 size_t serializeHeader(size_t output_index, uint8_t* header_buffer, size_t max_size) const override {
203 (void)output_index;
204 const size_t needed = sizeof(DataType) + 2 * sizeof(uint32_t);
205 if (max_size < needed) return 0;
206 DataType dt = getDataTypeEnum();
207 std::memcpy(header_buffer, &dt, sizeof(DataType));
208 std::memcpy(header_buffer + sizeof(DataType), &cached_num_elements_, sizeof(uint32_t));
209 std::memcpy(header_buffer + sizeof(DataType) + sizeof(uint32_t),
210 &chunk_size_, sizeof(uint32_t));
211 return needed;
212 }
213
214 void deserializeHeader(const uint8_t* header_buffer, size_t size) override {
215 if (size >= sizeof(DataType) + sizeof(uint32_t))
216 std::memcpy(&cached_num_elements_, header_buffer + sizeof(DataType), sizeof(uint32_t));
217 // chunk_size_ is absent in streams written before chunked mode existed;
218 // leaving it at its default keeps those decoding on the global path.
219 if (size >= sizeof(DataType) + 2 * sizeof(uint32_t))
220 std::memcpy(&chunk_size_, header_buffer + sizeof(DataType) + sizeof(uint32_t),
221 sizeof(uint32_t));
222 }
223
224 size_t getMaxHeaderSize(size_t output_index) const override {
225 (void)output_index;
226 return sizeof(DataType) + 2 * sizeof(uint32_t);
227 }
228
229private:
230 bool is_inverse_;
231
233 uint32_t chunk_size_ = 0;
234
236 uint32_t elemsPerChunk() const {
237 return static_cast<uint32_t>(chunk_size_ / sizeof(T));
238 }
239 size_t numChunks(size_t in_bytes) const {
240 const size_t epc = elemsPerChunk();
241 const size_t n = in_bytes / sizeof(T);
242 return epc ? (n + epc - 1) / epc : 0;
243 }
244
248 uint32_t cached_num_elements_ = 0;
249
250 // ── Persistent forward-path scratch ──────────────────────────────────────
251 // Allocated lazily on the first forward execute(); grown if n increases.
252 uint8_t* d_is_boundary_ = nullptr;
253 uint32_t* d_boundary_scan_ = nullptr;
254 uint32_t* d_boundary_positions_ = nullptr;
255 T* d_values_scratch_ = nullptr;
256 uint32_t* d_lengths_scratch_ = nullptr;
257 size_t fwd_scratch_n_ = 0;
258 MemoryPool* fwd_scratch_pool_ = nullptr;
259 bool fwd_from_pool_ = false;
260 std::weak_ptr<const void> fwd_scratch_alive_;
261
262 // Pinned host buffer for async D2H of num_runs.
263 // mutable so getActualOutputSizesByName() can complete the pending
264 // readback even when called on a const Stage reference.
265 mutable uint32_t* h_num_runs_ = nullptr;
266 mutable MemoryPool* h_num_runs_pool_ = nullptr;
267 mutable std::weak_ptr<const void> h_num_runs_alive_;
268 mutable bool fwd_sync_pending_ = false;
269 mutable fz::stream_t fwd_last_stream_ = nullptr;
270 mutable std::vector<size_t> actual_output_sizes_;
271
272 // Pinned host buffer for the decode path's deferred total-output-size
273 // readback (the decompress kernel launch itself only needs num_runs, which
274 // is already synced earlier — this second D2H was previously blocking
275 // *before* that kernel launch for no operational reason; deferring it here
276 // removes a host stall from the decode critical path, same pattern as the
277 // forward path's h_num_runs_ above).
278 mutable uint32_t* h_dec_total_size_ = nullptr;
279 mutable MemoryPool* h_dec_total_size_pool_ = nullptr;
280 mutable std::weak_ptr<const void> h_dec_total_size_alive_;
281 mutable bool dec_sync_pending_ = false;
282 mutable fz::stream_t dec_last_stream_ = nullptr;
283
284 void completePendingDecodeSync() const {
285 if (!dec_sync_pending_) return;
286 cudaStreamSynchronize(dec_last_stream_);
287 const uint32_t total_output_size = *h_dec_total_size_;
288 actual_output_sizes_ = {static_cast<size_t>(total_output_size) * sizeof(T)};
289 dec_sync_pending_ = false;
290 }
291
292 // Complete a pending forward-path readback (if any) by syncing the stream
293 // that was used and computing actual_output_sizes_. Safe to call from
294 // const methods; all state it touches is mutable.
295 void completePendingSync() const {
296 if (!fwd_sync_pending_) return;
297 cudaStreamSynchronize(fwd_last_stream_);
298 const uint32_t num_runs = *h_num_runs_;
299 const size_t values_bytes = static_cast<size_t>(num_runs) * sizeof(T);
300 const size_t values_aligned = (values_bytes + 3) & ~3;
301 const size_t values_offset = isChunked()
302 ? rleChunkedValuesOffset<T>(
303 numChunks(static_cast<size_t>(cached_num_elements_) * sizeof(T)))
304 : rleValuesOffset<T>();
305 actual_output_sizes_ = {
306 values_offset + values_aligned + num_runs * sizeof(uint32_t)
307 };
308 fwd_sync_pending_ = false;
309 // Log run count and effective compression ratio.
310 const size_t in_bytes = static_cast<size_t>(cached_num_elements_) * sizeof(T);
311 const size_t out_bytes = actual_output_sizes_[0];
312 const float ratio = in_bytes > 0
313 ? static_cast<float>(in_bytes) / static_cast<float>(out_bytes) : 0.0f;
314 FZ_LOG(DEBUG, "RLE encode: %u runs / %u elems %.1f KB -> %.1f KB ratio %.2fx",
315 num_runs, cached_num_elements_,
316 in_bytes / 1024.0f, out_bytes / 1024.0f, ratio);
317 }
318
319 // Helper to map template type T to DataType enum
320 DataType getDataTypeEnum() const {
321 if (std::is_same<T, uint8_t>::value) return DataType::UINT8;
322 if (std::is_same<T, uint16_t>::value) return DataType::UINT16;
323 if (std::is_same<T, uint32_t>::value) return DataType::UINT32;
324 if (std::is_same<T, uint64_t>::value) return DataType::UINT64;
325 if (std::is_same<T, int8_t>::value) return DataType::INT8;
326 if (std::is_same<T, int16_t>::value) return DataType::INT16;
327 if (std::is_same<T, int32_t>::value) return DataType::INT32;
328 if (std::is_same<T, int64_t>::value) return DataType::INT64;
329 if (std::is_same<T, float>::value) return DataType::FLOAT32;
330 if (std::is_same<T, double>::value) return DataType::FLOAT64;
331 return DataType::UINT8; // Fallback
332 }
333};
334
335extern template class RLEStage<uint8_t>;
336extern template class RLEStage<uint16_t>;
337extern template class RLEStage<uint32_t>;
338extern template class RLEStage<uint64_t>;
339extern template class RLEStage<int8_t>;
340extern template class RLEStage<int16_t>;
341extern template class RLEStage<int32_t>;
342extern template class RLEStage<int64_t>;
343
344} // namespace fz
Definition mempool.h:82
Definition rle.h:83
void setInverse(bool inverse) override
Definition rle.h:88
void setChunkSize(size_t bytes)
Definition rle.h:115
void postStreamSync(fz::stream_t stream) override
void deserializeHeader(const uint8_t *header_buffer, size_t size) override
Definition rle.h:214
std::unordered_map< std::string, size_t > getActualOutputSizesByName() const override
Definition rle.h:178
std::string getName() const override
Definition rle.h:106
size_t serializeHeader(size_t output_index, uint8_t *header_buffer, size_t max_size) const override
Definition rle.h:202
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
uint16_t getStageTypeId() const override
Definition rle.h:189
size_t getMaxHeaderSize(size_t output_index) const override
Definition rle.h:224
uint8_t getOutputDataType(size_t output_index) const override
Definition rle.h:193
std::vector< size_t > estimateOutputSizes(const std::vector< size_t > &input_sizes) const override
Definition rle.h:149
uint8_t getInputDataType(size_t) const override
Definition rle.h:198
size_t getActualOutputSize(int index) const override
Definition rle.h:183
size_t getRequiredInputAlignment() const override
Chunked mode needs whole chunks; the pipeline zero-pads the input to suit.
Definition rle.h:122
size_t estimateScratchBytes(const std::vector< size_t > &input_sizes) const override
Definition rle.h:136
Definition stage.h:31
FZM binary file format definitions — structs, enums, and helpers.
Logging infrastructure and macros.
#define FZ_LOG(level,...)
Definition log.h:201
Definition dag.h:24
constexpr size_t rleChunkedValuesOffset(size_t num_chunks)
Definition rle.h:39
constexpr size_t rleValuesOffset()
Definition rle.h:28
DataType
Element data type identifiers used in buffer and stage descriptors.
Definition fzm_format.h:142
@ DEBUG
Pipeline construction, buffer allocation, data stats.
Base class interface for all compression stages.
Backend-neutral GPU type aliases.