Skip to main content

OpAttrs

Struct OpAttrs 

Source
pub struct OpAttrs {
Show 32 fields pub scalars: Vec<f64>, pub axis: Option<i64>, pub perm: Vec<u8>, pub target_shape: Vec<i64>, pub dims: Vec<u8>, pub cast_dtype: Option<String>, pub slice_start: Option<u64>, pub slice_len: Option<u64>, pub roll_shift: Option<i64>, pub pad_amounts: Vec<(u64, u64)>, pub pad_mode: Option<u8>, pub pad_value: Option<f64>, pub keepdim: Option<bool>, pub target_shape_rel: Option<ShapeExpr>, pub slice_start_rel: Option<Dim>, pub slice_len_rel: Option<Dim>, pub axis_last: bool, pub scalar_rel: Option<Dim>, pub lhs_roles: Vec<u8>, pub rhs_roles: Vec<u8>, pub const_bits: Option<u64>, pub slot_index: Option<u32>, pub scan_role: Option<u8>, pub scan_index: Option<u32>, pub scan_n_carries: Option<u32>, pub scan_n_xs: Option<u32>, pub scan_bound: Option<u32>, pub scan_emit: Option<u8>, pub scan_early_exit: Option<bool>, pub view_slot: Option<u32>, pub rank0_target: bool, pub fused_op: Option<String>,
}
Expand description

Non-tensor attributes a PatternNode::Op carries (fkc-fusion-patterns §3a.4; Baracuda reconcile §2). For scalar-param ops the value is not baked — it identifies the slot the emitted extract: path points at, and the matcher re-reads the live value from the matched graph node at match time. Carries the load-bearing attributes the general vocabulary needs.

Fields§

§scalars: Vec<f64>

Scalar value(s) for scalar-param ops (AddScalar/MulScalar/Clamp), as the region’s snapshot of the slot (re-read live via extract:).

§axis: Option<i64>

Axis attribute for dim-bearing ops (reductions, Triu/Tril diagonal).

§perm: Vec<u8>

Permute/Transpose: the new axis order, ABSOLUTE — a permutation of 0..rank with out.axis[i] = in.axis[perm[i]] (matches Fuel Op::Permute semantics exactly; Op::Transpose is the last-two-axes special case). Empty ⇒ not a permuting node (a matcher wildcard). See baracuda-layout-fusion-response.md F1/F2a.

§target_shape: Vec<i64>

BroadcastTo/Reshape: the target LOGICAL shape (the op’s output shape); one field serves both (the OpTag disambiguates which op). Empty ⇒ not a shape-target node (a matcher wildcard).

§dims: Vec<u8>

Squeeze/Unsqueeze: the affected dim list (0-based, output-rank terms). Fuel’s single-dim Op::Squeeze/Op::Unsqueeze emit a one-element list. Empty ⇒ not a squeeze/unsqueeze node (a matcher wildcard).

§cast_dtype: Option<String>

Cast target dtype as the stable DType::as_str() name (dep-free: this crate can’t reference fuel_ir::DType; fuel-graph maps back via DType::from_str). Also carries MaskedFill’s value dtype. None ⇒ not a dtype-carrying node (a matcher wildcard). Convergence Increment A.

§slice_start: Option<u64>

Slice start offset along axis (the dim rides the existing axis field). None ⇒ not a slice node. Convergence Increment A.

§slice_len: Option<u64>

Slice length along axis. None ⇒ not a slice node. Convergence Increment A.

§roll_shift: Option<i64>

Roll cyclic shift along axis (signed; the dim rides axis). None ⇒ not a roll node. Convergence Increment A.

§pad_amounts: Vec<(u64, u64)>

Pad per-axis (before, after) amounts, one pair per input dim. Empty ⇒ not a pad node. Convergence Increment A.

§pad_mode: Option<u8>

Pad fill mode code: 0=Constant, 1=Reflect, 2=Replicate (mirrors Fuel’s PadMode order; dep-free integer code). None ⇒ not a pad node. Convergence Increment A.

§pad_value: Option<f64>

Pad constant fill value (used by PadMode::Constant). None ⇒ not a pad node. Convergence Increment A.

§keepdim: Option<bool>

Reduction keepdim flag (§6.19 reduce-schema conformance — serialized in the canonical blob). NOT consumed by tag_to_op: Fuel’s reduce Ops encode keepdim structurally (ReduceSumTo/ReduceMaxTo carry the kept shape; SumDim/MeanDim are rank-reducing). Convergence Increment A.

