Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions bench/mo_exact_decimal.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
/* Copyright 2026 Sirius Contributors. SPDX-License-Identifier: Apache-2.0 */
#include "expression/ast/node.hpp"
#include "expression_evaluator/expression_evaluator.hpp"
#include "numeric/exact_decimal_gpu.hpp"

#include <cudf/binaryop.hpp>
Expand Down Expand Up @@ -117,4 +119,23 @@ int main()
return evaluate_decimal_columns(
operation, x->view(), wide, y->view(), wide, wide, nullptr, stream.view(), mr);
});
// A values-identical ordinary-expression control makes the evaluator's
// additional admission/error/mask cost visible. It is not a full-domain
// semantic baseline for checked numeric errors or a SQL rollout gate.
for (bool checked : {false, true}) {
auto type = checked ? sirius::logical_type::make_mo_decimal(narrow, false)
: sirius::logical_type::make_decimal(18, 0);
std::vector<std::unique_ptr<sirius::ast::node>> arguments;
arguments.push_back(std::make_unique<sirius::ast::node>(sirius::ast::reference{0, type}));
arguments.push_back(std::make_unique<sirius::ast::node>(sirius::ast::reference{1, type}));
sirius::ast::node expression(sirius::ast::function_call{
checked ? sirius::function_id::mo_decimal_add : sirius::function_id::add,
std::move(arguments),
type});
sirius::expression_evaluator evaluator(
expression, mr, stream.view(), sirius::expression_evaluator_strategy::MATERIALIZE);
measure(checked ? "exact_expression_add64" : "ordinary_expression_add64", stream.view(), [&] {
return evaluator.evaluate(cudf::table_view{{a->view(), b->view()}});
});
}
}
17 changes: 14 additions & 3 deletions cmake/sirius-embedding-tests.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,24 @@ target_link_libraries(sirius_native_result_integration PRIVATE Sirius::embed
add_executable(
sirius_native_binding_unittest
test/cpp/exec/execution_evidence_unittest.cpp
test/cpp/embedding/test_native_binding.cpp src/embedding/plan.cpp)
test/cpp/embedding/test_native_binding.cpp
test/cpp/embedding/test_decimal_import.cpp
test/cpp/expression/test_function_id.cpp
test/cpp/helper/test_logical_type.cpp
src/embedding/plan.cpp)
target_compile_features(sirius_native_binding_unittest PRIVATE cxx_std_20)
target_include_directories(
sirius_native_binding_unittest
PRIVATE "${CMAKE_SOURCE_DIR}/third_party/catch"
"${SIRIUS_SUBSTRAIT_DIR}/src/include"
"${CMAKE_CURRENT_SOURCE_DIR}/src"
"${CMAKE_CURRENT_SOURCE_DIR}/tae-scanner/include"
"${SIRIUS_SUBSTRAIT_DIR}/third_party"
"${SIRIUS_SUBSTRAIT_DIR}/third_party/substrait")
target_link_libraries(sirius_native_binding_unittest PRIVATE sirius_extension
Threads::Threads)
target_link_libraries(
sirius_native_binding_unittest
PRIVATE sirius_extension duckdb_static dummy_static_extension_loader
Threads::Threads)

