SplatForge QAT-PLY v1

Bit-level specification for the on-disk PLY-with-quantized-fields format produced by every SplatForge codec preset (splatforge-qat-scaffold, splatforge-qat-bundle, splatforge-qat-3dgs). Any third-party Gaussian-Splat renderer can decode a SplatForge-compressed PLY bit-exactly by following this document — no runtime dependency on SplatForge code is required.

Version 1 · status frozen · reference C99 decoder: apps/codec/qat-ply-c/ · conformance suite: apps/codec/conformance/

1 · Overview

QAT-PLY v1 is a strict superset of the canonical Stanford PLY 1.0 binary little-endian format. Every QAT-PLY v1 file is a valid PLY file: legacy readers that ignore comment lines see a standard PLY with no surprises. The quantization layer is carried entirely in:

  1. Header markerscomment quantized_field ... lines that declare each quantized field's dtype, channel count, and (for per-channel scales) the base64-encoded scale array.
  2. Body columns — per-vertex char (signed 8-bit) columns named <field>_q_<i> that carry the quantized values directly, plus (for per-anchor scaled int4 fields) a per-vertex float column named <field>_scale.

A complementary header marker comment constant_field <name> <hex> carries columns whose value is fp32-constant across all vertices, but constant-field handling is orthogonal to this spec.

The grammar of one quantized-field declaration:

comment quantized_field <NAME> <DTYPE> channels=<C> [scale_b64=<BASE64>] [scale_kind=<KIND>] [packed_per_byte=<N>]

Decoders MUST tolerate unknown key=value tokens for forward compatibility. Tokens MUST be separated by a single ASCII space or tab. Line endings MAY be LF or CRLF; decoders strip a trailing CR before parsing. The header is terminated by an end_header line per PLY 1.0.

2.1 · Example markers

comment quantized_field f_anchor_feat int8 channels=32 scale_b64=zczMPc3MTD6amZk+...
comment quantized_field f_offset int4 channels=30 packed_per_byte=2 scale_kind=per_anchor

3 · Body layout

The PLY body follows the format binary_little_endian 1.0 declared in the standard PLY header. Element order, property order, and row layout are all standard PLY — quantized columns are simply additional property char (signed 8-bit) and property float declarations.

3.1 · int8 layout

For a field declared int8 channels=C, the body carries exactly C consecutive property char <NAME>_q_<i> columns for i = 0 .. C-1. Each value is a signed int8 in the range [-128, 127]. The PLY type token MAY be char or uchar: both refer to the same 1-byte storage and the decoder reinterprets the byte as signed.

3.2 · int4 layout

For a field declared int4 channels=C packed_per_byte=2:

4 · Quantization math

Dequantization is symmetric (zero-point = 0) and stateless. The decoder computes:

4.1 · int8 per-channel

signed_q = (int8) byte           // [-128, 127]
output[row][c] = (float) signed_q * scale[c]

4.2 · int4 per-anchor

byte_u   = (uint8) byte          // [0, 255]
nibble   = (c % 2 == 0) ? (byte_u & 0x0F) : ((byte_u >> 4) & 0x0F)
signed_q = (int) nibble - 8       // [-8, 7]
output[row][c] = (float) signed_q * scale[row]

All arithmetic is fp32. (float) signed_q is exact for every value in the encoded range; the only rounding step is the final fp32 multiply, which rounds-half-to-even per IEEE-754. Decoders that perform the multiplication in fp64 and round the result to fp32 at storage time produce the same byte output and are conformant.

The scale values in the scale_b64 block are stored as fp32, so a decoder that reads them into fp64 variables MUST first round them through fp32 before multiplying, otherwise its byte output may differ from the reference by 1 ULP. The reference C decoder reads scales as float and performs all arithmetic in fp32, sidestepping the issue.

5 · Scale-block encoding

For per-channel scales, the scale_b64 token's value is base64-encoded according to RFC 4648 §4 (standard alphabet, padding required). Decoded byte length MUST equal 4 * channels. The decoded bytes are interpreted as a contiguous little-endian fp32 array of length channels.