§target_shape_rel: Option<ShapeExpr>

Shape-relative alternative to target_shape: the target is another operand’s shape (SameAs { operand } over the Bind space). None ⇒ use target_shape (or not a shape-target node).

Not every output frame is expressible this way. A broadcast frame assembled by per-axis max across TWO binds (a[N,1] ⊗ b[1,M] → [N,M]) is carried by NO single operand, so no SameAs spelling names it — the resolver refuses such a BroadcastTo target as a Dims-class gap (RelAttrError::FrameNotExpressible) rather than silently resolving to one operand’s PARTIAL frame. The constructor that would express it is the reserved shape_expr::TAG_DIMS entrant (KISS #80).

§slice_start_rel: Option<Dim>

Shape-relative alternative to slice_start: a DimExpr over the Bind space (e.g. rope’s Div(Extent(0, LAST), 2)). None ⇒ use slice_start.

§slice_len_rel: Option<Dim>

Shape-relative alternative to slice_len. None ⇒ use slice_len.

§axis_last: bool

Rank-relative alternative to the op’s axis carrier (axis, or dims for Squeeze/Unsqueeze): this op’s axis is its per-tag LAST — resolved against rank(operand[0]) as rank − 1 for reduces/Slice/Concat/Flip/ CumSum/… and Squeeze, and rank (append) for Unsqueeze. false ⇒ use the absolute carrier.

§scalar_rel: Option<Dim>

Shape-relative alternative to scalars for a scalar-param op (AddScalar/MulScalar): the scalar VALUE is a DimExpr over the Bind space, resolved to a concrete number at emit time (fuel_graph::runtime_fused::resolve_rel_attrs) — the reduced_count concept made emittable: Dim::Extent { operand: 0, axis: LAST } yields n = extent of operand 0's last axis (the MulScalar(n) divisor a norm backward needs). It rides the SAME Dim/eval_dim/resolve_axis machinery as slice_start_rel/slice_len_rel (no parallel axis resolver). Distinct from an OPEN scalar slot: a scalar_rel node is filled from an input SHAPE, NOT from the params projection cursor, so it is NOT counted by count_scalar_slots and never consumes a cursor value. None ⇒ use scalars (a baked value or an open slot). Set together with a non-empty scalars is a typed resolution error (rel XOR abs). Like the other rel fields, it is IN-MEMORY recipe data — deliberately NOT serialized to the §6.19 wire (pinned by rel_attr_fields_are_absent_from_the_6_19_wire).

§lhs_roles: Vec<u8>

LHS per-axis contraction roles ({Batch=0,FreeM=1,FreeN=2,ContractedK=3}, u8 each; length = lhs rank). Empty ⇒ implicit (rank-polymorphic) matmul.

§rhs_roles: Vec<u8>

RHS per-axis contraction roles (same encoding; length = rhs rank). Empty ⇒ implicit (rank-polymorphic) matmul.

§const_bits: Option<u64>

OpTag::Const value: the scalar literal’s dtype-agnostic storage bit pattern. Narrow dtypes place their storage bits LOW-order with the upper bits zero (MBZ) — use const_bits_narrow. A NaN payload is carried verbatim (never quieted). None ⇒ not a const leaf.

§slot_index: Option<u32>

OpTag::RuntimeScalar value: the index of the dispatch-bound scalar slot this leaf reads. None ⇒ not a runtime-scalar leaf.

§scan_role: Option<u8>

OpTag::ScanPlaceholder role code: SCAN_ROLE_CARRY (0) or SCAN_ROLE_ELEM (1). None ⇒ not a scan-placeholder leaf.

§scan_index: Option<u32>

OpTag::ScanPlaceholder index: which xs[i] an elem hole stands for (a carry hole is index 0 in the v1 single-carry model). None ⇒ not a scan-placeholder leaf.

§scan_n_carries: Option<u32>

OpTag::Scan n_carries: how many recurrent carries the scan threads (GAP-303). 1 is the v1 single-carry model.

⚠️ Like its siblings this is Fuel-internal and NOT on the KISS-OPS-6.19 wire: OpTag::Scan is in has_empty_canonical_body’s empty group BY DESIGN, so no Scan param is serialized and adding one here changes no bytes. Carry IDENTITY on the wire is already carried by OpTag::ScanPlaceholder’s body, which serializes scan_index as a u32 — Carry/0 and Carry/1 have always produced different bytes.

§scan_n_xs: Option<u32>

OpTag::Scan n_xs: number of per-step xs operands.

§scan_bound: Option<u32>

OpTag::Scan bound: the static step horizon.

§scan_emit: Option<u8>

OpTag::Scan emit code: 0 = All (stack per-step ys, slot 0), 1 = Final (final carry). Matches fuel_graph::ScanEmit’s declaration order.

§scan_early_exit: Option<bool>

OpTag::Scan early-exit presence flag: Some(true) ⇒ the Phase-2 ScanPredicate marker is present (its predicate LOGIC rides the trailing pred_exit operand). None/Some(false) ⇒ no early exit.

§view_slot: Option<u32>

OpTag::View slot index: which of the producer’s output_views slots this projection reads. Fuel-internal (not on the §6.19 wire). None ⇒ not a view node.

§rank0_target: bool

Marks an INTENTIONAL rank-0 ([], scalar) target on a shape-target op (ReduceSumTo/ReduceMaxTo/Reshape/BroadcastTo). A rank-0 shape has ZERO dims, so its target_shape is empty — the SAME empty state a wildcard / non-shape-target node carries — leaving the two indistinguishable in memory. true ⇒ the empty target_shape denotes the concrete rank-0 shape [] (a reduce-to-scalar loss tail, e.g. FSCE’s ReduceSumTo([])); false (the default) ⇒ empty target_shape is unset / a matcher wildcard. Reusable for every future reduce-to-scalar loss.

Fuel-INTERNAL, off the §6.19 wire. The wire already encodes a rank-0 target correctly as a 0-length target_shape i64 list (put_i64_list(&[])), so to_canonical_bytes needs no new byte — this marker only disambiguates the in-memory wildcard-vs-rank-0 overload that the recipe emitter (fuel_graph::runtime_fused::shape_from_attr) reads.

§fused_op: Option<String>

OpTag::Fused selector: the stable registry-entry name of the nested fused op a recipe carries as-is (e.g. "SoftmaxLastDim" / "SoftmaxLastDimBackward"), mirroring Self::cast_dtype’s DType::as_str name-string precedent. fuel-graph’s runtime_fused::tag_to_op resolves it back to a FusedOpId via registry::id_for_name and reconstructs the param-less Op::Fused(fid, params) through a small fid → params map. None ⇒ not a nested-fused node (an honest miss for OpTag::Fused).

Fuel-INTERNAL, off the §6.19 wire. A Fused node serializes to the empty canonical body (the _ arm of Self::to_canonical_bytes), so this selector never flows to a cross-producer blob — a nested fused node is a Fuel-side recipe-interior structural token, not a KISS base op.

Implementations§

Source§

impl OpAttrs

Source

pub fn to_canonical_bytes(&self, op: OpTag) -> Result<Vec<u8>, UnresolvedAttr>

Serialize these attrs to the node-envelope blob for op in the §6.19 positional shape (see the SCOPE note above: this is carrier (a), and only the tags inside KISS-OPS-6.19-0003’s carrier set have a §6.19 schema at all): op: a per-op positional little-endian body (no elision — the OpTag determines the fixed schema), length-prefixed with a u32 LE byte count. An op whose schema is empty (Add, Neg, Where, comparisons, …) serializes as the single canonical form [0,0,0,0]. MatMul is empty-bodied ONLY when its role vectors are unset (the implicit rank-polymorphic form); explicit roles serialize the LOCKED §5 contraction descriptor. Deterministic + dependency-free.

Conformance scope (do not overclaim): byte-comparable with a Baracuda-emitted blob for the positionally-conformant ops (elementwise, cast, slice, concat, roll, pad, flip, iota, permute, (un)squeeze, shape-target, matmul role-vectors — the shared cross-producer golden, Baracuda #68). reduce emits Fuel’s single-axis {axis, keepdim} and gather/scatter emit {axis}; oob_policy and a multi-axis reduce_axes list are DEFERRED (no carrier/consumer yet), while monoid/scatter_combine ride op_name and the index operand/dtype ride child_edges/that operand node per the pinned node schema — so they legitimately do not belong in this blob. See the module comment above and kernel-seam-interop.md §7.3.2.

M-3: the unwrap_or(...) defaults below cannot distinguish an unset field from a genuine zero (e.g. axis: None vs Some(0)), and for five (op, field) pairs that collapse is LOSSY TODAY - two semantically distinct ops serialize to identical bytes. Registry: GAP-287.

This paragraph previously read “harmless today … an op that reaches a given arm always has the field set (op_to_attrs / tag_to_op guarantee it)”. That was false, and the guarantor’s own doc says so: fuel_graph::jit::op_to_attrs documents itself as “not exhaustive” and leaves axis unset for CumSum/IndexSelect/Gather/IndexAdd/ ScatterAdd (its _ => {} arm). So Gather on axis 2 and on axis 0 emit the same bytes. KISS-OPS-6.19 makes axis MANDATORY with no default, so unwrap_or(0) fabricates a value the schema does not permit.

keepdim is also set by nothing in the repository, and that one is NOT a defect - the distinction worth keeping. Every tag on that arm removes the reduced dim (Op::SumDim/MaxDim/MeanDim say so verbatim; CumSum is “same shape as input”), so false is the true value. keepdim=TRUE is Op::ReduceSumTo(Shape): a different tag, on the shape-target arm, with no unwrap_or at all. A field being unset is only a defect if the default CAN differ from the truth - “never set” has several causes and only one of them is this bug.

const_bits, slot_index, scan_role and scan_index are reachable only from hand-built regions (op_to_tag emits none of the four leaf tags); scan_* are set together by both producers today, guaranteed by nothing. Note scan_role.unwrap_or(SCAN_ROLE_CARRY) is the only default here that names a semantic value rather than a neutral zero: an unset role does not decay to “absent”, it asserts “carry”.

The old note also said “a future decoder must not round-trip None”. A decoder cannot recover what this encoder already discarded - the mitigation has to live on this side of the wire. And “harmless, there is no decoder” was scoped to Fuel’s own repo while this format exists to leave it - the decoder is the counterparty’s by design, i.e. the claim was scoped to the one place the hazard cannot occur.

On what checks this format: exactly one conformance fixture exists, matmul_role_vectors_serialize_the_locked_rank2_golden, and Baracuda “has NO near-term binary arm” (its own comment), so there is no second encoder. That golden covers the MatMul arm - which emits lhs_roles/ rhs_roles directly and contains no unwrap_or at all. The single fixture this format has is aimed at an arm that cannot exhibit this defect, which is why six lossy collapses were found by audit rather than by a failing test.

⚠️ THIS CRATE’S TWO DIRECTIONS DISAGREE, AND axis IS ONE OF THEM. fuel_graph::runtime_fused::tag_to_op - the DECODE direction this serializer round-trips against - treats an unset required attr as a hard decline: 24 attrs.<field>? declines over 11 fields, plus 5 explicit return None, and its own comments name the policy (“an honest miss (unset required attr)”). Seven fields appear on BOTH sides:

axis  pad_mode  roll_shift  scan_index  scan_role  slice_len  slice_start

For every one of them the decoder REFUSES what this encoder INVENTS. Six are latent (op_to_attrs does set them, so the default is currently unreachable); axis is LIVE, because the five ops above are the ones it leaves unprojected. So making the encoder decline is not a new design - it is this codebase’s own settled answer to this exact condition, and the encoder is the side that dissents.

Gated by the collapse table in fuel-graph/src/jit.rs tests, which pins exactly which (op, field) pairs are lossy so that fixing one is visible. Fixing the encoder is a wire-format change and needs the external ask. Serialize self for op as carrier (a): u32_le(body_len) ++ body.

The FRAME is separated from the per-op BODY here only because they are different things – the envelope is fixed for every tag, the body is the schema table. The table itself is deliberately NOT split further: it is one match over the wire schema, and a reader checking Fuel against the schema it borrows needs to see all of it at once. (Only the carrier-set tags are checkable against KISS-OPS-6.19 itself; for the rest the schema is Fuel’s own – SCOPE note above.)

Source§

impl OpAttrs

Source

pub fn region_key(&self) -> RegionKey

Structural region identity of these attrs — see RegionKey. Excludes the scalar-param scalars VALUES (runtime ScalarExpr::Param), keys floats by to_bits, keeps None distinct from Some(0), and is built by an exhaustive destructure so a new OpAttrs field must be classified before this compiles.

Trait Implementations§

Source§

impl Clone for OpAttrs

Source§

fn clone(&self) -> OpAttrs

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for OpAttrs

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error>

Formats the value using the given formatter. Read more
Source§

impl Default for OpAttrs

Source§

fn default() -> OpAttrs

Returns the “default value” for a type. Read more
Source§

impl PartialEq for OpAttrs

Source§

fn eq(&self, other: &OpAttrs) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for OpAttrs

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V