add_executable(
sirius_native_gpu_unittest
Expand All @@ -68,13 +75,17 @@ add_executable(
test/cpp/embedding/test_native_admission.cpp
test/cpp/embedding/test_native_result_codec.cpp
test/cpp/embedding/test_exact_decimal_gpu.cpp
test/cpp/embedding/test_decimal_expression_gpu.cpp
test/cpp/embedding/test_tae_gpu.cpp
src/embedding/c_api.cpp)
target_compile_features(sirius_native_gpu_unittest PRIVATE cxx_std_20)
target_include_directories(
sirius_native_gpu_unittest
PRIVATE "${CMAKE_SOURCE_DIR}/third_party/catch"
"${CMAKE_CURRENT_SOURCE_DIR}/test/cpp"
"${SIRIUS_SUBSTRAIT_DIR}/src/include"
"${SIRIUS_SUBSTRAIT_DIR}/third_party"
"${SIRIUS_SUBSTRAIT_DIR}/third_party/substrait"
"${CMAKE_CURRENT_SOURCE_DIR}/tae-scanner/include"
"${CMAKE_CURRENT_SOURCE_DIR}/src"
"${CMAKE_CURRENT_SOURCE_DIR}/src/compression/simpatico_codegen/src")
Expand Down
8 changes: 5 additions & 3 deletions cmake/sirius-embedding.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ target_link_libraries(sirius_c_smoke PRIVATE Sirius::embed)
set_property(
TARGET sirius_c_smoke
APPEND
PROPERTY LINK_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/scripts/export_embed_link.py"
"${CMAKE_CURRENT_SOURCE_DIR}/src/sirius_c.h")
PROPERTY
LINK_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/scripts/export_embed_link.py"
"${CMAKE_CURRENT_SOURCE_DIR}/proto/matrixone/sirius/numeric/v1/exact_decimal.proto"
"${CMAKE_CURRENT_SOURCE_DIR}/src/sirius_c.h")
add_custom_command(
TARGET sirius_c_smoke
POST_BUILD
Expand Down
57 changes: 57 additions & 0 deletions docs/mo-exact-decimal-import-kernels.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
case,rows,repetition,wall_ms,rows_per_second
cudf_add64,262144,0,0.201209,1.30284e+09
cudf_add64,262144,1,0.18722,1.40019e+09
cudf_add64,262144,2,0.204459,1.28213e+09
cudf_add64,262144,3,0.199659,1.31296e+09
cudf_add64,262144,4,0.19854,1.32036e+09
cudf_add64,262144,5,0.198959,1.31758e+09
cudf_add64,262144,6,0.19894,1.3177e+09
checked_add64,262144,0,0.02799,9.36563e+09
checked_add64,262144,1,0.02695,9.72705e+09
checked_add64,262144,2,0.02728,9.60938e+09
checked_add64,262144,3,0.02825,9.27943e+09
checked_add64,262144,4,0.033239,7.88664e+09
checked_add64,262144,5,0.02721,9.63411e+09
checked_add64,262144,6,0.03398,7.71466e+09
cudf_widen_multiply64_to128,262144,0,0.510259,5.13747e+08
cudf_widen_multiply64_to128,262144,1,0.512328,5.11672e+08
cudf_widen_multiply64_to128,262144,2,0.509408,5.14605e+08
cudf_widen_multiply64_to128,262144,3,0.505559,5.18523e+08
cudf_widen_multiply64_to128,262144,4,0.505398,5.18688e+08
cudf_widen_multiply64_to128,262144,5,0.503878,5.20253e+08
cudf_widen_multiply64_to128,262144,6,0.505139,5.18954e+08
checked_multiply64_to128,262144,0,0.510949,5.13053e+08
checked_multiply64_to128,262144,1,0.503798,5.20336e+08
checked_multiply64_to128,262144,2,0.510058,5.13949e+08
checked_multiply64_to128,262144,3,0.503409,5.20738e+08
checked_multiply64_to128,262144,4,0.507878,5.16155e+08
checked_multiply64_to128,262144,5,0.504078,5.20047e+08
checked_multiply64_to128,262144,6,0.504059,5.20066e+08
checked_add256,262144,0,0.263589,9.94518e+08
checked_add256,262144,1,0.260059,1.00802e+09
checked_add256,262144,2,0.268119,9.77715e+08
checked_add256,262144,3,0.259359,1.01074e+09
checked_add256,262144,4,0.260209,1.00744e+09
checked_add256,262144,5,0.26219,9.99825e+08
checked_add256,262144,6,0.264499,9.91096e+08
checked_divide256,262144,0,4.84093,5.41515e+07
checked_divide256,262144,1,4.83929,5.41699e+07
checked_divide256,262144,2,4.83929,5.41699e+07
checked_divide256,262144,3,4.8423,5.41362e+07
checked_divide256,262144,4,4.84289,5.41296e+07
checked_divide256,262144,5,4.84123,5.41482e+07
checked_divide256,262144,6,4.84392,5.41181e+07
ordinary_expression_add64,262144,0,0.199989,1.31079e+09
ordinary_expression_add64,262144,1,0.20326,1.2897e+09
ordinary_expression_add64,262144,2,0.19919,1.31605e+09
ordinary_expression_add64,262144,3,0.198689,1.31937e+09
ordinary_expression_add64,262144,4,0.198569,1.32017e+09
ordinary_expression_add64,262144,5,0.1986,1.31996e+09
ordinary_expression_add64,262144,6,0.199959,1.31099e+09
exact_expression_add64,262144,0,0.15899,1.64881e+09
exact_expression_add64,262144,1,0.167109,1.5687e+09
exact_expression_add64,262144,2,0.16494,1.58933e+09
exact_expression_add64,262144,3,0.156899,1.67078e+09
exact_expression_add64,262144,4,0.16549,1.58405e+09
exact_expression_add64,262144,5,0.157249,1.66706e+09
exact_expression_add64,262144,6,0.15738,1.66568e+09
124 changes: 124 additions & 0 deletions docs/mo-exact-decimal-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# Embedded MO exact-decimal import and scalar execution

This is the first of the two native integration increments approved for
[MO #28968](https://github.com/matrixorigin/matrixone/issues/28968) and
[MO #28966](https://github.com/matrixorigin/matrixone/issues/28966). It follows
merged Sirius #25 and importer #4. The semantic contract remains the approved
MO #29449 document blob `42a89f09a1d168d02b9583cb3ea7b4de6dbb5634`.

## Contract and ownership

Physical coefficient width, declared precision, scale, and nullability survive
Substrait import, DuckDB binding, Sirius translation, cloning and GPU evaluation.
The first type owner is the validated import descriptor. Ordinary DuckDB
decimal inference cannot substitute a narrower carrier. Decimal64/128 use
tagged BIGINT/HUGEINT DuckDB carriers; Decimal256 uses the signed-high/unsigned
low limb STRUCT introduced by #25. Sirius retains a distinct MO decimal type.

The private alias records the descriptor and is validated against the complete
physical carrier on conversion. It is internal binding metadata, not a new
wire encoding or registered SQL type. The public literal wire contract is the
approved `ExactDecimalLiteral` protobuf with one fixed-width coefficient field,
defined under `proto/matrixone/sirius/numeric/v1/`.
The build-tree SDK exports and fingerprints this schema beside its C header.
The pinned protobuf reader accepts standard unknown fields and length varints;
the final coefficient must still have exactly the declared physical width.

One scoped importer validates exact-decimal identities and output type anchors
from its own bounded serialized plan. Private scalar markers bind immutable
input/output types and retain MO's declared result. They cannot execute on CPU.
The typed signature survives the DuckDB/Sirius round trip. Existing function
IDs retain their values; exact-decimal IDs are appended.

GPU scalar execution uses the caller's task stream and resource. Checked
results retain the failing operation's internal error kind. CASE and COALESCE
propagate active rows, so an inactive arithmetic or narrowing failure does not
become a query failure. At most one conditional branch's temporary columns
remain owned at a time. Failure proves stream quiescence before cleanup;
unprovable owners transfer to the existing fatal process quarantine.
cuDF STRUCT selection may superimpose the parent bitmap on child columns.
After validating canonical operands and proving stream completion, conditional
selection removes only those redundant child masks without copying coefficient
data. A following exact operation verifies that parent-only validity survives.

No process-wide type/function registry or GPU allocator is replaced. Query
metadata remains bounded by the existing plan/admission contract. Literal
broadcast and error reduction allocate only through the supplied resource;
there is no per-row host callback or numeric string conversion.

## Exposure boundary

The helper and direct-consumer tests can import and evaluate the scalar family.
Production embedded admission still rejects the new extension, and the exact
capability remains clear. Ordinary importer calls continue without the scoped
handler. Aggregates, keys, complete admission, public statuses 12/13 and
capability advertisement belong to the next native increment.

The remaining approved sequence is:

1. Sirius import/type/scalar plumbing, with capability disabled (this increment).
2. Sirius aggregates/keys, complete native admission and capability.
3. MO lowering, reconstruction, typed errors and query-local evidence.
4. Real native-MO/embedded-MO SF1/SF10 parity, resource and performance campaign.
5. MO embedded default and strict Flight configuration/recovery cutover.
6. Sidecar Flight retirement after the verified MO release artifact exists.

The campaign requires streams 2 plus controls 1/4, keeps the 10% numeric
regression policy, and retains the 1.0 Flight+MO gate only on available equivalent
coverage. Native MO is the complete correctness oracle. Direct TAE and Docker
image/base changes remain outside this migration milestone.

## Validation map

| Closure | Risk | Required evidence |
| --- | --- | --- |
| Type/literal and binding descriptors | R2 | Independent byte/domain controls, malformed identity/width rejection, binding and clone/round-trip fidelity |
| GPU scalar and conditional owners | R3 | Actual GPU values, masks, scalar/cast error kinds, empty inputs, inactive failures, same-stream cleanup |
| Existing consumers and exposure | R2 | Ordinary type/function controls, native ownership suites, unchanged C ABI/capability |
| Build, schema and documentation | R0/R1 | Native build, registered tests and pinned static hooks |

Full public SQL acceptance is the MO lowering/campaign boundary. This increment
does not claim all-22 execution, resource rollout acceptance or migration completion.

The standalone decimal benchmark retains the primitive measurements and adds
ordinary/exact expression add controls on the same values and resource. Report
their raw repetitions separately: these controls measure evaluator cost, not
full-domain error equivalence or complete query performance.

## Local evidence

Validated on 2026-10-08 using the frozen `mo` Pixi environment: CUDA 13.3.73,
GCC 14.4.0, cuDF/RMM 26.08, an RTX 3070 and driver 615.71.09.

| Check | Result |
| --- | --- |
| Native binding, logical types and ordinary function controls | 727 assertions / 75 cases |
| CPU exact-decimal foundation | 52,330 assertions / 5 cases |
| Native ownership and protocol controls | 594 assertions / 77 cases |
| Imported expressions, decimal kernels and native GPU/result controls | 31,459 assertions / 18 cases |
| Compute Sanitizer memcheck, imported expressions | 1,664 assertions / 7 cases; zero sanitizer errors |
| SDK export tests | 8 tests |
| Independent C ABI consumer and SDK schema fingerprint | Passed |

All listed tests passed. The native targets built and pinned pre-commit hooks
passed. Memcheck covers invalid memory access; it does not establish the later
resource, concurrency or leak acceptance campaign.

Reproduce the GPU and sanitizer groups from the Sirius source directory:

```bash
pixi run --frozen -e mo build/mo/extension/sirius/sirius_native_gpu_unittest \
'[decimal_import_gpu],[exact_decimal_gpu],[native_gpu],[native_result_gpu]'
pixi run --frozen -e mo compute-sanitizer --tool memcheck --error-exitcode 99 \
build/mo/extension/sirius/sirius_native_gpu_unittest '[decimal_import_gpu]'
pixi run --frozen -e mo build/mo/extension/sirius/sirius_exact_decimal_benchmark
```

The benchmark uses two warmups; the
[raw scalar repetitions](mo-exact-decimal-import-kernels.csv) record seven
measured repetitions at 262,144 rows. Against the
recorded #25 primitive run, checked-operation median wall time changed by
+1.49% (add64), +0.77% (widened multiply), -0.05% (add256), and +3.83%
(divide256). This is a historical local comparison, not a concurrent SQL
acceptance run. The new ordinary/exact expression add controls measured
0.199190/0.158990 ms median on identical values and the same bounded pool.
10 changes: 10 additions & 0 deletions proto/matrixone/sirius/numeric/v1/exact_decimal.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Copyright 2026 Sirius Contributors. SPDX-License-Identifier: Apache-2.0
syntax = "proto3";
package matrixone.sirius.numeric.v1;
option go_package = "github.com/matrixorigin/matrixone/pkg/pb/siriusnumeric";

// Type parameters carry physical width, precision and scale; this payload is
// exactly the signed little-endian two's-complement coefficient for that width.
message ExactDecimalLiteral {
bytes coefficient_le = 1;
}
9 changes: 9 additions & 0 deletions scripts/export_embed_link.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,15 @@ def main():
manifest["artifact_sha256"][str((args.output / "sirius_c.h").resolve())] = sha256(
args.output / "sirius_c.h"
)
# The versioned literal schema is owned by Sirius, so downstream language
# bindings generate from the same pinned SDK contract instead of duplicating it.
relative_schema = Path("proto/matrixone/sirius/numeric/v1/exact_decimal.proto")
schema = Path(manifest["source_directory"]) / relative_schema
if schema.is_file():
destination = args.output / relative_schema
destination.parent.mkdir(parents=True, exist_ok=True)
shutil.copyfile(schema, destination)
manifest["artifact_sha256"][str(destination.resolve())] = sha256(destination)
(args.output / "link.json").write_text(json.dumps(manifest, indent=2) + "\n")
# Reusing a build tree must not leave the retired GPU-provider manifest
# beside a newly exported SDK that no longer records or validates it.
Expand Down
21 changes: 19 additions & 2 deletions scripts/tests/test_export_embed_link.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# SPDX-License-Identifier: Apache-2.0

import importlib.util
import hashlib
import json
from pathlib import Path
import subprocess
Expand All @@ -18,9 +19,16 @@


class ExportTest(unittest.TestCase):
def test_export_retires_stale_gpu_manifest(self):
def test_export_records_literal_schema_and_retires_stale_gpu_manifest(self):
with tempfile.TemporaryDirectory() as directory:
root = Path(directory)
relative_schema = Path(
"proto/matrixone/sirius/numeric/v1/exact_decimal.proto"
)
schema = root / relative_schema
schema.parent.mkdir(parents=True)
payload = b'syntax = "proto3"; message ExactDecimalLiteral { bytes coefficient_le = 1; }\n'
schema.write_bytes(payload)
output = root / "embedding-sdk"
output.mkdir()
stale = output / "toolchain.json"
Expand Down Expand Up @@ -51,12 +59,21 @@ def test_export_retires_stale_gpu_manifest(self):
), patch.object(
sdk, "c_compiler", return_value="/usr/bin/cc"
), patch.object(
sdk, "source_provenance", return_value={"source_revision": "test"}
sdk,
"source_provenance",
return_value={"source_revision": "test", "source_directory": str(root)},
), patch.object(
sdk, "artifact_hashes", return_value={}
):
sdk.main()
self.assertFalse(stale.exists())
destination = output / relative_schema
self.assertEqual(destination.read_bytes(), payload)
manifest = json.loads((output / "link.json").read_text())
self.assertEqual(
manifest["artifact_sha256"][str(destination.resolve())],
hashlib.sha256(payload).hexdigest(),
)
self.assertNotIn(
"gpu_toolchain_manifest", json.loads((output / "link.json").read_text())
)
Expand Down
18 changes: 17 additions & 1 deletion src/cudf/cudf_utils.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#define CUDF_VERSION_NUM (CUDF_VERSION_MAJOR * 100 + CUDF_VERSION_MINOR)

#include "helper/logical_type.hpp"
#include "numeric/exact_decimal_gpu.hpp"
#include "sirius/exception.hpp"

#include <cudf/aggregation.hpp>
Expand Down Expand Up @@ -172,6 +173,13 @@ inline std::size_t estimate_referenced_column_bytes(
*/
inline cudf::data_type get_cudf_type(const logical_type& t)
{
if (t.is_mo_decimal()) {
auto type = t.mo_decimal_type();
return type.bits == 256 ? cudf::data_type{cudf::type_id::STRUCT}
: cudf::data_type{type.bits == 64 ? cudf::type_id::DECIMAL64
: cudf::type_id::DECIMAL128,
-static_cast<int32_t>(type.scale)};
}
switch (t.id()) {
case type_id::TINYINT: return cudf::data_type(cudf::type_id::INT8);
case type_id::SMALLINT: return cudf::data_type(cudf::type_id::INT16);
Expand Down Expand Up @@ -341,11 +349,19 @@ template <typename... Columns>
* types). Used wherever an empty but schema-bearing table is needed — e.g. an all-pruned GPU-values
* source, or the synthesized missing side of a join against an empty table.
*/
inline std::unique_ptr<cudf::table> make_empty_table(const duckdb::vector<logical_type>& types)
inline std::unique_ptr<cudf::table> make_empty_table(
const duckdb::vector<logical_type>& types,
rmm::cuda_stream_view stream = cudf::get_default_stream(),
rmm::device_async_resource_ref mr = cudf::get_current_device_resource_ref())
{
std::vector<std::unique_ptr<cudf::column>> columns;
columns.reserve(types.size());
for (auto const& t : types) {
if (t.is_mo_decimal()) {
columns.push_back(mo_decimal::make_decimal_column(
t.mo_decimal_type(), 0, cudf::mask_state::UNALLOCATED, stream, mr));
continue;
}
columns.push_back(t.is_array() ? cudf::make_empty_lists_column(get_cudf_type(t.array_child()))
: cudf::make_empty_column(get_cudf_type(t)));
}
Expand Down
Loading
Loading