PositionByte (hex)Meaning
0..3xx xx xx xxscale[0] as little-endian fp32
4..7xx xx xx xxscale[1]
.........
4(C-1)..4C-1xx xx xx xxscale[C-1]

6 · Worked example

A 1-anchor, 3-channel int8 field f_anchor_feat. The scales are [0.1, 0.25, 0.5]. The quantized values for the single anchor are [-50, 10, 127].

6.1 · Header marker

The three scales as little-endian fp32 bytes: cd cc cc 3d  00 00 80 3e  00 00 00 3f (the first 4 bytes are 0x3DCCCCCD = fp32 0.1; next four are 0.25; last four 0.5).

Base64-encoding those 12 bytes yields zczMPQAAgD4AAAA/. The full marker reads:

comment quantized_field f_anchor_feat int8 channels=3 scale_b64=zczMPQAAgD4AAAA/

6.2 · Body bytes

With x, y, z all 0.0 and the three quant columns declared as property char f_anchor_feat_q_0..2, the single 15-byte vertex record is:

00 00 00 00   00 00 00 00   00 00 00 00   ce   0a   7f
[--- x ---]   [--- y ---]   [--- z ---]   q0   q1   q2

where 0xce = -50 (signed int8), 0x0a = 10, and 0x7f = 127.

6.3 · Dequant

The reference computation is:

(-50) * fp32(0.1)  = -5.0
( 10) * fp32(0.25) =  2.5
(127) * fp32(0.5)  = 63.5

The resulting fp32 little-endian bytes are:

00 00 a0 c0   00 00 20 40   00 00 7e 42
[ -5.0   ]   [  2.5   ]   [ 63.5    ]

A conforming decoder MUST produce exactly those 12 bytes when reconstructing the three fp32 values. The conformance test case01_int8_1x1.ply exercises a single-element variant of this example; case02_int8_4x3.ply extends it to four anchors.

7 · Conformance

The conformance suite at apps/codec/conformance/ ships ten test fixtures covering the full v1 surface:

CaseWhat it tests
case01_int8_1x1minimal int8: 1 anchor, 1 channel
case02_int8_4x3typical int8: 4 anchors, 3 channels, mixed signs
case03_int8_5x32realistic int8: 5 anchors, 32 channels (Scaffold-GS anchor-feat shape)
case04_int4_1x4minimal int4: 1 anchor, 4 channels, full nibble range
case05_int4_3x30typical int4: 3 anchors, 30 channels (Scaffold-GS f_offset shape)
case06_int4_2x5_oddint4 with odd channel count (5) — exercises the high-nibble-zero rule
case07_mixedboth int8 and int4 fields in one PLY
case08_int8_zeroall-zero quantized values (degenerate but legal)
case09_int8_extreme_scalesscales spanning 1e-10 .. 1e10
case10_int8_with_constantquantized_field plus a constant_field marker

Each fixture ships with a JSON assertion (in conformance.json) listing the expected dequantized fp32 output as a base64-encoded byte string. A decoder is conformant when its dequant output matches each fixture's expected_fp32_b64 byte-for-byte.

The verifier script verify.py cross-checks two independent reference decoders (Python + C99) against every fixture; both currently agree byte-for-byte across all 10 cases.

8 · Versioning

Version 1 is identified solely by the presence of quantized_field comments and the int8 / int4 dtype tokens. Future versions will add an explicit version token (e.g. quantized_field f_x int8 v=2 ...) so v1 decoders MUST reject any quantized_field token whose dtype is not in {int8, int4} and MAY skip tokens with unknown key=value pairs (forward compatibility).

The next iteration (v2, planned) is expected to introduce: int2 / non-symmetric quant (asymmetric zero-point), per-block scale tables (instead of per-channel / per-anchor), and an optional CRC32 footer over the body for tamper detection.

9 · Reference implementations

All four runtime implementations (C / WASM / Metal / Vulkan) are cross-checked against the Python reference and the same JSON expectations under conformance/cross-target/; any deviation breaks CI. Bindings for additional languages and runtimes are tracked at label:qat-ply-v1.

← back to Try it  ·  QAT-3DGS (Inria 3DGS single-PLY)  ·  QAT-Bundle (Scaffold-GS retrain)  ·  QAT-Scaffold