Version 1 CatBoost mapping
Normative source: docs/format-catboost.md. This page renders that file unchanged; the guides explain it, this text defines it.
Normative section of the format contract. Audited and tested with
CatBoost 1.2.10 on Python 3.14. catboost>=1.2.10 is an optional extra included
in all; its floor supplies the tested CBM/API baseline and Python 3.14 wheels.
The floor does not promise compatibility with every later native schema.
Base import and metadata-only inspect do not import CatBoost.
Closed support boundary
Only exact fitted catboost.CatBoostClassifier and CatBoostRegressor are
supported, with one target and numeric features. Classifier losses are
Logloss, CrossEntropy, MultiClass. Regressor losses are RMSE, MAE,
Quantile, Huber, Poisson, MAPE, including their native parameter strings.
MultiClassOneVsAll does not guarantee probability row sums of one and is
rejected. Multi-target/uncertainty, ranking, other losses, bare CatBoost,
rankers, subclasses and custom Python objectives/metrics are unsupported.
The fitted wrapper attribute set and native _object type are closed; overrides, unknown
attributes and prediction-changing parameter caches are rejected. Export never
silently normalizes a blob-loaded Poisson wrapper that originally predicts raw values.
Only SymmetricTree (oblivious trees) is admitted. Depthwise and Lossguide
are rejected. Categorical, text and embedding features are rejected at export
and independently before native load. Categorical CBM round trips work natively,
but CTR storage has an unchecked native loader and needs its own preflight.
There is no implicit input encoding or model conversion in this adapter.
An exact outer sklearn Pipeline may end in an admitted CatBoost wrapper via the
composite representation. The prefix uses checked skops bytes;
CBM passes through this adapter’s unchanged preflight/claims/structure checks.
Other native placements are rejected; CatBoost remains outside skops trust.
class_labels= is rejected even if equal: native classes_ owns the labels.
Class arrays retain native scalar types and probability-column order and are
output-major, including [[0, 1]] for binary classification. Int and string
binary/multiclass labels have tested exact round trips.
Native I/O and wrapper recovery
Library/serialization/member pairing is catboost / cbm / model.cbm.
Standalone bundles have one native member; composites may also have a skops prefix.
CBM never goes through skops; there is no pickle fallback.
Public save_model(path, format="cbm") writes in an export-only temporary
directory that is removed before publication. Public saving has no in-memory
API. Public load_model(blob=data) consumes bytes without reader-side disk I/O.
The blob path ignores its format argument and supports CBM, not native JSON.
CatBoost’s audited private _serialize_model() returns CBM bytes through its
C++ SerializeModel; it is used only to recompute current native state for
extraction/verification. It performs no pickle serialization and writes no files.
CBM layout/metadata order may change when reserialized, so verification compares
semantic snapshots, not a SHA of the entire serialized CBM buffer.
Blob loading preserves get_all_params, feature names, class labels, best
iteration, stored tree truncation, scale/bias and probability threshold.
It loses constructor get_params and returns zero for n_features_in_.
The reader chooses a class from the fixed classifier/regressor map using the
validated native loss and sets its feature-width cache from the native table.
It constructs that wrapper with the validated loss. This is prediction-critical
for Poisson: a default regressor loaded via blob otherwise predicts raw values
instead of exponentiated values. No manifest setting restores state.
Constructor parameters are not claimed or reconstructed wholesale. The
recoverable effective parameters from get_all_params() are claimed. Native
training/evaluation metadata is committed verbatim, but API evaluation-history
projections are not separately claimed: native parsing may change their float
spelling/projection without changing predictions. Native labels/borders and
numeric CBM probability/regression predictions survived tested round trips
exactly. JSON file export/load produced float noise and is not the chosen format.
Claims and complete structure
hyperparameters has exactly parameters (the recoverable get_all_params()
JSON projection), tree_count (actual stored trees), best_iteration (native
value or null), scale_and_bias (native pair), and probability_threshold
(native classifier threshold, null for a regressor). CatBoost predicts all stored
trees by default; best iteration is descriptive, and shrinking changes the
actual native tree arrays. Feature names are always model-origin: CatBoost
generates numeric string names when fitting unnamed arrays. model_dtypes is
null; numerical acceptance/quantization does not establish training dtypes.
Counts, kind, class identity, ordered names, native labels, hyperparameters and
model.state_sha256 are extracted by the same code at export/load and compared
by canonical JSON bytes. The commitment covers every admitted native feature,
tree, repacked split and all native metadata, including class mapping, params,
best-iteration/training metadata and the threshold. It does not authenticate
training history. Loaded results contain the real CatBoost object. Mutating it
invalidates the verified relationship with metadata.
Mandatory structure is exactly family="catboost", feature_importances
and native_model. Importances are freshly computed with
get_feature_importance(type="PredictionValuesChange", thread_count=1) on a
native public copy(), so computation cannot overwrite the caller’s importance cache;
not copied from the fitted wrapper’s cached feature_importances_.
native_model has exactly these fields:
| Field | Native representation |
|---|---|
approx_dimension |
One for binary/regression; class count for MultiClass. |
tree_depths |
Depth per tree, in native order. |
tree_split_offsets |
Start offset per tree into tree_splits; cumulative depths. |
tree_splits |
Native bin-feature indices, in tree/level order. |
leaf_values |
Native flat binary64 array in tree, leaf, approximation-dimension order. Tree t has 2**tree_depths[t] * approx_dimension values; retain every dimension. |
leaf_weights |
Native flat binary64 weights in tree/leaf order. |
float_features |
Native ordered records with index, flat_index, name, has_nans, nan_value_treatment, borders. Borders promote binary32 exactly to binary64. NaN treatment is native enum 0=AsIs, 1=AsFalse, 2=AsTrue. |
scale, bias |
Native binary64 scale and dimension-length bias. Legacy scalar bias normalizes to that vector. |
repacked_bins |
Native little-endian uint32 words: low 16 bits feature bucket, next byte XOR mask, high byte split index; zero or one per stored split. |
The split lookup flattens float-feature borders in feature/border order. Bit
level in a leaf index corresponds to that level’s split decision. Splits use
CatBoost’s native greater-than-border test after native NaN handling. No pruning,
rounding or manufactured node gains. Full canonical structure comparison rejects
missing, altered or additional records. The existing 256 MiB structure ceiling
and caller-lowered limits apply; oversize structure is rejected, never truncated.
Native state commitment
Root is exactly {"native_model":NATIVE,"metadata":INFO_MAP} using the snapshot
above before JSON conversion. Every dictionary replaces children by digests
then hashes b"dict:" + canonical_dumps(record). Lists hash
b"list:" + canonical_dumps(child_digests). Scalars hash
b"scalar:" + canonical_dumps(value). Each numeric array hashes
b"array:" + canonical_dumps([dtype.str, shape]) + contiguous_bytes.
Arrays use their native little-endian <i4, <u4, <f4, <f8 dtypes and flat
shapes. Floats must be finite; signed zero and exact bits are retained. Digests
are lowercase SHA-256 hexadecimal. Dictionary key order and CBM buffer layout
do not affect the commitment. Unknown CBM table fields fail preflight.
The payload commitment must survive native loading/recomputation before claims
are compared with the manifest; ignored or altered state is rejected.
Bounded work and predictor
Before native load, require exact CBM1 prefix, uint32 core length equal to
the remaining member bytes, core version FlabuffersModel_v1 (native spelling),
closed tables, checked field/indirect offsets and stored vector/string extents.
Unknown model parts and nonempty categorical/CTR, non-symmetric, text,
embedding/estimated-feature arrays are rejected without native traversal.
Numeric vectors remain byte views. Table visits are capped at 200,000;
features/classes at 100,000, trees at 100,000, depth at 16 and aggregate borders
at 1,600,000. Feature indices/order, split offsets/indices, leaf dimensions,
finite values, nonnegative weights and ordered borders must agree. Metadata
has at most 4,096 distinct keys and 8 MiB aggregate UTF-8 bytes. Native params
and class JSON use 1 MiB; params nesting 16, other metadata JSON nesting 32.
The native model member remains capped at 512 MiB with caller-lowered bounds.
Predictors use shared rank-two numeric checks and the native feature width.
NaN is admitted for native handling; strings, complex values and infinity are
rejected. DataFrame columns must match native names/order; arrays/lists are
positional. Empty batches fail before CatBoost. Prediction uses one CPU thread;
regressors return a finite row vector, classifiers a finite row/class matrix in
[0,1] with row sums one (rtol=1e-6, atol=1e-7). Wrong-kind calls raise
bounded ValueError. Shape/name errors use PredictionInputError; remaining
native/model errors become bounded MLBundleError without native text or cells.
These checks do not sandbox CatBoost or bound internal native allocations/CPU, serializer/importance work or process crashes. Windows corrupt-byte subprocess probes included CatBoostError, UnicodeDecodeError and accepted truncation; no crash or timeout was observed. Full verification still needs host isolation and process/row limits. Full bundle load also parses and exactly compares expanded structure JSON; it has substantially more work than native CBM loading alone.
Before native parsing or Python list conversion, a conservative structure allocation budget also fits 256 MiB: 4,096 bytes overhead, 25 bytes per floating array element, 12 per integer element, and 256 bytes plus six times UTF-8 name bytes per feature (plus 25 per border). It can reject a model whose actual compact JSON fits; it avoids allocating a huge Python list merely to discover an oversized structure. Numeric commitments hash buffer views without a full copy.
