Skip to content

Add: record scalar_count in the ChipCallable header - #2064

Draft
sunkaixuan2018 wants to merge 1 commit into
hw-native-sys:mainfrom
sunkaixuan2018:skx/kernel-PR1
Draft

Add: record scalar_count in the ChipCallable header#2064
sunkaixuan2018 wants to merge 1 commit into
hw-native-sys:mainfrom
sunkaixuan2018:skx/kernel-PR1

Conversation

@sunkaixuan2018

Copy link
Copy Markdown
Contributor

Motivation

A ChipCallable is the self-describing compiled artifact (header + payload) whose bytes travel through L3/L4 IPC and the on-disk kernel cache. The header records the tensor signature (signature_[] + sig_count_) but not how many scalar arguments the orchestration expects, so the two sides of a call rely on an implicit convention. This PR makes the artifact record its scalar count. It only adds the data and the write path — no code consumes or validates the field yet.

How it serves the overall goal: the compiled artifact now fully records how many tensors and scalars it needs, which is the data a future call-entry argument check will read.

Design

int32_t scalar_count_ occupies four bytes of the historical padding between config_name_len_ and the 16-byte-aligned storage_, so:

  • every historical field offset, offsetof(storage_) (9376), and sizeof(ChipCallable) (9376) are unchanged;
  • make_callable takes the count right after sig_count and rejects values outside [0, CHIP_MAX_SCALAR_ARGS(=128)], naming the value and the limit;
  • ChipCallable.build(...) gains a trailing keyword scalar_count=0 and a read-only scalar_count property — every existing Python caller (including downstream repos) keeps working unchanged;
  • scalar_count == 0 means either a legacy artifact that never recorded the count or an orchestration that takes no scalars; the two are indistinguishable by design (no version bit).

CoreCallable carries no scalar_count: on current main its leaf header packs exactly to storage_ with zero padding (128+4+4+8 = 144, resolved_addr_ naturally aligned), so the field cannot be added there without moving the wire layout. The chip-level artifact is where a call entry looks the count up; the CoreCallable layout is instead pinned byte-for-byte by the new asserts.

Compatibility matrix

Direction Result
new code reads an old blob padding was zero-initialized by make_callable, so scalar_count() == 0 (legacy semantics)
old code reads a new blob offsets and sizes unchanged; old code never reads those four bytes
kernel cache / ABI token a build with the default scalar_count=0 is byte-identical to a pre-change build, so existing build/cache/kernels entries and the _chip_callable_abi_token stay valid
identity digest callable_blob_sha256 already covers the full bytes, so it automatically covers the field; the versioned CHIP_SIGNATURE_SCHEMA_V1 is untouched

Static assert inventory (compile-time, callable.h)

  • ChipCallable: offsets of signature_(0), sig_count_(1024), binary_size_(1028), func_name_(1032), func_name_len_(1096), child_func_ids_(1100), child_offsets_(5196), child_count_(9292), config_name_(9296), config_name_len_(9360), scalar_count_(9364), storage_(9376); sizeof == 9376
  • CoreCallable: offsets of signature_(0), sig_count_(128), binary_size_(132), resolved_addr_(136), storage_(144); sizeof == 144; binary_data_offset() == 192
  • both: is_trivially_copyable && is_standard_layout; existing CALLABLE_CHILD_ALIGN alignment asserts retained

Write chain

All in-repo C++ make_callable call sites pass their real count (0 — none of them takes scalars). The scene-test path keeps the binding default of 0: the CALLABLE declaration carries no scalar information (scalars appear only per-case in generate_args()), so a real value is not derivable at callable-compile time and is not guessed. Callers that know their count can start passing ChipCallable.build(scalar_count=...).

Tests

  • C++ (tests/ut/cpp/types/test_callable_scalar_count.cpp, new): factory round-trip (0/1/7/128), out-of-range rejection with value+limit in the message (-1, 129), a legacy blob (field bytes zeroed) reads 0 with all other accessors intact, and a byte-level proof that two builds differing only in scalar_count differ only in that field's four bytes.
  • Python (tests/ut/py/test_task_interface.py): default path reads 0, keyword round-trip, from_bytes round-trip, out-of-range raises ValueError naming value and limit.
  • Full tests/ut/cpp: 124/124 pass (Linux, default build type). Full tests/ut/py: 1976 passed / 15 skipped; the only failures are the torch-less-host environment set (every one fails at import torch), identical on a clean base.
  • Sim scene tests: left to this PR's CI matrix (the validation host has no torch, which scene tests require).

A ChipCallable records its tensor signature but not how many scalar
arguments its orchestration expects, so an argument list cannot be
checked against the artifact. Store the count in the header and thread
it through the factory and the binding, changing no byte of the
existing layout:

- scalar_count_ occupies four bytes of the padding between
  config_name_len_ and the 16-byte-aligned storage_, so every
  historical field offset, offsetof(storage_), and sizeof stay
  unchanged, and a legacy zero-initialized blob reads as 0 (legacy
  artifact and no-scalar orchestration are indistinguishable by
  design).
- make_callable takes the count after sig_count and rejects values
  outside [0, CHIP_MAX_SCALAR_ARGS], naming the value and the limit.
- ChipCallable.build gains a trailing scalar_count keyword defaulting
  to 0 and the binding exposes a read-only scalar_count property, so
  every existing caller keeps working unchanged.
- static_asserts pin every field offset, sizeof, and POD-ness of both
  ChipCallable and CoreCallable. CoreCallable carries no scalar_count:
  its leaf header packs exactly to storage_ with no padding, so the
  field cannot be added there without moving the wire layout, and the
  chip-level artifact is where callers look the count up.
- Tests cover factory round-trip, bounds rejection, legacy blobs
  reading 0, and that two builds differing only in scalar_count differ
  only in that field's four bytes.
@coderabbitai

coderabbitai Bot commented Aug 29, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant