FZGPUModules 2.0
GPU-accelerated modular compression pipelines
Loading...
Searching...
No Matches
tiled_lorenzo_stage.h
Go to the documentation of this file.
1#pragma once
2
19#include "stage/stage.h"
20#include "fzm_format.h"
21#include "backend/types.h"
23#include <algorithm>
24#include <array>
25#include <cstdint>
26#include <cstring>
27#include <stdexcept>
28#include <string>
29#include <type_traits>
30#include <unordered_map>
31#include <vector>
32
33namespace fz {
34
41 uint8_t ndim;
42 uint8_t tile_x;
43 uint8_t tile_y;
44 uint8_t tile_z;
45 uint8_t no_delta;
48 uint8_t reserved[2];
49 uint32_t dim_x;
50 uint32_t dim_y;
51 uint32_t dim_z;
52
54 : data_type(DataType::INT32), ndim(2),
55 tile_x(8), tile_y(8), tile_z(1), no_delta(0), reserved{0, 0},
56 dim_x(0), dim_y(1), dim_z(1) {}
57};
58static_assert(sizeof(TiledLorenzoConfig) <= FZM_STAGE_CONFIG_SIZE,
59 "TiledLorenzoConfig must fit in FZM_STAGE_CONFIG_SIZE");
60
73template<typename T>
74class TiledLorenzoStage : public Stage {
75 static_assert(std::is_same_v<T, int16_t> || std::is_same_v<T, int32_t>,
76 "TiledLorenzoStage: T must be int16_t or int32_t.");
77public:
78 TiledLorenzoStage() = default;
79 ~TiledLorenzoStage() override = default;
80
81 void setInverse(bool inv) override { is_inverse_ = inv; }
82 bool isInverse() const override { return is_inverse_; }
83
86 void setDims(const std::array<size_t, 3>& dims) override {
87 if (!dims_pinned_) dims_ = dims;
88 }
89 void setDims(size_t x, size_t y = 1, size_t z = 1) {
90 if (!dims_pinned_) dims_ = {x, y, z};
91 }
92 std::array<size_t, 3> getDims() const { return dims_; }
93 bool hasDimsOverride() const { return dims_pinned_; }
94
108 void setDimsOverride(size_t x, size_t y, size_t z) {
109 dims_ = {x, y, z};
110 dims_pinned_ = true;
111 }
112
119 void setTileShape(uint32_t tx, uint32_t ty = 1, uint32_t tz = 1) {
120 auto chk = [](uint32_t v, const char* nm) {
121 if (v > 255)
122 throw std::invalid_argument(
123 std::string("TiledLorenzoStage::setTileShape: ") + nm
124 + " must be in [0, 255], got " + std::to_string(v));
125 };
126 chk(tx, "tx"); chk(ty, "ty"); chk(tz, "tz");
127 const uint32_t prod = (tx ? tx : 1) * (ty ? ty : 1) * (tz ? tz : 1);
128 if (prod > 1024)
129 throw std::invalid_argument(
130 "TiledLorenzoStage::setTileShape: tx*ty*tz must be in [1, 1024], got "
131 + std::to_string(prod));
132 tile_ = {tx, ty, tz};
133 tile_set_ = true;
134 }
135 std::array<uint32_t, 3> getTileShape() const { return effectiveTile(); }
136
144 void setPredict(bool p) { predict_ = p; }
145 bool getPredict() const { return predict_; }
146
148 uint32_t getTileElems() const {
149 auto t = effectiveTile();
150 return t[0] * t[1] * t[2];
151 }
152
153 int ndim() const {
154 if (dims_[2] > 1) return 3;
155 if (dims_[1] > 1) return 2;
156 return 1;
157 }
158
160 fz::stream_t stream,
161 MemoryPool* pool,
162 const std::vector<void*>& inputs,
163 const std::vector<void*>& outputs,
164 const std::vector<size_t>& sizes
165 ) override;
166
167 std::string getName() const override { return "TiledLorenzo"; }
168 size_t getNumInputs() const override { return 1; }
169 size_t getNumOutputs() const override { return 1; }
170
171 // Region-local for specialization: forward, 2-D tile (tz == 1). The region a downstream
172 // coder sees is one tile (tile_elems); the fused driver (cuSZp3) recomputes
173 // the separable delta per element. tz > 1 is not fused yet.
174 FusionSpec getFusionSpec() const override {
175 auto t = effectiveTile();
176 // Both delta (plain/outlier) and no-delta (cuSZp3 fixed) fuse: the fixed
177 // variant uses the identity warp predictor (getFusedOp selects the op name).
178 if (is_inverse_) return {};
179 // Region-local for specialization; the region is one tile. 2-D (tz==1) and 3-D (tz>1)
180 // both fuse — the warp op gate (getFusedOp) requires tile_elems==64 (EPL=2).
181 return FusionSpec{FusionAccess::RegionLocal, t[0] * t[1] * t[2]};
182 }
183
189 FusedOpDecl getFusedOp() const override {
190 const auto t = effectiveTile();
191 if (is_inverse_ || t[0] * t[1] * t[2] != 64u) return {}; // only tile_elems==64 (EPL=2)
192 const uint32_t dx = static_cast<uint32_t>(dims_[0]);
193 const uint32_t dy = static_cast<uint32_t>(dims_[1]);
194 const uint32_t dz = static_cast<uint32_t>(dims_[2]);
195 const uint32_t tx = t[0], ty = t[1], tz = t[2];
196 const uint32_t ntx = (dx + tx - 1u) / tx;
197 const uint32_t nty = (dy + ty - 1u) / ty;
198 const uint32_t ntz = (dz + tz - 1u) / tz;
199 FusedOpDecl d;
200 d.strategy = FusionStrategy::WarpRegister;
201 d.include_header = "fused/fused_block/warp_fusion.cuh";
202 d.elems_per_lane = 2;
203 if (tz == 1u) { // 2-D
204 d.op_name = predict_ ? "TiledLorenzo2DPredictor" : "TiledLorenzoIdentity2DPredictor";
205 d.ti_op_name = predict_ ? "ThreadTiledLorenzo2DPredictor" : "ThreadTiledLorenzoIdentity2DPredictor";
206 d.n_ab = static_cast<size_t>(ntx) * nty * tx * ty;
207 fused::warp::TiledLorenzo2DParams p{0.0f, dx, dy, tx, ty, ntx};
208 d.params.resize(sizeof(p)); std::memcpy(d.params.data(), &p, sizeof(p));
209 } else { // 3-D (PROTOTYPE)
210 d.op_name = predict_ ? "TiledLorenzo3DPredictor" : "TiledLorenzoIdentity3DPredictor";
211 d.ti_op_name = predict_ ? "ThreadTiledLorenzo3DPredictor" : "ThreadTiledLorenzoIdentity3DPredictor";
212 d.n_ab = static_cast<size_t>(ntx) * nty * ntz * tx * ty * tz;
213 fused::warp::TiledLorenzo3DParams p{0.0f, dx, dy, dz, tx, ty, tz, ntx, nty};
214 d.params.resize(sizeof(p)); std::memcpy(d.params.data(), &p, sizeof(p));
215 }
216 return d;
217 }
218
219 // ── Inverse (decompress) fusion — cuSZp3 warp-register decode ─────────────
220 // Mirror of getFusionSpec/getFusedOp for the reversed chain. The warp inverse
221 // body reconstructs each tile by the same separable prefix sum as the staged
222 // tiled_lorenzo_scan_kernel_rows and scatters to natural row-major, so it also
223 // subsumes the tile→natural remap. Same tile_elems==64 (EPL=2) gate.
225 const auto t = effectiveTile();
226 if (!is_inverse_ || t[0] * t[1] * t[2] != 64u) return {};
227 return FusionSpec{FusionAccess::RegionLocal, 64u};
228 }
229
231 if (!getInverseFusionSpec().fusable()) return {};
232 const auto t = effectiveTile();
233 const uint32_t dx = static_cast<uint32_t>(dims_[0]);
234 const uint32_t dy = static_cast<uint32_t>(dims_[1]);
235 const uint32_t dz = static_cast<uint32_t>(dims_[2]);
236 const uint32_t tx = t[0], ty = t[1], tz = t[2];
237 const uint32_t ntx = (dx + tx - 1u) / tx;
238 const uint32_t nty = (dy + ty - 1u) / ty;
239 const uint32_t ntz = (dz + tz - 1u) / tz;
240 FusedOpDecl d;
241 d.strategy = FusionStrategy::WarpRegister;
242 d.include_header = "fused/fused_block/warp_fusion.cuh";
243 d.elems_per_lane = 2;
244 // ti_op_name (decode): identity mode only. Measured 2026-09-08: the
245 // thread-independent DELTA decode (ThreadTiledLorenzo{2,3}DPredictor's
246 // unpredict_and_write chase) is a real regression vs the existing
247 // warp-cooperative decode (NYX/temperature: 194-374 GB/s vs ~356-651 GB/s
248 // before). Root-caused via `sudo ncu` (unprivileged ncu is blocked here by
249 // ERR_NVGPUCTRPERM, but sudo with the full binary path works): registers
250 // (32/thread), occupancy (~46%), and shared memory are near-identical to the
251 // fast identity kernel -- none of those are the bottleneck. The one metric
252 // that diverges is average latency per issued instruction: 52 cycles (delta)
253 // vs 18 (identity). The chase is a genuine SERIAL PREFIX SUM (each `cur =
254 // pred + d[i]` needs the exact previous result, so the compiler cannot
255 // reorder or pipeline it) -- unlike cost()/pack()'s reductions/independent
256 // per-element ops, which parallelize even written as a "serial" loop. That is
257 // the real distinction between why compress always won (reduction-shaped) and
258 // decode only won for identity (no chase at all). The IDENTITY (no-delta,
259 // fixed mode) decode has no such chain and measured a clear win (591 GB/s vs
260 // ~410-495 before on the SAME field) -- see
261 // reports/w2_ti_decode_design.md UPDATE 2 for the full ncu numbers and the
262 // scoped fix direction (software-pipelining multiple tiles' chases within one
263 // thread to hide the dependency latency via intra-thread ILP). Forward
264 // (compress) ti_op_name is unaffected: it is set for BOTH modes above and
265 // both measured real wins there.
266 if (tz == 1u) { // 2-D
267 d.op_name = predict_ ? "TiledLorenzo2DPredictor" : "TiledLorenzoIdentity2DPredictor";
268 if (!predict_) d.ti_op_name = "ThreadTiledLorenzoIdentity2DPredictor";
269 d.n_ab = static_cast<size_t>(ntx) * nty * tx * ty;
270 fused::warp::TiledLorenzo2DParams p{0.0f, dx, dy, tx, ty, ntx}; // inv2eb unused on decode
271 d.params.resize(sizeof(p)); std::memcpy(d.params.data(), &p, sizeof(p));
272 } else { // 3-D
273 d.op_name = predict_ ? "TiledLorenzo3DPredictor" : "TiledLorenzoIdentity3DPredictor";
274 if (!predict_) d.ti_op_name = "ThreadTiledLorenzoIdentity3DPredictor";
275 d.n_ab = static_cast<size_t>(ntx) * nty * ntz * tx * ty * tz;
276 fused::warp::TiledLorenzo3DParams p{0.0f, dx, dy, dz, tx, ty, tz, ntx, nty};
277 d.params.resize(sizeof(p)); std::memcpy(d.params.data(), &p, sizeof(p));
278 }
279 return d;
280 }
281
286 size_t getFusedInverseElementCount() const override {
287 if (!getInverseFusionSpec().fusable()) return 0;
288 return static_cast<size_t>(dims_[0]) * dims_[1] * dims_[2];
289 }
290
291 std::vector<size_t> estimateOutputSizes(
292 const std::vector<size_t>& input_sizes
293 ) const override {
294 // Forward: natural n -> padded tile-major (num_tiles * tile_elems).
295 // Inverse: padded tile-major -> natural n.
296 const size_t n = naturalElems(input_sizes);
297 const size_t out_elems = is_inverse_ ? n : paddedElems(n);
298 return {out_elems * sizeof(T)};
299 }
300
301 std::unordered_map<std::string, size_t>
302 getActualOutputSizesByName() const override {
303 return {{"output", actual_output_size_}};
304 }
305 size_t getActualOutputSize(int index) const override {
306 return (index == 0) ? actual_output_size_ : 0;
307 }
308
309 uint16_t getStageTypeId() const override {
310 return static_cast<uint16_t>(StageType::TILED_LORENZO);
311 }
312
313 uint8_t getOutputDataType(size_t /*output_index*/) const override {
314 return static_cast<uint8_t>(getElementDataType());
315 }
316 uint8_t getInputDataType(size_t /*input_index*/) const override {
317 return static_cast<uint8_t>(getElementDataType());
318 }
319
320 size_t serializeHeader(size_t /*output_index*/, uint8_t* buf, size_t max_size) const override {
321 if (max_size < sizeof(TiledLorenzoConfig))
322 throw std::runtime_error("TiledLorenzoStage: header buffer too small");
323 auto t = effectiveTile();
325 cfg.data_type = getElementDataType();
326 cfg.ndim = static_cast<uint8_t>(ndim());
327 cfg.tile_x = static_cast<uint8_t>(t[0]);
328 cfg.tile_y = static_cast<uint8_t>(t[1]);
329 cfg.tile_z = static_cast<uint8_t>(t[2]);
330 cfg.no_delta = predict_ ? 0 : 1;
331 cfg.dim_x = static_cast<uint32_t>(dims_[0]);
332 cfg.dim_y = static_cast<uint32_t>(dims_[1]);
333 cfg.dim_z = static_cast<uint32_t>(dims_[2]);
334 std::memcpy(buf, &cfg, sizeof(cfg));
335 return sizeof(cfg);
336 }
337
338 void deserializeHeader(const uint8_t* buf, size_t size) override {
339 if (size < sizeof(TiledLorenzoConfig))
340 throw std::runtime_error("TiledLorenzoStage: header too small");
342 std::memcpy(&cfg, buf, sizeof(cfg));
343 int eff_ndim = (cfg.ndim == 0) ? 1 : static_cast<int>(cfg.ndim);
344 dims_[0] = cfg.dim_x;
345 dims_[1] = (eff_ndim >= 2) ? cfg.dim_y : 1;
346 dims_[2] = (eff_ndim >= 3) ? cfg.dim_z : 1;
347 tile_ = {cfg.tile_x, cfg.tile_y, cfg.tile_z};
348 tile_set_ = (cfg.tile_x != 0);
349 predict_ = (cfg.no_delta == 0); // 0 = delta (legacy default), 1 = no-delta
350 }
351
352 size_t getMaxHeaderSize(size_t /*output_index*/) const override {
353 return sizeof(TiledLorenzoConfig);
354 }
355
356private:
357 bool is_inverse_ = false;
358 bool predict_ = true;
359 bool dims_pinned_ = false;
360 size_t actual_output_size_ = 0;
361 std::array<size_t, 3> dims_ = {0, 1, 1};
362 std::array<uint32_t, 3> tile_ = {8, 8, 1};
363 bool tile_set_ = false;
364
366 std::array<uint32_t, 3> effectiveTile() const {
367 if (tile_set_) {
368 return {tile_[0] ? tile_[0] : 1u,
369 tile_[1] ? tile_[1] : 1u,
370 tile_[2] ? tile_[2] : 1u};
371 }
372 switch (ndim()) {
373 case 3: return {4, 4, 4};
374 case 2: return {8, 8, 1};
375 default: return {64, 1, 1};
376 }
377 }
378
380 size_t naturalElems(const std::vector<size_t>& input_sizes) const {
381 if (dims_[0] > 0) return dims_[0] * dims_[1] * dims_[2];
382 return input_sizes.empty() ? 0 : input_sizes[0] / sizeof(T);
383 }
384
390 size_t paddedElems(size_t natural_n) const {
391 auto t = effectiveTile();
392 const size_t dx = (dims_[0] > 0) ? dims_[0] : natural_n;
393 const size_t dy = (dims_[0] > 0) ? dims_[1] : 1;
394 const size_t dz = (dims_[0] > 0) ? dims_[2] : 1;
395 if (dx == 0) return 0;
396 const size_t ntx = (dx + t[0] - 1) / t[0];
397 const size_t nty = (dy + t[1] - 1) / t[1];
398 const size_t ntz = (dz + t[2] - 1) / t[2];
399 return ntx * nty * ntz * (size_t)t[0] * t[1] * t[2];
400 }
401
402 static DataType getElementDataType() {
403 if (std::is_same<T, int16_t>::value) return DataType::INT16;
404 return DataType::INT32;
405 }
406};
407
408extern template class TiledLorenzoStage<int16_t>;
409extern template class TiledLorenzoStage<int32_t>;
410
411} // namespace fz
Definition mempool.h:82
Definition stage.h:31
Definition tiled_lorenzo_stage.h:74
void deserializeHeader(const uint8_t *buf, size_t size) override
Definition tiled_lorenzo_stage.h:338
FusedOpDecl getFusedOp() const override
Definition tiled_lorenzo_stage.h:189
std::string getName() const override
Definition tiled_lorenzo_stage.h:167
uint16_t getStageTypeId() const override
Definition tiled_lorenzo_stage.h:309
size_t getFusedInverseElementCount() const override
Definition tiled_lorenzo_stage.h:286
FusionSpec getInverseFusionSpec() const override
Definition tiled_lorenzo_stage.h:224
uint32_t getTileElems() const
Elements per tile = the AdaptiveBitpack block_size that aligns blocks to tiles.
Definition tiled_lorenzo_stage.h:148
size_t getActualOutputSize(int index) const override
Definition tiled_lorenzo_stage.h:305
FusionSpec getFusionSpec() const override
Definition tiled_lorenzo_stage.h:174
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
std::vector< size_t > estimateOutputSizes(const std::vector< size_t > &input_sizes) const override
Definition tiled_lorenzo_stage.h:291
void setDims(const std::array< size_t, 3 > &dims) override
Definition tiled_lorenzo_stage.h:86
uint8_t getInputDataType(size_t) const override
Definition tiled_lorenzo_stage.h:316
void setDimsOverride(size_t x, size_t y, size_t z)
Definition tiled_lorenzo_stage.h:108
void setPredict(bool p)
Definition tiled_lorenzo_stage.h:144
uint8_t getOutputDataType(size_t) const override
Definition tiled_lorenzo_stage.h:313
void setTileShape(uint32_t tx, uint32_t ty=1, uint32_t tz=1)
Definition tiled_lorenzo_stage.h:119
void setInverse(bool inv) override
Definition tiled_lorenzo_stage.h:81
size_t getMaxHeaderSize(size_t) const override
Definition tiled_lorenzo_stage.h:352
FusedOpDecl getInverseFusedOp() const override
Definition tiled_lorenzo_stage.h:230
std::unordered_map< std::string, size_t > getActualOutputSizesByName() const override
Definition tiled_lorenzo_stage.h:302
size_t serializeHeader(size_t, uint8_t *buf, size_t max_size) const override
Definition tiled_lorenzo_stage.h:320
FZM binary file format definitions — structs, enums, and helpers.
Definition dag.h:24
constexpr size_t FZM_STAGE_CONFIG_SIZE
Per-stage serialized config slot (bytes)
Definition fzm_format.h:65
@ TILED_LORENZO
Dimension-aware (tiled separable) Lorenzo predictor (cuSZp3 delta)
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 tiled_lorenzo_stage.h:39
uint8_t reserved[2]
Must be zero.
Definition tiled_lorenzo_stage.h:48
uint32_t dim_z
Z dimension (1 for 1-D/2-D).
Definition tiled_lorenzo_stage.h:51
uint8_t no_delta
Definition tiled_lorenzo_stage.h:45
uint8_t tile_z
Tile extent in z (1 for 1-D/2-D).
Definition tiled_lorenzo_stage.h:44
DataType data_type
Signed integer element type (1B): INT16 / INT32.
Definition tiled_lorenzo_stage.h:40
uint8_t tile_y
Tile extent in y (1 for 1-D).
Definition tiled_lorenzo_stage.h:43
uint8_t tile_x
Tile extent in x (fast dim).
Definition tiled_lorenzo_stage.h:42
uint32_t dim_y
Y dimension (1 for 1-D).
Definition tiled_lorenzo_stage.h:50
uint8_t ndim
Spatial dimensionality 1/2/3.
Definition tiled_lorenzo_stage.h:41
uint32_t dim_x
X (fast) dimension.
Definition tiled_lorenzo_stage.h:49
cuSZp3: linear-ABS quant + 2-D separable tiled Lorenzo (tz == 1).
Definition warp_op_params.h:45
cuSZp3: linear-ABS quant + 3-D separable tiled Lorenzo (tz > 1). PROTOTYPE.
Definition warp_op_params.h:53
Backend-neutral GPU type aliases.
POD parameter blocks for the warp-register predictor policies.