ptwm 1.0.1__tar.gz → 1.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. {ptwm-1.0.1 → ptwm-1.1.0}/Cargo.lock +2 -2
  2. {ptwm-1.0.1 → ptwm-1.1.0}/Cargo.toml +1 -1
  3. {ptwm-1.0.1 → ptwm-1.1.0}/PKG-INFO +3 -3
  4. {ptwm-1.0.1 → ptwm-1.1.0}/README.md +2 -2
  5. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/arithmetic.rs +52 -13
  6. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/huff_llm.rs +2 -2
  7. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/huffman.rs +6 -5
  8. ptwm-1.1.0/crates/ptwm-core/src/codecs/length_prefix.rs +89 -0
  9. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/mod.rs +1 -0
  10. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/order1_arithmetic.rs +5 -1
  11. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/order1_scale_ac.rs +16 -15
  12. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/per_group_codebook.rs +150 -9
  13. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/rans.rs +39 -12
  14. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/tans.rs +33 -6
  15. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/compressor.rs +58 -17
  16. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/container.rs +1 -1
  17. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/discovery/mod.rs +7 -5
  18. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/dispatch.rs +8 -9
  19. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/context_mixing/model.rs +1 -1
  20. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/context_mixing/squash.rs +1 -1
  21. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/id.rs +52 -0
  22. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/mod.rs +2 -0
  23. ptwm-1.1.0/crates/ptwm-core/src/extension/resolve.rs +194 -0
  24. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/flavor/mod.rs +13 -4
  25. ptwm-1.1.0/crates/ptwm-core/src/flavor/native.rs +833 -0
  26. ptwm-1.1.0/crates/ptwm-core/src/flavor/router.rs +1532 -0
  27. ptwm-1.1.0/crates/ptwm-core/src/flavor/third_party.rs +225 -0
  28. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/flavor/wasm.rs +384 -10
  29. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/policy/capability_check.rs +119 -0
  30. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transcode/base64.rs +1 -1
  31. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transcode/manifest.rs +1 -1
  32. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/mxfp4_deinterleave.rs +11 -2
  33. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/op.rs +19 -12
  34. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/predictor_xor.rs +1 -1
  35. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/source.rs +81 -9
  36. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/trust/org.rs +1 -1
  37. ptwm-1.1.0/crates/ptwm-core/tests/cuda_plane_codec_rust_only.rs +16 -0
  38. ptwm-1.1.0/crates/ptwm-core/tests/hardware_backend_router_native.rs +207 -0
  39. ptwm-1.1.0/crates/ptwm-py/src/delta_scheme.rs +78 -0
  40. ptwm-1.1.0/crates/ptwm-py/src/device_buffer.rs +466 -0
  41. ptwm-1.1.0/crates/ptwm-py/src/hardware.rs +166 -0
  42. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-py/src/lib.rs +7 -0
  43. ptwm-1.1.0/crates/ptwm-py/src/plane_codec_cuda.rs +196 -0
  44. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-py/src/policy.rs +15 -1
  45. {ptwm-1.0.1 → ptwm-1.1.0}/pyproject.toml +36 -31
  46. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/_config.py +4 -0
  47. ptwm-1.1.0/python/ptwm/_rust/delta_scheme.py +13 -0
  48. ptwm-1.1.0/python/ptwm/_rust/hardware.py +13 -0
  49. ptwm-1.1.0/python/ptwm/_rust/plane_codec_cuda.py +17 -0
  50. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/chains.py +11 -8
  51. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/compress.py +56 -1
  52. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/decompress.py +29 -4
  53. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/ext.py +16 -2
  54. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/core/_compressor.py +24 -0
  55. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/core/_decompressor.py +20 -0
  56. ptwm-1.1.0/python/ptwm/ext_tooling/_templates/rust/_kind_delta_scheme/Cargo.toml +12 -0
  57. ptwm-1.1.0/python/ptwm/ext_tooling/_templates/rust/_kind_delta_scheme/manifest.toml +14 -0
  58. ptwm-1.1.0/python/ptwm/ext_tooling/_templates/rust/_kind_delta_scheme/src/lib.rs +128 -0
  59. ptwm-1.1.0/python/ptwm/ext_tooling/_templates/rust/_kind_hardware_backend/Cargo.toml +12 -0
  60. ptwm-1.1.0/python/ptwm/ext_tooling/_templates/rust/_kind_hardware_backend/manifest.toml +14 -0
  61. ptwm-1.1.0/python/ptwm/ext_tooling/_templates/rust/_kind_hardware_backend/src/lib.rs +88 -0
  62. {ptwm-1.0.1/python/ptwm/ext_tooling/_templates/zig → ptwm-1.1.0/python/ptwm/ext_tooling/_templates/rust}/manifest.toml +1 -1
  63. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/build.py +42 -6
  64. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/init.py +23 -3
  65. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/pack.py +10 -2
  66. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/integrations/_compress_safetensors.py +38 -18
  67. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/preprocessing/__init__.py +4 -0
  68. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/preprocessing/_cache.py +61 -13
  69. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/preprocessing/_chains.py +1 -1
  70. ptwm-1.0.1/crates/ptwm-core/src/flavor/native.rs +0 -313
  71. ptwm-1.0.1/crates/ptwm-core/src/flavor/router.rs +0 -576
  72. ptwm-1.0.1/crates/ptwm-core/src/flavor/third_party.rs +0 -92
  73. {ptwm-1.0.1 → ptwm-1.1.0}/LICENSE +0 -0
  74. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/Cargo.toml +0 -0
  75. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/build.rs +0 -0
  76. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/data/bundled-keys.toml +0 -0
  77. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/data/vendor_table.toml +0 -0
  78. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/chain/dispatch.rs +0 -0
  79. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/chain/graph.rs +0 -0
  80. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/chain/mod.rs +0 -0
  81. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/chain/runtime.rs +0 -0
  82. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/chain/validate.rs +0 -0
  83. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/chain/wire.rs +0 -0
  84. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codec.rs +0 -0
  85. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codec_tagged.rs +0 -0
  86. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/context_mixing.rs +0 -0
  87. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/fpc.rs +0 -0
  88. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/identity.rs +0 -0
  89. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/neural_predictor.rs +0 -0
  90. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/zstd.rs +0 -0
  91. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/codecs/zstd_dict.rs +0 -0
  92. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/delta.rs +0 -0
  93. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/dtype.rs +0 -0
  94. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/arithmetic/mod.rs +0 -0
  95. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/arithmetic/model.rs +0 -0
  96. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/context_mixing/coder.rs +0 -0
  97. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/context_mixing/mixer.rs +0 -0
  98. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/context_mixing/mod.rs +0 -0
  99. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/huff_llm/group_codec.rs +0 -0
  100. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/huff_llm/mod.rs +0 -0
  101. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/huffman/bitstream.rs +0 -0
  102. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/huffman/compress.rs +0 -0
  103. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/huffman/decompress.rs +0 -0
  104. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/huffman/mod.rs +0 -0
  105. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/huffman/tree.rs +0 -0
  106. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/huffman/weights.rs +0 -0
  107. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/mod.rs +0 -0
  108. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/neural_predictor/coder.rs +0 -0
  109. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/neural_predictor/mod.rs +0 -0
  110. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/neural_predictor/net.rs +0 -0
  111. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/outcome.rs +0 -0
  112. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/rans/compress.rs +0 -0
  113. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/rans/decompress.rs +0 -0
  114. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/rans/mod.rs +0 -0
  115. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/stream_frame.rs +0 -0
  116. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/entropy/tans/mod.rs +0 -0
  117. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/error.rs +0 -0
  118. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/builder.rs +0 -0
  119. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/builtins.rs +0 -0
  120. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/capability.rs +0 -0
  121. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/dispatch.rs +0 -0
  122. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/error.rs +0 -0
  123. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/kind.rs +0 -0
  124. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/lifecycle.rs +0 -0
  125. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/manifest.rs +0 -0
  126. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/extension/table.rs +0 -0
  127. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/fit/mod.rs +0 -0
  128. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/fit/order1.rs +0 -0
  129. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/fit/per_group_codebook.rs +0 -0
  130. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/flavor/abi.rs +0 -0
  131. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/flavor/host.rs +0 -0
  132. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/header.rs +0 -0
  133. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/index.rs +0 -0
  134. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/install/git.rs +0 -0
  135. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/install/https.rs +0 -0
  136. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/install/local.rs +0 -0
  137. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/install/mod.rs +0 -0
  138. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/install/oci.rs +0 -0
  139. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/install/pip.rs +0 -0
  140. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/layout.rs +0 -0
  141. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/lib.rs +0 -0
  142. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/metadata.rs +0 -0
  143. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/plane_record.rs +0 -0
  144. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/policy/file.rs +0 -0
  145. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/policy/mod.rs +0 -0
  146. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/policy/native_deps.rs +0 -0
  147. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/policy/resolve.rs +0 -0
  148. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/prelude.rs +0 -0
  149. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/quantize.rs +0 -0
  150. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/range_coder.rs +0 -0
  151. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/select.rs +0 -0
  152. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/split/dtype16.rs +0 -0
  153. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/split/dtype32.rs +0 -0
  154. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/split/dtype64.rs +0 -0
  155. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/split/dtype8.rs +0 -0
  156. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/split/mod.rs +0 -0
  157. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/tensor_record.rs +0 -0
  158. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transcode/jcs.rs +0 -0
  159. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transcode/mod.rs +0 -0
  160. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transcode/tests.rs +0 -0
  161. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/alpha_stable_normalize.rs +0 -0
  162. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/bit_reorder_fp8.rs +0 -0
  163. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/bit_reorder_ieee.rs +0 -0
  164. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/block_microscaling_repack.rs +0 -0
  165. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/burrows_wheeler.rs +0 -0
  166. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/byte_passthrough.rs +0 -0
  167. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/byte_split.rs +0 -0
  168. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/concat.rs +0 -0
  169. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/delta.rs +0 -0
  170. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/entropy.rs +0 -0
  171. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/fusion.rs +0 -0
  172. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/index_pack.rs +0 -0
  173. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/int_delta.rs +0 -0
  174. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/mantissa_zero.rs +0 -0
  175. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/mod.rs +0 -0
  176. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/move_to_front.rs +0 -0
  177. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/nibble_split.rs +0 -0
  178. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/reshape.rs +0 -0
  179. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/spherical_normalize.rs +0 -0
  180. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/transforms/terminal.rs +0 -0
  181. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/trust/bundled.rs +0 -0
  182. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/trust/keyring.rs +0 -0
  183. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/trust/mod.rs +0 -0
  184. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/trust/signature.rs +0 -0
  185. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/trust/verifier.rs +0 -0
  186. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/types/descriptor.rs +0 -0
  187. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/types/mod.rs +0 -0
  188. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/types/plane_role.rs +0 -0
  189. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-core/src/types/role.rs +0 -0
  190. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-py/Cargo.toml +0 -0
  191. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-py/src/ext.rs +0 -0
  192. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-py/src/host_flavor.rs +0 -0
  193. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-py/src/inspect.rs +0 -0
  194. {ptwm-1.0.1 → ptwm-1.1.0}/crates/ptwm-py/src/trust.rs +0 -0
  195. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/__init__.py +0 -0
  196. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/_exceptions.py +0 -0
  197. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/_loader.py +0 -0
  198. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/_rust/__init__.py +0 -0
  199. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/_rust/_errors.py +0 -0
  200. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/_rust/ext.py +0 -0
  201. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/_rust/host.py +0 -0
  202. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/_rust/policy.py +0 -0
  203. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/_rust/trust.py +0 -0
  204. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ablation/__init__.py +0 -0
  205. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ablation/_attribute.py +0 -0
  206. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ablation/_bench.py +0 -0
  207. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ablation/_compare.py +0 -0
  208. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ablation/_replay.py +0 -0
  209. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ablation/_resolve.py +0 -0
  210. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/classify/__init__.py +0 -0
  211. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/classify/_audit.py +0 -0
  212. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/classify/_chain.py +0 -0
  213. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/classify/_flags.py +0 -0
  214. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/classify/_heuristic.py +0 -0
  215. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/classify/_hf_quant.py +0 -0
  216. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/classify/_protocol.py +0 -0
  217. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/classify/_ptwm_config.py +0 -0
  218. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/classify/_role.py +0 -0
  219. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/README.md +0 -0
  220. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/__init__.py +0 -0
  221. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/bench.py +0 -0
  222. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/main.py +0 -0
  223. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/policy.py +0 -0
  224. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/trust.py +0 -0
  225. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/cli/utils.py +0 -0
  226. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/codecs/__init__.py +0 -0
  227. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/core/__init__.py +0 -0
  228. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/delta/__init__.py +0 -0
  229. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/entropy/__init__.py +0 -0
  230. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/__init__.py +0 -0
  231. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/_templates/assemblyscript/manifest.toml +0 -0
  232. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/_templates/assemblyscript/package.json +0 -0
  233. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/_templates/assemblyscript/src/index.ts +0 -0
  234. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/_templates/c/Makefile +0 -0
  235. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/_templates/c/manifest.toml +0 -0
  236. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/_templates/c/src/lib.c +0 -0
  237. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/_templates/rust/Cargo.toml +0 -0
  238. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/_templates/rust/src/lib.rs +0 -0
  239. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/_templates/zig/build.zig +0 -0
  240. {ptwm-1.0.1/python/ptwm/ext_tooling/_templates/rust → ptwm-1.1.0/python/ptwm/ext_tooling/_templates/zig}/manifest.toml +0 -0
  241. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/_templates/zig/src/main.zig +0 -0
  242. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/ext_tooling/sign.py +0 -0
  243. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/integrations/__init__.py +0 -0
  244. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/integrations/_hf.py +0 -0
  245. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/integrations/_safetensors.py +0 -0
  246. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/preprocessing/_explorer.py +0 -0
  247. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/preprocessing/_plane.py +0 -0
  248. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/random_access/__init__.py +0 -0
  249. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/sharding/__init__.py +0 -0
  250. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/sharding/_index.py +0 -0
  251. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/sharding/_reader.py +0 -0
  252. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/sharding/_sharder.py +0 -0
  253. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/sharding/_writer.py +0 -0
  254. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/stores/__init__.py +0 -0
  255. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/stores/_common.py +0 -0
  256. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/stores/_lmdb.py +0 -0
  257. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/stores/_webdataset.py +0 -0
  258. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/utils/__init__.py +0 -0
  259. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/utils/_decode.py +0 -0
  260. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/utils/_patch.py +0 -0
  261. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/utils/_safetensors.py +0 -0
  262. {ptwm-1.0.1 → ptwm-1.1.0}/python/ptwm/utils/_torch.py +0 -0
