FZGPUModules 2.0
GPU-accelerated modular compression pipelines
Loading...
Searching...
No Matches
gpulz_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 "backend/types.h"
31#include <cstdint>
32#include <cstring>
33#include <memory>
34#include <stdexcept>
35#include <string>
36#include <unordered_map>
37#include <vector>
38
39namespace fz {
40
67class GPULZStage : public Stage {
68public:
70 : is_inverse_(false)
71 , chunk_size_(2048)
72 , word_size_(4)
73 , actual_output_size_(0)
74 , cached_orig_bytes_(0)
75 {}
76
77 ~GPULZStage() override;
78
79 // ── Stage control ──────────────────────────────────────────────────────
80 void setInverse(bool inv) override { is_inverse_ = inv; }
81 bool isInverse() const override { return is_inverse_; }
82
83 bool isGraphCompatible() const override { return !is_inverse_; }
84
85 void setChunkSize(size_t bytes) { chunk_size_ = static_cast<uint32_t>(bytes); }
86 void setWordSize(size_t bytes) { word_size_ = static_cast<uint8_t>(bytes); }
87
100 void setMatchLevel(int level) { match_level_ = static_cast<uint8_t>(level); }
101 int getMatchLevel() const { return static_cast<int>(match_level_); }
102
129 void setSplitMode(bool on) { split_mode_ = on; }
130 bool getSplitMode() const { return split_mode_; }
131
132 size_t getChunkSize() const { return chunk_size_; }
133
146 size_t getRequiredInputAlignment() const override { return 1; }
147 int getWordSize() const { return static_cast<int>(word_size_); }
148 uint32_t getCachedOrigBytes() const { return cached_orig_bytes_; }
149
150 // ── Execution ─────────────────────────────────────────────────────────
152 fz::stream_t stream,
153 MemoryPool* pool,
154 const std::vector<void*>& inputs,
155 const std::vector<void*>& outputs,
156 const std::vector<size_t>& sizes
157 ) override;
158 void postStreamSync(fz::stream_t stream) override;
159
160 // ── Metadata ──────────────────────────────────────────────────────────
161 std::string getName() const override { return "GPULZ"; }
162 size_t getNumInputs() const override {
163 return (is_inverse_ && split_mode_) ? 4 : 1;
164 }
165 size_t getNumOutputs() const override {
166 return (!is_inverse_ && split_mode_) ? 4 : 1;
167 }
168
169 std::vector<std::string> getOutputNames() const override {
170 if (!is_inverse_ && split_mode_)
171 return {"literals", "lengths", "offsets", "meta"};
172 return {"output"};
173 }
174
175 std::vector<size_t> estimateOutputSizes(
176 const std::vector<size_t>& input_sizes
177 ) const override {
178 if (is_inverse_) {
179 // Capacity must cover the padded extent the decode kernel writes,
180 // even though execute() reports the unpadded size afterwards.
181 if (cached_orig_bytes_ > 0)
182 return {static_cast<size_t>(cached_orig_bytes_)};
183 return {input_sizes.empty() ? 0 : input_sizes[0]};
184 }
185 const size_t n_bytes = input_sizes.empty() ? 0 : input_sizes[0];
186 const size_t n_chunks = (n_bytes + chunk_size_ - 1) / chunk_size_;
187 const size_t hdr = 4 + 4 + 8 * n_chunks;
188 // Every bound below is against the PADDED extent, not the input size.
189 // execute() zero-pads a partial tail chunk up to chunk_size_ and encodes
190 // it as a full chunk, so the tail can contribute chunk_size_ bytes of
191 // output from fewer than chunk_size_ bytes of input. Bounding by
192 // `n_bytes` therefore under-reserves by exactly the tail padding, and
193 // the encode writes past the buffer the DAG allocated (E22 in the
194 // benchmarking repo: overruns of 4-40 B observed, silent when this
195 // stage is mid-pipeline).
196 const size_t padded = n_chunks * chunk_size_;
197
198 if (split_mode_) {
199 const size_t block_elems = chunk_size_ / word_size_;
200 const size_t flag_stride = (block_elems + 7) / 8;
201 // literals: every element a literal (or every chunk raw) -> padded.
202 // lengths/offsets: one byte per match, at most one match per element.
203 // meta: header + every chunk's full-width bitmap.
204 return {align4(padded),
205 align4(n_chunks * block_elems),
206 align4(n_chunks * block_elems),
207 align4(hdr + n_chunks * flag_stride)};
208 }
209 // Forward: worst case = padded data (every chunk falls back to raw
210 // storage) + stream header (two uint32_t per chunk).
211 // postStreamSync() rounds the final size up to a 4-byte boundary and
212 // zero-fills the pad; reserve that pad here too (see RREStage).
213 return {align4(padded + hdr)};
214 }
215
216 std::unordered_map<std::string, size_t>
218 size_t getActualOutputSize(int index) const override;
219
231 const std::vector<size_t>& input_sizes
232 ) const override {
233 if (input_sizes.empty()) return 0;
234 const size_t in_bytes = input_sizes[0];
235 const size_t n_chunks = (in_bytes + chunk_size_ - 1) / chunk_size_;
236 const size_t block_elems = chunk_size_ / word_size_;
237 const size_t flag_bytes_max = (block_elems + 7) / 8;
238 if (is_inverse_) {
239 // Split inverse restripes the four ports back into the packed
240 // single-stream form before running the normal decode path.
241 return split_mode_ ? (in_bytes + n_chunks * flag_bytes_max
242 + 4 * n_chunks * sizeof(uint32_t))
243 : 0;
244 }
245 size_t bytes = n_chunks * (static_cast<size_t>(chunk_size_)
246 + flag_bytes_max + 4 * sizeof(uint32_t));
247 if (split_mode_) bytes += n_chunks * 5 * sizeof(uint32_t) + 16;
248 return bytes;
249 }
250
251 uint16_t getStageTypeId() const override {
252 return static_cast<uint16_t>(StageType::GPULZ);
253 }
254
255 uint8_t getOutputDataType(size_t) const override {
256 return static_cast<uint8_t>(DataType::UINT8);
257 }
258 uint8_t getInputDataType(size_t) const override {
259 return static_cast<uint8_t>(DataType::UNKNOWN);
260 }
261
262 // ── Serialization ─────────────────────────────────────────────────────
264 size_t output_index, uint8_t* buf, size_t max_size
265 ) const override {
266 (void)output_index;
267 if (max_size < 14) return 0;
268 std::memcpy(buf, &chunk_size_, sizeof(uint32_t));
269 buf[4] = word_size_;
270 std::memcpy(buf + 5, &cached_orig_bytes_, sizeof(uint32_t));
271 buf[9] = split_mode_ ? 1u : 0u;
272 std::memcpy(buf + 10, &orig_unpadded_bytes_, sizeof(uint32_t));
273 return 14;
274 }
275
276 void deserializeHeader(const uint8_t* buf, size_t size) override {
277 if (size >= 4) std::memcpy(&chunk_size_, buf, sizeof(uint32_t));
278 if (size >= 5) word_size_ = buf[4];
279 if (size >= 9) std::memcpy(&cached_orig_bytes_, buf + 5, sizeof(uint32_t));
280 if (size >= 10) split_mode_ = (buf[9] != 0);
281 if (size >= 14) std::memcpy(&orig_unpadded_bytes_, buf + 10, sizeof(uint32_t));
282 }
283
284 size_t getMaxHeaderSize(size_t) const override { return 14; }
285
286 void saveState() override {
287 saved_chunk_size_ = chunk_size_;
288 saved_word_size_ = word_size_;
289 saved_cached_orig_bytes_ = cached_orig_bytes_;
290 saved_split_mode_ = split_mode_;
291 saved_orig_unpadded_bytes_ = orig_unpadded_bytes_;
292 }
293
294 void restoreState() override {
295 chunk_size_ = saved_chunk_size_;
296 word_size_ = saved_word_size_;
297 cached_orig_bytes_ = saved_cached_orig_bytes_;
298 split_mode_ = saved_split_mode_;
299 orig_unpadded_bytes_ = saved_orig_unpadded_bytes_;
300 }
301
302private:
303 static constexpr size_t align4(size_t n) { return (n + 3) & ~size_t(3); }
304
306 void finishSplitReadback(fz::stream_t stream) const;
307
312 void freeForwardScratch(fz::stream_t stream, bool sync_before_cuda_free);
313
314 void executeForward(fz::stream_t stream, MemoryPool* pool,
315 const std::vector<void*>& inputs,
316 const std::vector<void*>& outputs,
317 size_t in_bytes);
318 void executeInverse(fz::stream_t stream, MemoryPool* pool,
319 const std::vector<void*>& inputs,
320 const std::vector<void*>& outputs,
321 size_t in_bytes);
322
323 bool is_inverse_;
324 uint32_t chunk_size_;
325 uint32_t saved_chunk_size_ = 0;
326 uint8_t word_size_;
327 uint8_t saved_word_size_ = 0;
328 uint8_t match_level_ = 1;
329 bool split_mode_ = false;
330 bool saved_split_mode_ = false;
331 size_t actual_output_size_;
332 // Split mode: per-port actual sizes, in getOutputNames() order.
333 size_t actual_split_sizes_[4] = {0, 0, 0, 0};
334 uint32_t cached_orig_bytes_ = 0; // chunk-padded extent the codec works on
335 uint32_t saved_cached_orig_bytes_ = 0;
336 // True input size before tail-chunk padding. The inverse decodes the full
337 // padded extent but must *report* this, or a downstream stage that derives
338 // an element count from its input size (LorenzoQuantStage's inverse does
339 // exactly that) inflates its own output past the allocated buffer.
340 uint32_t orig_unpadded_bytes_ = 0;
341 uint32_t saved_orig_unpadded_bytes_ = 0;
342
343 // ── Persistent forward scratch buffers ─────────────────────────────────
344 uint8_t* d_data_scratch_ = nullptr;
345 uint8_t* d_flag_scratch_ = nullptr;
346 uint32_t* d_flag_size_ = nullptr;
347 uint32_t* d_data_size_ = nullptr;
348 uint32_t* d_clean_dev_ = nullptr;
349 uint32_t* d_dst_off_dev_ = nullptr;
350 // Split mode: per-chunk destination offsets into the literals / token
351 // streams, plus a 4-entry device totals array read back in postStreamSync.
352 uint32_t* d_lit_off_dev_ = nullptr;
353 uint32_t* d_tok_off_dev_ = nullptr;
354 uint32_t* d_meta_off_dev_ = nullptr;
355 uint32_t* d_lit_cnt_dev_ = nullptr;
356 uint32_t* d_tok_cnt_dev_ = nullptr;
357 uint32_t* d_totals_dev_ = nullptr;
358 mutable uint8_t* split_out_ptr_[4] = {nullptr, nullptr, nullptr, nullptr};
359 mutable bool split_readback_pending_ = false;
360 mutable bool tail_readback_pending_ = false;
361 mutable fz::stream_t tail_readback_stream_ = nullptr;
362 mutable uint32_t tail_last_index_ = 0;
363 mutable uint8_t* tail_output_ptr_ = nullptr;
364 size_t scratch_capacity_ = 0;
365 MemoryPool* scratch_pool_owner_ = nullptr;
366 bool scratch_from_pool_ = false;
368 std::weak_ptr<const void> scratch_alive_;
369};
370
371} // namespace fz
Definition gpulz_stage.h:67
uint8_t getInputDataType(size_t) const override
Definition gpulz_stage.h:258
std::vector< size_t > estimateOutputSizes(const std::vector< size_t > &input_sizes) const override
Definition gpulz_stage.h:175
std::vector< std::string > getOutputNames() const override
Definition gpulz_stage.h:169
std::unordered_map< std::string, size_t > getActualOutputSizesByName() const override
void setInverse(bool inv) override
Definition gpulz_stage.h:80
std::string getName() const override
Definition gpulz_stage.h:161
size_t getActualOutputSize(int index) const override
size_t getMaxHeaderSize(size_t) const override
Definition gpulz_stage.h:284
void saveState() override
Definition gpulz_stage.h:286
void postStreamSync(fz::stream_t stream) override
bool isGraphCompatible() const override
Definition gpulz_stage.h:83
size_t serializeHeader(size_t output_index, uint8_t *buf, size_t max_size) const override
Definition gpulz_stage.h:263
size_t estimateScratchBytes(const std::vector< size_t > &input_sizes) const override
Definition gpulz_stage.h:230
void setMatchLevel(int level)
Definition gpulz_stage.h:100
size_t getRequiredInputAlignment() const override
Definition gpulz_stage.h:146
void setSplitMode(bool on)
Definition gpulz_stage.h:129
uint8_t getOutputDataType(size_t) const override
Definition gpulz_stage.h:255
void deserializeHeader(const uint8_t *buf, size_t size) override
Definition gpulz_stage.h:276
uint16_t getStageTypeId() const override
Definition gpulz_stage.h:251
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
Definition mempool.h:82
Definition stage.h:31
FZM binary file format definitions — structs, enums, and helpers.
Definition dag.h:24
@ GPULZ
TODO: describe this stage.
@ UNKNOWN
Byte-transparent stages: skip type checking at finalize()
Base class interface for all compression stages.
Backend-neutral GPU type aliases.