FZGPUModules 2.0
GPU-accelerated modular compression pipelines
Loading...
Searching...
No Matches
lorenzo_stage.h
Go to the documentation of this file.
1#pragma once
2
8#include "stage/stage.h"
9#include "fzm_format.h"
10#include "backend/types.h"
12#include <algorithm>
13#include <array>
14#include <cstdint>
15#include <cstring>
16#include <stdexcept>
17#include <string>
18#include <type_traits>
19#include <unordered_map>
20#include <vector>
21
22namespace fz {
23
31 uint8_t ndim;
32 uint8_t centering;
33 uint8_t order;
34 uint32_t dim_x;
35 uint32_t dim_y;
36 uint32_t dim_z;
37 uint32_t block_size;
38
40 : data_type(DataType::INT32), ndim(1), centering(0), order(1),
41 dim_x(0), dim_y(1), dim_z(1), block_size(0) {}
42};
43static_assert(sizeof(LorenzoConfig) <= FZM_STAGE_CONFIG_SIZE,
44 "LorenzoConfig must fit in FZM_STAGE_CONFIG_SIZE");
45
58template<typename T>
59class LorenzoStage : public Stage {
60 static_assert(std::is_integral<T>::value && std::is_signed<T>::value,
61 "LorenzoStage requires a signed integer type");
62public:
63 LorenzoStage() = default;
64
73 explicit LorenzoStage(uint32_t block_size, bool centering = false,
74 uint8_t order = 1)
75 : block_size_(block_size), centering_(centering), order_(order) {
76 if (block_size > 1024)
77 throw std::invalid_argument(
78 "LorenzoStage: block_size must be in [0, 1024], got "
79 + std::to_string(block_size));
80 if (centering && block_size == 0)
81 throw std::invalid_argument(
82 "LorenzoStage: centering requires block_size > 0");
83 if (order != 1 && order != 2)
84 throw std::invalid_argument(
85 "LorenzoStage: order must be 1 or 2, got " + std::to_string(order));
86 if (order == 2 && block_size == 0)
87 throw std::invalid_argument(
88 "LorenzoStage: order 2 requires block_size > 0");
89 }
90
91 void setInverse(bool inv) override { is_inverse_ = inv; }
92 bool isInverse() const override { return is_inverse_; }
93
94 void setDims(const std::array<size_t, 3>& dims) override { dims_ = dims; }
95 void setDims(size_t x, size_t y = 1, size_t z = 1) { dims_ = {x, y, z}; }
96 std::array<size_t, 3> getDims() const { return dims_; }
97
110 void setBlockSize(uint32_t n) {
111 if (n > 1024)
112 throw std::invalid_argument(
113 "LorenzoStage::setBlockSize: n must be in [0, 1024], got "
114 + std::to_string(n));
115 block_size_ = n;
116 }
117 uint32_t getBlockSize() const { return block_size_; }
118
135 FusionSpec getFusionSpec() const override {
136 if (isInverse() || centeringActive() || block_size_ == 0) return {};
137 return FusionSpec{FusionAccess::RegionLocal, block_size_};
138 }
139
145 FusedOpDecl getFusedOp() const override {
146 // 1-D block-reset Lorenzo fuses for any block_size that is a multiple of
147 // 32 up to the warp-register cap (block_size = 32*EPL). EPL == 1 is
148 // cuSZp2; EPL == 4 (block 128) is SZp's composed chain. The device policy
149 // runs the delta chain across the whole block for EPL > 1. Centering is
150 // excluded via getFusionSpec() above (checked again here so a caller who
151 // calls getFusedOp() directly, bypassing getFusionSpec(), can't get a
152 // false-positive fused op either).
153 if (!getFusionSpec().fusable() || block_size_ % 32u != 0 ||
154 block_size_ / 32u > fused::warp::kMaxWarpElemsPerLane) return {};
155 const uint32_t epl = block_size_ / 32u;
156 FusedOpDecl d;
157 d.strategy = FusionStrategy::WarpRegister;
158 d.op_name = "Lorenzo1DPredictor";
159 d.include_header = "fused/fused_block/warp_fusion.cuh";
160 d.elems_per_lane = epl;
161 d.n_ab = 0;
162 // ThreadLorenzo1DPredictor's predict(base, d[32]) is written for exactly one
163 // 32-element block; EPL > 1 (block 64/96/128, e.g. SZp's composed chain) has
164 // no TI policy today, so only declare it at EPL == 1.
165 if (epl == 1u) d.ti_op_name = "ThreadLorenzo1DPredictor";
166 fused::warp::Lorenzo1DParams p{0.0f, epl};
167 d.params.resize(sizeof(p));
168 std::memcpy(d.params.data(), &p, sizeof(p));
169 return d;
170 }
171
180 if (!isInverse() || centeringActive() || block_size_ == 0 ||
181 block_size_ % 32u != 0 ||
182 block_size_ / 32u > fused::warp::kMaxWarpElemsPerLane) return {};
183 return FusionSpec{FusionAccess::RegionLocal, block_size_};
184 }
186 if (!getInverseFusionSpec().fusable()) return {};
187 FusedOpDecl d;
188 d.strategy = FusionStrategy::WarpRegister;
189 d.op_name = "Lorenzo1DPredictor";
190 d.include_header = "fused/fused_block/warp_fusion.cuh";
191 d.elems_per_lane = block_size_ / 32u;
192 // ti_op_name intentionally left empty: measured 2026-09-08, TI decode's
193 // serial de-delta chain (ThreadLorenzo1DPredictor::unpredict_and_write) is a
194 // real regression vs the existing warp-cooperative decode (NYX/temperature
195 // cuszp2 outlier: 374 GB/s vs ~651-897 before). Root-caused via `sudo ncu`
196 // (same investigation as TiledLorenzoStage's delta modes): registers/
197 // occupancy/shared-mem are near-identical to the fast identity decode, but
198 // average latency per issued instruction is ~3x higher (52 vs 18 cycles) --
199 // the chase is a genuine serial prefix sum (exact-order dependent), unlike
200 // cost()/pack()'s reduction-shaped ops, which is why compress wins everywhere
201 // but decode only wins where there is no chase. See
202 // reports/w2_ti_decode_design.md UPDATE 2. Forward (compress) is unaffected.
203 return d;
204 }
205
233 void setCentering(bool enable) { centering_ = enable; }
234 bool getCentering() const { return centering_; }
235
253 void setOrder(uint8_t k) {
254 if (k != 1 && k != 2)
255 throw std::invalid_argument(
256 "LorenzoStage::setOrder: order must be 1 or 2, got " + std::to_string(k));
257 order_ = k;
258 }
259 uint8_t getOrder() const { return order_; }
260
261 int ndim() const {
262 if (dims_[2] > 1) return 3;
263 if (dims_[1] > 1) return 2;
264 return 1;
265 }
266
268 fz::stream_t stream,
269 MemoryPool* pool,
270 const std::vector<void*>& inputs,
271 const std::vector<void*>& outputs,
272 const std::vector<size_t>& sizes
273 ) override;
274
275 std::string getName() const override { return "Lorenzo"; }
276
278 size_t getNumInputs() const override {
279 return (is_inverse_ && centeringActive()) ? 2 : 1;
280 }
281 size_t getNumOutputs() const override {
282 return (!is_inverse_ && centeringActive()) ? 2 : 1;
283 }
284
285 std::vector<std::string> getOutputNames() const override {
286 if (centeringActive()) return {"output", "means"};
287 return {"output"};
288 }
289
290 std::vector<size_t> estimateOutputSizes(
291 const std::vector<size_t>& input_sizes
292 ) const override {
293 const size_t in = input_sizes.empty() ? 0 : input_sizes[0];
294 if (!centeringActive()) return {in};
295 return {in, numBlocks(in / sizeof(T)) * sizeof(T)};
296 }
297
298 std::unordered_map<std::string, size_t>
299 getActualOutputSizesByName() const override {
300 if (centeringActive())
301 return {{"output", actual_output_size_}, {"means", actual_means_size_}};
302 return {{"output", actual_output_size_}};
303 }
304
305 size_t getActualOutputSize(int index) const override {
306 if (index == 0) return actual_output_size_;
307 if (index == 1 && centeringActive()) return actual_means_size_;
308 return 0;
309 }
310
311 uint16_t getStageTypeId() const override {
312 return static_cast<uint16_t>(StageType::LORENZO);
313 }
314
315 uint8_t getOutputDataType(size_t /*output_index*/) const override {
316 return static_cast<uint8_t>(getElementDataType());
317 }
318
319 uint8_t getInputDataType(size_t /*input_index*/) const override {
320 return static_cast<uint8_t>(getElementDataType());
321 }
322
323 size_t serializeHeader(size_t /*output_index*/, uint8_t* buf, size_t max_size) const override {
324 if (max_size < sizeof(LorenzoConfig))
325 throw std::runtime_error("LorenzoStage: header buffer too small");
326 LorenzoConfig cfg;
327 cfg.data_type = getElementDataType();
328 cfg.ndim = static_cast<uint8_t>(ndim());
329 cfg.dim_x = static_cast<uint32_t>(dims_[0]);
330 cfg.dim_y = static_cast<uint32_t>(dims_[1]);
331 cfg.dim_z = static_cast<uint32_t>(dims_[2]);
332 cfg.block_size = block_size_;
333 cfg.centering = centeringActive() ? 1u : 0u;
334 cfg.order = order_;
335 std::memcpy(buf, &cfg, sizeof(LorenzoConfig));
336 return sizeof(LorenzoConfig);
337 }
338
339 void deserializeHeader(const uint8_t* buf, size_t size) override {
340 // Accept legacy 16-byte headers (no block_size field).
341 constexpr size_t kMinSize = 16;
342 if (size < kMinSize)
343 throw std::runtime_error("LorenzoStage: header too small");
344 LorenzoConfig cfg; // default-constructed: block_size = 0
345 std::memcpy(&cfg, buf, std::min(size, sizeof(LorenzoConfig)));
346 int eff_ndim = (cfg.ndim == 0) ? 1 : static_cast<int>(cfg.ndim);
347 dims_[0] = cfg.dim_x;
348 dims_[1] = (eff_ndim >= 2) ? cfg.dim_y : 1;
349 dims_[2] = (eff_ndim >= 3) ? cfg.dim_z : 1;
350 block_size_ = (size >= sizeof(LorenzoConfig)) ? cfg.block_size : 0;
351 // `centering` occupies a byte that legacy writers zeroed as `reserved`,
352 // so old archives decode as centering-off without a version bump.
353 centering_ = (cfg.centering != 0);
354 // `order` reuses a byte legacy writers zeroed as `reserved`; 0 reads as
355 // first order, so pre-LZ2 archives decode unchanged.
356 order_ = (cfg.order == 2) ? 2u : 1u;
357 }
358
359 size_t getMaxHeaderSize(size_t /*output_index*/) const override {
360 return sizeof(LorenzoConfig);
361 }
362
363private:
367 void executeBlockMode(
368 fz::stream_t stream,
369 const T* in, T* out, size_t n, size_t byte_size,
370 const std::vector<void*>& inputs,
371 const std::vector<void*>& outputs);
372
375 void executeNDMode(
376 fz::stream_t stream,
377 const T* in, T* out, size_t n, size_t byte_size);
378
379 bool is_inverse_ = false;
380 size_t actual_output_size_ = 0;
381 size_t actual_means_size_ = 0;
382 std::array<size_t, 3> dims_ = {0, 1, 1};
383 uint32_t block_size_ = 0;
384 bool centering_ = false;
385 uint8_t order_ = 1;
386
390 bool centeringActive() const { return centering_ && block_size_ > 0; }
391
393 size_t numBlocks(size_t n) const {
394 return (block_size_ == 0) ? 0 : (n + block_size_ - 1) / block_size_;
395 }
396
397 static DataType getElementDataType() {
398 if (std::is_same<T, int8_t>::value) return DataType::INT8;
399 if (std::is_same<T, int16_t>::value) return DataType::INT16;
400 if (std::is_same<T, int32_t>::value) return DataType::INT32;
401 if (std::is_same<T, int64_t>::value) return DataType::INT64;
402 return DataType::INT32;
403 }
404};
405
406extern template class LorenzoStage<int8_t>;
407extern template class LorenzoStage<int16_t>;
408extern template class LorenzoStage<int32_t>;
409extern template class LorenzoStage<int64_t>;
410
411// Kernel launcher declarations — defined in lorenzo_stage.cu.
412
413template<typename T>
414void launchLorenzoDeltaKernel1D(
415 const T* d_input, T* d_output, size_t n, fz::stream_t stream,
416 unsigned block_threads = 256);
417
418template<typename T>
419void launchLorenzoPrefixSumKernel1D(
420 const T* d_input, T* d_output, size_t n, fz::stream_t stream,
421 unsigned block_threads = 256);
422
426template<typename T>
428 const T* d_input, T* d_output, T* d_means, size_t n, fz::stream_t stream,
429 unsigned block_threads);
430
435template<typename T>
437 const T* d_input, const T* d_means, T* d_output, size_t n, fz::stream_t stream,
438 unsigned block_threads, int passes);
439
442template<typename T>
444 const T* d_input, T* d_output, T* d_means, size_t n, fz::stream_t stream,
445 unsigned block_threads);
446
447template<typename T>
448void launchLorenzoDeltaKernel2D(
449 const T* d_input, T* d_output, size_t nx, size_t ny, fz::stream_t stream);
450
451template<typename T>
452void launchLorenzoPrefixSumKernel2D(
453 const T* d_input, T* d_output, size_t nx, size_t ny, fz::stream_t stream);
454
455template<typename T>
456void launchLorenzoDeltaKernel3D(
457 const T* d_input, T* d_output, size_t nx, size_t ny, size_t nz, fz::stream_t stream);
458
459template<typename T>
460void launchLorenzoPrefixSumKernel3D(
461 const T* d_input, T* d_output, size_t nx, size_t ny, size_t nz, fz::stream_t stream);
462
463} // namespace fz
Definition lorenzo_stage.h:59
void setBlockSize(uint32_t n)
Definition lorenzo_stage.h:110
std::vector< size_t > estimateOutputSizes(const std::vector< size_t > &input_sizes) const override
Definition lorenzo_stage.h:290
size_t getMaxHeaderSize(size_t) const override
Definition lorenzo_stage.h:359
std::string getName() const override
Definition lorenzo_stage.h:275
FusedOpDecl getFusedOp() const override
Definition lorenzo_stage.h:145
size_t getActualOutputSize(int index) const override
Definition lorenzo_stage.h:305
uint16_t getStageTypeId() const override
Definition lorenzo_stage.h:311
void setInverse(bool inv) override
Definition lorenzo_stage.h:91
FusedOpDecl getInverseFusedOp() const override
Definition lorenzo_stage.h:185
std::vector< std::string > getOutputNames() const override
Definition lorenzo_stage.h:285
void setCentering(bool enable)
Definition lorenzo_stage.h:233
size_t serializeHeader(size_t, uint8_t *buf, size_t max_size) const override
Definition lorenzo_stage.h:323
void setOrder(uint8_t k)
Definition lorenzo_stage.h:253
std::unordered_map< std::string, size_t > getActualOutputSizesByName() const override
Definition lorenzo_stage.h:299
uint8_t getInputDataType(size_t) const override
Definition lorenzo_stage.h:319
uint8_t getOutputDataType(size_t) const override
Definition lorenzo_stage.h:315
FusionSpec getInverseFusionSpec() const override
Definition lorenzo_stage.h:179
LorenzoStage(uint32_t block_size, bool centering=false, uint8_t order=1)
Definition lorenzo_stage.h:73
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 getNumInputs() const override
Centering adds a "means" port: a second output forward, a second input inverse.
Definition lorenzo_stage.h:278
void deserializeHeader(const uint8_t *buf, size_t size) override
Definition lorenzo_stage.h:339
FusionSpec getFusionSpec() const override
Definition lorenzo_stage.h:135
void setDims(const std::array< size_t, 3 > &dims) override
Definition lorenzo_stage.h:94
Definition mempool.h:82
Definition stage.h:31
FZM binary file format definitions — structs, enums, and helpers.
Definition dag.h:24
void launchLorenzoDeltaCentered1D(const T *d_input, T *d_output, T *d_means, size_t n, fz::stream_t stream, unsigned block_threads)
void launchLorenzoSegmentedScan(const T *d_input, const T *d_means, T *d_output, size_t n, fz::stream_t stream, unsigned block_threads, int passes)
void launchLorenzo2Delta1D(const T *d_input, T *d_output, T *d_means, size_t n, fz::stream_t stream, unsigned block_threads)
constexpr size_t FZM_STAGE_CONFIG_SIZE
Per-stage serialized config slot (bytes)
Definition fzm_format.h:65
DataType
Element data type identifiers used in buffer and stage descriptors.
Definition fzm_format.h:142
Base class interface for all compression stages.
A stage's contribution to a generated fused kernel — the device-op it maps to, where its source lives...
Definition fusion.h:161
std::vector< uint8_t > params
POD Params bytes; empty for stateless ops.
Definition fusion.h:165
uint32_t elems_per_lane
Definition fusion.h:172
std::string op_name
device-op type name, e.g. "DiffNegabinary"
Definition fusion.h:163
std::string include_header
header used by the generated source
Definition fusion.h:164
std::string ti_op_name
Definition fusion.h:191
A stage's fusion contract. Stages that can participate in a fused kernel override Stage::getFusionSpe...
Definition fusion.h:51
Definition lorenzo_stage.h:29
uint8_t centering
1 if per-block mean centering is enabled, else 0.
Definition lorenzo_stage.h:32
uint32_t dim_z
Z dimension (1 for 1-D/2-D).
Definition lorenzo_stage.h:36
DataType data_type
Signed integer element type (1B).
Definition lorenzo_stage.h:30
uint32_t dim_y
Y dimension (1 for 1-D).
Definition lorenzo_stage.h:35
uint8_t ndim
Spatial dimensionality 1/2/3 (0 treated as 1).
Definition lorenzo_stage.h:31
uint32_t dim_x
X (fast) dimension.
Definition lorenzo_stage.h:34
uint8_t order
Prediction order: 0/1 = first, 2 = second. 0 reads as 1.
Definition lorenzo_stage.h:33
uint32_t block_size
1-D block-local reset period; 0 = default N-D behavior.
Definition lorenzo_stage.h:37
Definition warp_op_params.h:42
Backend-neutral GPU type aliases.
POD parameter blocks for the warp-register predictor policies.