@@ -1945,7 +1945,7 @@ dependencies = [
1945
1945
 
1946
1946
  [[package]]
1947
1947
  name = "ptwm-core"
1948
- version = "1.0.1"
1948
+ version = "1.1.0"
1949
1949
  dependencies = [
1950
1950
  "blake3",
1951
1951
  "ciborium",
@@ -1980,7 +1980,7 @@ dependencies = [
1980
1980
 
1981
1981
  [[package]]
1982
1982
  name = "ptwm-py"
1983
- version = "1.0.1"
1983
+ version = "1.1.0"
1984
1984
  dependencies = [
1985
1985
  "hex",
1986
1986
  "memmap2",
@@ -3,7 +3,7 @@ resolver = "2"
3
3
  members = ["crates/ptwm-core", "crates/ptwm-py"]
4
4
 
5
5
  [workspace.package]
6
- version = "1.0.1"
6
+ version = "1.1.0"
7
7
  edition = "2024"
8
8
  rust-version = "1.87"
9
9
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ptwm
3
- Version: 1.0.1
3
+ Version: 1.1.0
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Intended Audience :: Developers
6
6
  Classifier: Intended Audience :: Science/Research
@@ -354,8 +354,8 @@ fields; see its docstring for the full list.
354
354
  | `bfloat16` / `float16` | Bit reorder + 2-plane byte split | Exponent → plane 1 |
355
355
  | `float8_e4m3fn` / `float8_e5m2` | Bit reorder + 2-plane nibble split | Exponent → high nibble |
356
356
  | `float4_e2m1fn_x2` (weight nibbles) | Passthrough, per-plane trial-encode | `PerGroupCodebook` competes against Huffman / rANS on the nibble-packed byte stream |
357
- | MXFP4 scale (`uint8`, E8M0) | Passthrough, per-plane trial-encode | Pure 8-bit exponent with no row structure to exploit; compresses to ≈ 0.16 of original via the generic codec menu |
358
- | NVFP4 scale (FP8-E4M3) | `Order1ScaleAC` (per-row lag-1 arithmetic coding) | Mantissa bits give exploitable row autocorrelation that E8M0 lacks |
357
+ | MXFP4 scale (`uint8`, E8M0) | `Order1ScaleAC` (per-row lag-1 arithmetic coding), per-plane trial-encode | Rows are autocorrelated: on gpt-oss-20b the entropy given the previous scale in the row is 1.20 bits, against 1.89 bits unconditioned |
358
+ | NVFP4 scale (FP8-E4M3) | `Order1ScaleAC` (per-row lag-1 arithmetic coding) | Mantissa bits give exploitable row autocorrelation |
359
359
  | `int8`, `uint8`, `int16`, `int32`, `int64`, `bool` | Single stream | No byte split or bit reorder yet; whatever wins per-plane trial-encode on the raw bytes |
360
360
 
361
361
  MXFP4 / NVFP4 tensors are compressed as a pair: the packed FP4 weight
@@ -314,8 +314,8 @@ fields; see its docstring for the full list.
314
314
  | `bfloat16` / `float16` | Bit reorder + 2-plane byte split | Exponent → plane 1 |
315
315
  | `float8_e4m3fn` / `float8_e5m2` | Bit reorder + 2-plane nibble split | Exponent → high nibble |
316
316
  | `float4_e2m1fn_x2` (weight nibbles) | Passthrough, per-plane trial-encode | `PerGroupCodebook` competes against Huffman / rANS on the nibble-packed byte stream |
317
- | MXFP4 scale (`uint8`, E8M0) | Passthrough, per-plane trial-encode | Pure 8-bit exponent with no row structure to exploit; compresses to ≈ 0.16 of original via the generic codec menu |
318
- | NVFP4 scale (FP8-E4M3) | `Order1ScaleAC` (per-row lag-1 arithmetic coding) | Mantissa bits give exploitable row autocorrelation that E8M0 lacks |
317
+ | MXFP4 scale (`uint8`, E8M0) | `Order1ScaleAC` (per-row lag-1 arithmetic coding), per-plane trial-encode | Rows are autocorrelated: on gpt-oss-20b the entropy given the previous scale in the row is 1.20 bits, against 1.89 bits unconditioned |
318
+ | NVFP4 scale (FP8-E4M3) | `Order1ScaleAC` (per-row lag-1 arithmetic coding) | Mantissa bits give exploitable row autocorrelation |
319
319
  | `int8`, `uint8`, `int16`, `int32`, `int64`, `bool` | Single stream | No byte split or bit reorder yet; whatever wins per-plane trial-encode on the raw bytes |
320
320
 
321
321
  MXFP4 / NVFP4 tensors are compressed as a pair: the packed FP4 weight
@@ -5,6 +5,7 @@
5
5
  //! planes).
6
6
 
7
7
  use crate::codec::{CodecId, Encoded, PlaneCodec};
8
+ use crate::codecs::length_prefix;
8
9
  use crate::entropy::arithmetic::{
9
10
  Order0Adaptive, Order0Static, Order1Adaptive, decode_bytes, encode_bytes,
10
11
  };
@@ -12,22 +13,35 @@ use crate::error::PtwmCoreError;
12
13
  use crate::layout::PlaneLayout;
13
14
  use crate::types::descriptor::PlaneDescriptor;
14
15
 
15
- /// Absolute cap on the framed decoded length, mirroring `rans.rs`. Guards a
16
- /// hostile length prefix from driving a multi-GiB decode loop.
17
- const MAX_DECODED_LEN: usize = 1 << 30; // 1 GiB
18
-
19
16
  /// Prepend the true decoded length (u32 LE) so decode is self-delimiting.
20
17
  ///
18
+ /// Fails when the decoder would refuse the length (see
19
+ /// [`length_prefix::accepted`]); these coders have no raw fallback.
20
+ ///
21
21
  /// The container's `decode` receives a `decoded_len` derived from the
22
22
  /// *source-tensor* descriptor, which for byte-split planes is larger than the
23
23
  /// actual plane length. Self-delimiting codecs (Huffman, rANS, …) ignore that
24
24
  /// argument and recover the length from their own payload; the arithmetic
25
25
  /// coders do the same here rather than trusting `decoded_len`.
26
- pub(crate) fn frame_payload(decoded_len: usize, payload: &[u8]) -> Vec<u8> {
26
+ pub(crate) fn frame_payload(
27
+ decoded_len: usize,
28
+ payload: &[u8],
29
+ codec: &'static str,
30
+ ) -> Result<Vec<u8>, PtwmCoreError> {
31
+ if !length_prefix::accepted(decoded_len, payload.len()) {
32
+ return Err(PtwmCoreError::CodecEncode {
33
+ codec,
34
+ msg: format!(
35
+ "decoded length {decoded_len} with {} encoded bytes exceeds the length-prefix limit",
36
+ payload.len()
37
+ ),
38
+ });
39
+ }
27
40
  let mut out = Vec::with_capacity(4 + payload.len());
41
+ // `accepted` guarantees the length fits the u32 prefix.
28
42
  out.extend_from_slice(&(decoded_len as u32).to_le_bytes());
29
43
  out.extend_from_slice(payload);
30
- out
44
+ Ok(out)
31
45
  }
32
46
 
33
47
  /// Inverse of [`frame_payload`]: read the length prefix and return the rest.
@@ -42,13 +56,15 @@ pub(crate) fn unframe_payload<'a>(
42
56
  });
43
57
  }
44
58
  let n = u32::from_le_bytes([payload[0], payload[1], payload[2], payload[3]]) as usize;
45
- if n > MAX_DECODED_LEN {
59
+ let body = &payload[4..];
60
+ // Guards a hostile length prefix from driving a multi-GiB decode loop.
61
+ if !length_prefix::accepted(n, body.len()) {
46
62
  return Err(PtwmCoreError::CodecDecode {
47
63
  codec,
48
64
  msg: format!("implausible decoded length {n}"),
49
65
  });
50
66
  }
51
- Ok((n, &payload[4..]))
67
+ Ok((n, body))
52
68
  }
53
69
 
54
70
  /// Order-0 arithmetic coder with a serialized normalized table.
@@ -85,7 +101,11 @@ impl PlaneCodec for ArithmeticO0 {
85
101
  });
86
102
  }
87
103
  let mut model = Order0Static::fit(plane);
88
- let payload = frame_payload(plane.len(), &encode_bytes(&mut model, plane));
104
+ let payload = frame_payload(
105
+ plane.len(),
106
+ &encode_bytes(&mut model, plane),
107
+ "arithmetic_o0",
108
+ )?;
89
109
  Ok(Encoded {
90
110
  state_bytes: model.serialize(),
91
111
  state_format_version: 1,
@@ -156,7 +176,8 @@ impl PlaneCodec for ArithmeticO0Adaptive {
156
176
  let payload = frame_payload(
157
177
  plane.len(),
158
178
  &encode_bytes(&mut Order0Adaptive::new(), plane),
159
- );
179
+ "arithmetic_o0_adaptive",
180
+ )?;
160
181
  Ok(Encoded {
161
182
  state_bytes: Vec::new(),
162
183
  state_format_version: 0,
@@ -229,7 +250,8 @@ impl PlaneCodec for ArithmeticO1 {
229
250
  let payload = frame_payload(
230
251
  plane.len(),
231
252
  &encode_bytes(&mut Order1Adaptive::new(), plane),
232
- );
253
+ "arithmetic_o1",
254
+ )?;
233
255
  Ok(Encoded {
234
256
  state_bytes: Vec::new(),
235
257
  state_format_version: 0,
@@ -419,8 +441,8 @@ mod tests {
419
441
  let data: Vec<u8> = (0..262144)
420
442
  .map(|i| match i % 16 {
421
443
  0..=9 => 0x40u8,
422
- 10 | 11 | 12 => 0x41,
423
- 13 | 14 => 0x3F,
444
+ 10..=12 => 0x41,
445
+ 13..=14 => 0x3F,
424
446
  _ => 0x42,
425
447
  })
426
448
  .collect();
@@ -435,4 +457,21 @@ mod tests {
435
457
  "arithmetic O0 ({ac_total}) must not exceed Huffman ({huff_total}) on a structured plane",
436
458
  );
437
459
  }
460
+
461
+ #[test]
462
+ fn unframe_rejects_a_short_payload_declaring_a_large_plane() {
463
+ let mut payload = length_prefix::SHORT_BODY_DECLARED_LEN
464
+ .to_le_bytes()
465
+ .to_vec();
466
+ payload.extend_from_slice(&[0u8; 64]);
467
+ assert!(unframe_payload(&payload, "arithmetic_o0").is_err());
468
+ }
469
+
470
+ #[test]
471
+ fn frame_and_unframe_agree_on_the_length() {
472
+ let framed = frame_payload(5, &[1, 2, 3], "arithmetic_o0").unwrap();
473
+ let (n, body) = unframe_payload(&framed, "arithmetic_o0").unwrap();
474
+ assert_eq!((n, body), (5, &[1u8, 2, 3][..]));
475
+ assert!(frame_payload(length_prefix::RATIO_FREE_LEN + 1, &[0], "arithmetic_o0").is_err());
476
+ }
438
477
  }
@@ -64,7 +64,7 @@ impl PlaneCodec for HuffLlm5Bit {
64
64
  _layout: &PlaneLayout,
65
65
  ) -> bool {
66
66
  // Skip tiny planes (three table headers don't amortize) and odd lengths.
67
- plane.len() >= MIN_WORDS * 2 && plane.len() % 2 == 0
67
+ plane.len() >= MIN_WORDS * 2 && plane.len().is_multiple_of(2)
68
68
  }
69
69
 
70
70
  fn encode(
@@ -82,7 +82,7 @@ impl PlaneCodec for HuffLlm5Bit {
82
82
  // Empty or odd-length → raw store (still total). Short-circuit empty
83
83
  // before allocating `words`, since trial-encode hits this path on every
84
84
  // candidate.
85
- if plane.is_empty() || plane.len() % 2 != 0 {
85
+ if plane.is_empty() || !plane.len().is_multiple_of(2) {
86
86
  return Ok(raw_store(plane));
87
87
  }
88
88
  let words: Vec<u16> = plane
@@ -57,10 +57,10 @@ impl PlaneCodec for Huffman {
57
57
  }
58
58
 
59
59
  let mut buf = vec![0u8; huffman_output_bound(plane.len())];
60
- match huffman::compress_outcome(&mut buf, plane)? {
61
- CompressOutcome::Encoded(n) => {
60
+ let outcome = huffman::compress_outcome(&mut buf, plane)?;
61
+ match (outcome, u32::try_from(plane.len())) {
62
+ (CompressOutcome::Encoded(n), Ok(orig_len)) => {
62
63
  // TAG_HUF | orig_len (4 bytes LE) | huffman-encoded bytes
63
- let orig_len = plane.len() as u32;
64
64
  let mut payload = Vec::with_capacity(1 + 4 + n);
65
65
  payload.push(TAG_HUF);
66
66
  payload.extend_from_slice(&orig_len.to_le_bytes());
@@ -71,9 +71,10 @@ impl PlaneCodec for Huffman {
71
71
  payload,
72
72
  })
73
73
  }
74
- _outcome => {
74
+ _ => {
75
75
  // NotBeneficial / Incompressible / DstTooSmall /
76
- // LenOverflow → store raw
76
+ // LenOverflow, or a plane longer than the u32 length
77
+ // prefix → store raw
77
78
  let mut payload = Vec::with_capacity(1 + plane.len());
78
79
  payload.push(TAG_RAW);
79
80
  payload.extend_from_slice(plane);
@@ -0,0 +1,89 @@
1
+ //! Acceptance rule for the decoded length a plane payload declares.
2
+ //!
3
+ //! rANS, tANS, and the arithmetic coders prefix their payload with the
4
+ //! decoded length as a little-endian `u32`, and the decoder sizes its output
5
+ //! (or its decode loop) from that prefix before reading the body. A corrupted
6
+ //! or hostile prefix must not drive an arbitrarily large allocation, so the
7
+ //! decoder checks it against [`accepted`]. Encoders apply the same rule before
8
+ //! writing a prefix, so every plane they emit is one their decoder accepts.
9
+
10
+ /// Decoded length accepted at any compression ratio.
11
+ ///
12
+ /// These coders reach very high ratios on near-degenerate distributions (an
13
+ /// all-zero 64 KiB plane encodes to about 30 bytes), so below this size the
14
+ /// rule bounds the length alone.
15
+ pub(crate) const RATIO_FREE_LEN: usize = 1 << 30; // 1 GiB
16
+
17
+ /// Largest decoded-to-encoded length ratio accepted above [`RATIO_FREE_LEN`].
18
+ ///
19
+ /// A payload that declares more than [`RATIO_FREE_LEN`] bytes must carry at
20
+ /// least `1 / MAX_RATIO` of that length as encoded body, which bounds the
21
+ /// allocation a short payload can request. Weight planes this large (a BF16
22
+ /// embedding or output projection of a large-vocabulary model) compress far
23
+ /// less than this.
24
+ pub(crate) const MAX_RATIO: usize = 64;
25
+
26
+ /// Whether a payload declaring `decoded_len` bytes from an encoded body of
27
+ /// `body_len` bytes is accepted.
28
+ ///
29
+ /// Lengths that do not fit the `u32` prefix are never accepted.
30
+ pub(crate) fn accepted(decoded_len: usize, body_len: usize) -> bool {
31
+ u32::try_from(decoded_len).is_ok()
32
+ && (decoded_len <= RATIO_FREE_LEN || decoded_len <= body_len.saturating_mul(MAX_RATIO))
33
+ }
34
+
35
+ /// Bytes from a skewed distribution (about 2 bits per byte), long enough to
36
+ /// exercise lengths above [`RATIO_FREE_LEN`] while staying far below
37
+ /// [`MAX_RATIO`].
38
+ #[cfg(test)]
39
+ pub(crate) fn skewed_bytes(len: usize) -> Vec<u8> {
40
+ let mut x: u64 = 0x9E37_79B9_7F4A_7C15;
41
+ (0..len)
42
+ .map(|_| {
43
+ x ^= x << 13;
44
+ x ^= x >> 7;
45
+ x ^= x << 17;
46
+ x.trailing_zeros().min(15) as u8
47
+ })
48
+ .collect()
49
+ }
50
+
51
+ /// A payload body shorter than `1 / MAX_RATIO` of the 3 GiB it declares.
52
+ #[cfg(test)]
53
+ pub(crate) const SHORT_BODY_DECLARED_LEN: u32 = 3 << 30;
54
+
55
+ #[cfg(test)]
56
+ mod tests {
57
+ use super::*;
58
+
59
+ #[test]
60
+ fn any_ratio_is_accepted_up_to_the_ratio_free_length() {
61
+ assert!(accepted(0, 0));
62
+ assert!(accepted(RATIO_FREE_LEN, 1));
63
+ }
64
+
65
+ #[test]
66
+ fn a_large_plane_at_a_weight_like_ratio_is_accepted() {
67
+ // A BF16 embedding of a 128256-token, 8192-wide model, at the ratio
68
+ // rANS reached on it.
69
+ let decoded = 128_256 * 8192 * 2;
70
+ assert!(decoded > RATIO_FREE_LEN);
71
+ assert!(accepted(decoded, 1_611_325_931));
72
+ }
73
+
74
+ #[test]
75
+ fn a_short_body_cannot_declare_a_large_plane() {
76
+ let decoded = RATIO_FREE_LEN + 1;
77
+ assert!(!accepted(decoded, 16));
78
+ assert!(!accepted(decoded, decoded / MAX_RATIO - 1));
79
+ assert!(accepted(decoded, decoded.div_ceil(MAX_RATIO)));
80
+ }
81
+
82
+ #[test]
83
+ #[cfg(target_pointer_width = "64")]
84
+ fn lengths_beyond_the_u32_prefix_are_rejected() {
85
+ let too_long = u32::MAX as usize + 1;
86
+ assert!(!accepted(too_long, too_long));
87
+ assert!(accepted(u32::MAX as usize, u32::MAX as usize));
88
+ }
89
+ }
@@ -4,6 +4,7 @@ pub mod fpc;
4
4
  pub mod huff_llm;
5
5
  pub mod huffman;
6
6
  pub mod identity;
7
+ pub(crate) mod length_prefix;
7
8
  pub mod neural_predictor;
8
9
  pub mod order1_arithmetic;
9
10
  pub mod order1_scale_ac;
@@ -72,7 +72,11 @@ impl PlaneCodec for Order1Arithmetic {
72
72
  });
73
73
  }
74
74
  let mut model = Order1Static::fit(plane);
75
- let payload = frame_payload(plane.len(), &encode_bytes(&mut model, plane));
75
+ let payload = frame_payload(
76
+ plane.len(),
77
+ &encode_bytes(&mut model, plane),
78
+ "order1_arithmetic",
79
+ )?;
76
80
  Ok(Encoded {
77
81
  state_bytes: model.serialize(),
78
82
  state_format_version: STATE_FORMAT_VERSION,
@@ -453,16 +453,19 @@ impl PlaneCodec for Order1ScaleAC {
453
453
 
454
454
  fn accepts(&self, descriptor: &PlaneDescriptor) -> bool {
455
455
  // O1SAC requires byte-width Scale or GlobalScale planes with row layout
456
- // AND an FP8 scale format (E4M3 or E5M2). The codec models per-row
457
- // lag-1 PMFs over scale bytes — a structure that exists in FP8 scale
458
- // distributions (mantissa bits give a continuous distribution with
459
- // exploitable autocorrelation) but not in E8M0 scales, which are pure
460
- // 8-bit exponents (no sign, no mantissa) used by MXFP4 block scales.
461
- // Allow-listing E4M3/E5M2 keeps the codec inside its design domain;
462
- // future scale formats need explicit vetting before being admitted.
456
+ // and a vetted scale format. The codec models per-row lag-1 PMFs over
457
+ // scale bytes. FP8 scales (E4M3, E5M2) carry that structure, and so do
458
+ // MXFP4's E8M0 block scales: across gpt-oss-20b's 597M E8M0 scales the
459
+ // entropy conditioned on the previous scale in the row is 1.20
460
+ // bits against 1.89 bits unconditioned. Trial encoding still picks the
461
+ // smallest codec per plane. Future scale formats need the same vetting
462
+ // before being admitted.
463
463
  let role_ok = match descriptor.role {
464
464
  Role::Scale { format } | Role::GlobalScale { format } => {
465
- matches!(format, ScaleFormat::E4M3 | ScaleFormat::E5M2)
465
+ matches!(
466
+ format,
467
+ ScaleFormat::E4M3 | ScaleFormat::E5M2 | ScaleFormat::E8M0
468
+ )
466
469
  }
467
470
  _ => false,
468
471
  };
@@ -1081,10 +1084,8 @@ mod capability_tests {
1081
1084
  }
1082
1085
 
1083
1086
  #[test]
1084
- fn o1sac_rejects_e8m0_scale() {
1085
- // E8M0 (MXFP4 block scale) is a pure 8-bit exponent — structurally
1086
- // different from FP8 scales (E4M3 / E5M2) and outside O1SAC's design
1087
- // domain.
1087
+ fn o1sac_accepts_e8m0_scale() {
1088
+ // E8M0 (MXFP4 block scale) rows are autocorrelated like FP8 scale rows.
1088
1089
  let c = Order1ScaleAC;
1089
1090
  let d = descriptor(
1090
1091
  Role::Scale {
@@ -1094,11 +1095,11 @@ mod capability_tests {
1094
1095
  Layout::Rows { row_len: 32 },
1095
1096
  false,
1096
1097
  );
1097
- assert!(!c.accepts(&d));
1098
+ assert!(c.accepts(&d));
1098
1099
  }
1099
1100
 
1100
1101
  #[test]
1101
- fn o1sac_rejects_e8m0_global_scale() {
1102
+ fn o1sac_accepts_e8m0_global_scale() {
1102
1103
  let c = Order1ScaleAC;
1103
1104
  let d = descriptor(
1104
1105
  Role::GlobalScale {
@@ -1108,7 +1109,7 @@ mod capability_tests {
1108
1109
  Layout::Rows { row_len: 16 },
1109
1110
  false,
1110
1111
  );
1111
- assert!(!c.accepts(&d));
1112
+ assert!(c.accepts(&d));
1112
1113
  }
1113
1114
 
1114
1115
  #[test]
@@ -405,10 +405,28 @@ impl PlaneCodec for PerGroupCodebook {
405
405
  CodecId::PerGroupCodebook
406
406
  }
407
407
 
408
+ /// Accept expanded FP4 value planes whose length divides into whole
409
+ /// groups.
410
+ ///
411
+ /// The codec models one nibble per byte, and `GROUP_SIZE` of 32 is one
412
+ /// MXFP4 block: the unit that shares a scale, and therefore the unit
413
+ /// worth giving its own codebook. `MxFp4Deinterleave` produces exactly
414
+ /// that, 32 single-nibble slots per block.
415
+ ///
416
+ /// Two exclusions are deliberate. A *packed* plane, two values per
417
+ /// byte, is declined rather than misread: reading one would take low
418
+ /// nibbles only and return them with the high half zeroed, silently
419
+ /// losing half the data. Such a plane must be deinterleaved first. And
420
+ /// a length that is not a whole number of groups is declined here
421
+ /// rather than failing inside `encode`, because dispatch drops the
422
+ /// whole candidate chain when a codec errors and packed FP4 has only
423
+ /// one chain to drop. Declining keeps an `encode` error meaning what it
424
+ /// should: an invariant was violated, not merely an unsuitable input.
408
425
  fn accepts(&self, descriptor: &PlaneDescriptor) -> bool {
409
- descriptor.is_nibble_packed
426
+ !descriptor.is_nibble_packed
410
427
  && matches!(descriptor.role, Role::Value { .. })
411
428
  && descriptor.element_width == ElementWidth::Nibble
429
+ && (descriptor.length_bytes as usize).is_multiple_of(GROUP_SIZE)
412
430
  }
413
431
 
414
432
  fn priority_for(&self, descriptor: &PlaneDescriptor) -> i8 {
@@ -793,27 +811,27 @@ mod capability_tests {
793
811
  }
794
812
 
795
813
  #[test]
796
- fn pgc_accepts_nibble_packed_value_nibble() {
814
+ fn pgc_accepts_expanded_value_nibble() {
797
815
  let c = PerGroupCodebook;
798
816
  let d = descriptor(
799
817
  Role::Value {
800
818
  format: ValueFormat::Fp4E2m1,
801
819
  },
802
820
  ElementWidth::Nibble,
803
- true,
821
+ false,
804
822
  );
805
823
  assert!(c.accepts(&d));
806
824
  }
807
825
 
808
826
  #[test]
809
- fn pgc_rejects_not_nibble_packed() {
827
+ fn pgc_rejects_packed_plane() {
810
828
  let c = PerGroupCodebook;
811
829
  let d = descriptor(
812
830
  Role::Value {
813
831
  format: ValueFormat::Fp4E2m1,
814
832
  },
815
833
  ElementWidth::Nibble,
816
- false,
834
+ true,
817
835
  );
818
836
  assert!(!c.accepts(&d));
819
837
  }
@@ -826,7 +844,7 @@ mod capability_tests {
826
844
  format: ScaleFormat::E4M3,
827
845
  },
828
846
  ElementWidth::Nibble,
829
- true,
847
+ false,
830
848
  );
831
849
  assert!(!c.accepts(&d));
832
850
  }
@@ -839,7 +857,7 @@ mod capability_tests {
839
857
  format: ValueFormat::Fp4E2m1,
840
858
  },
841
859
  ElementWidth::Byte,
842
- true,
860
+ false,
843
861
  );
844
862
  assert!(!c.accepts(&d));
845
863
  }
@@ -852,7 +870,7 @@ mod capability_tests {
852
870
  format: ValueFormat::Fp4E2m1,
853
871
  },
854
872
  ElementWidth::Nibble,
855
- true,
873
+ false,
856
874
  );
857
875
  assert_eq!(c.priority_for(&d), 10);
858
876
  }
@@ -865,8 +883,131 @@ mod capability_tests {
865
883
  format: ScaleFormat::E4M3,
866
884
  },
867
885
  ElementWidth::Nibble,
868
- true,
886
+ false,
869
887
  );
870
888
  assert_eq!(c.priority_for(&d), i8::MIN);
871
889
  }
872
890
  }
891
+
892
+ /// End-to-end coverage for the producer this codec is actually built for.
893
+ ///
894
+ /// The suite previously exercised the codec only on hand-built planes. It
895
+ /// never drove the op that supplies it in practice, which is how a
896
+ /// descriptor mismatch between the two survived: `MxFp4Deinterleave`
897
+ /// labelled its output nibble-packed while emitting one nibble per byte,
898
+ /// and the codec's own contract expects the expanded form.
899
+ #[cfg(test)]
900
+ mod deinterleave_integration_tests {
901
+ use super::*;
902
+ use crate::transforms::mxfp4_deinterleave::MxFp4Deinterleave;
903
+ use crate::transforms::op::{Op, Plane};
904
+ use crate::types::descriptor::{Layout, PlaneDescriptor};
905
+ use crate::types::role::Role;
906
+
907
+ const BLOCK_BYTES: usize = 17;
908
+
909
+ /// OCP MXFP4 wire bytes: 16 packed-nibble bytes then one E8M0 scale.
910
+ fn mxfp4_blocks(n_blocks: usize, seed: u64) -> Vec<u8> {
911
+ let mut s = seed.wrapping_mul(0x9E37_79B9_7F4A_7C15) | 1;
912
+ let mut out = Vec::with_capacity(n_blocks * BLOCK_BYTES);
913
+ for b in 0..n_blocks {
914
+ for _ in 0..16 {
915
+ s ^= s << 13;
916
+ s ^= s >> 7;
917
+ s ^= s << 17;
918
+ // Skewed within a block, varying across blocks, so a
919
+ // per-block codebook has structure to find.
920
+ let lo = (s % 4 + (b as u64 % 3) * 4) as u8 & 0x0F;
921
+ let hi = ((s >> 8) % 4 + (b as u64 % 3) * 4) as u8 & 0x0F;
922
+ out.push(lo | (hi << 4));
923
+ }
924
+ out.push(0x80 | (b as u8 & 0x0F));
925
+ }
926
+ out
927
+ }
928
+
929
+ fn raw_descriptor(len: usize) -> PlaneDescriptor {
930
+ PlaneDescriptor {
931
+ role: Role::Raw,
932
+ element_width: ElementWidth::Byte,
933
+ length_bytes: len as u64,
934
+ layout: Layout::Flat,
935
+ derives_from_tensor: None,
936
+ residual_of: None,
937
+ is_nibble_packed: false,
938
+ vendor_bytes: vec![],
939
+ }
940
+ }
941
+
942
+ #[test]
943
+ fn codec_accepts_and_round_trips_the_deinterleaved_value_plane() {
944
+ let n_blocks = 8;
945
+ let raw = mxfp4_blocks(n_blocks, 11);
946
+ let op = MxFp4Deinterleave::new(32).unwrap();
947
+
948
+ let descs = op
949
+ .propagate_descriptors(&[raw_descriptor(raw.len())])
950
+ .unwrap();
951
+ let value_desc = descs[0].clone();
952
+
953
+ // One group per MXFP4 block is the whole premise of the codec.
954
+ assert_eq!(value_desc.length_bytes as usize, n_blocks * GROUP_SIZE);
955
+
956
+ let c = PerGroupCodebook;
957
+ assert!(
958
+ c.accepts(&value_desc),
959
+ "the codec must accept the plane its own producer emits: {value_desc:?}"
960
+ );
961
+
962
+ let planes = op
963
+ .forward(&[Plane::new(raw.clone(), raw_descriptor(raw.len())).unwrap()])
964
+ .unwrap();
965
+ let value_plane = planes[0].bytes.to_vec();
966
+ assert_eq!(value_plane.len(), n_blocks * GROUP_SIZE);
967
+ assert!(
968
+ value_plane.iter().all(|&b| b <= 0x0F),
969
+ "the value plane is one nibble per byte, so no byte exceeds 0x0F"
970
+ );
971
+
972
+ let enc = c.encode(&value_plane, None, &PlaneLayout::Flat).unwrap();
973
+ let dec = c
974
+ .decode(
975
+ enc.state_format_version,
976
+ &enc.state_bytes,
977
+ &enc.payload,
978
+ &PlaneLayout::Flat,
979
+ value_plane.len(),
980
+ )
981
+ .unwrap();
982
+ assert_eq!(
983
+ dec, value_plane,
984
+ "codec must be lossless on its own producer"
985
+ );
986
+ }
987
+
988
+ #[test]
989
+ fn codec_declines_a_genuinely_packed_plane_instead_of_halving_it() {
990
+ // Two values per byte. Reading this as one-nibble-per-byte would
991
+ // drop every high nibble, which is exactly the silent loss the
992
+ // `accepts` guard exists to prevent.
993
+ let mut d = raw_descriptor(64);
994
+ d.role = Role::Value {
995
+ format: crate::types::role::ValueFormat::Fp4E2m1,
996
+ };
997
+ d.element_width = ElementWidth::Nibble;
998
+ d.is_nibble_packed = true;
999
+ assert!(!PerGroupCodebook.accepts(&d));
1000
+ }
1001
+
1002
+ #[test]
1003
+ fn codec_declines_a_length_that_is_not_whole_groups() {
1004
+ // Declined rather than failed in `encode`: packed FP4 has one
1005
+ // candidate chain, and a codec error drops the chain entirely.
1006
+ let mut d = raw_descriptor(GROUP_SIZE + 1);
1007
+ d.role = Role::Value {
1008
+ format: crate::types::role::ValueFormat::Fp4E2m1,
1009
+ };
1010
+ d.element_width = ElementWidth::Nibble;
1011
+ assert!(!PerGroupCodebook.accepts(&d));
1012
+ }
1013
+ }