# Tensor

Tensors are the primary data structure in HEAL. They represent multi-dimensional arrays with fixed shape and data type. Every function in the HEAL runtime operates over tensors, which are passed as pointers into device memory.

{% hint style="info" %}
We adopt a widely accepted approach to tensor structure and semantics, consistent with **NumPy** and other modern ML frameworks like **PyTorch**.
{% endhint %}

This page introduces how tensors are structured in HEAL: their **data types**, **shapes**, **strides**, and **broadcasting** behavior.

## Data Types

HEAL tensors support integer types (`int32_t` ;`int64_t`)

Every tensor must have a clearly defined scalar type. The type is determined at tensor creation and cannot be changed.

**Example:**

```cpp
std::shared_ptr<DeviceTensor<int32_t>> my_tensor;
```

This declares a tensor of 32-bit integers, managed on the device.

## Shape

A tensor’s **shape** defines its dimensionality -  the size of the tensor along each axis.&#x20;

**Example:**

A tensor with shape `[2, 3]` contains 2 rows and 3 columns:

```
[
 [1, 2, 3],
 [4, 5, 6]
]
```

* Shape is immutable unless explicitly changed (e.g. via `reshape`).
* Shape compatibility is enforced in all HEAL operations.

## Strides

**Strides** describe how tensor elements are laid out in memory. Each stride tells you how many memory steps to take to move between elements along a particular axis.

Strides are especially important when tensors are **non-contiguous**, such as after slicing, reshaping, or transposing.

**Example:**

A tensor with shape `[2, 3]` laid out in row-major order will have strides `[3, 1]`:

* Move 3 units in memory to go from row 0 to row 1.
* Move 1 unit to move across columns.

Strides allow for memory-efficient views and slicing without copying data.

## Broadcasting Semantics

Broadcasting allows element-wise operations between tensors of different shapes, as long as the shapes are *broadcastable*. This enables operations without explicitly reshaping tensors.

### Broadcasting Rules

Two tensors are **broadcastable** if:

1. Both have at least one dimension.
2. Starting from the **last (trailing)** dimension and comparing backward:

   * Dimensions are equal&#x20;

   **or**

   * One of the dimensions is `1`&#x20;

   **or**

   * One of the tensors has **fewer dimensions**, in which case it's **left-padded with 1s** to match the other tensor's rank.

<details>

<summary>Example</summary>

```plaintext
A.shape =       [  3, 1, 5]
B.shape = [2, 1, 3, 1, 1]
```

Becomes (after left-padding `A` with two 1s):

```plaintext
A.shape = [1, 1, 3, 1, 5]
B.shape = [2, 1, 3, 1, 1]
```

Resulting broadcasted shape:

```plaintext
[2, 1, 3, 1, 5]
```

</details>

If these rules hold for every dimension, the shapes are broadcastable. The resulting shape is the maximum size in each dimension.

#### ✓  Valid Broadcasting Example

```
Shape A: [5, 1, 4, 1]
Shape B:     [3, 1, 1]

Resulting shape: [5, 3, 4, 1]
```

<details>

<summary>Why is this a valid example?</summary>

To align shapes, we left-pad B’s shape:

```
Shape A: [5, 1, 4, 1]
Shape B: [1, 3, 1, 1]
```

Comparison (from last to first):

| Dimension | A | B | Valid?     |
| --------- | - | - | ---------- |
| 4th       | 1 | 1 | ✓ equal    |
| 3rd       | 4 | 1 | ✓ one is 1 |
| 2nd       | 1 | 3 | ✓ one is 1 |
| 1st       | 5 | 1 | ✓ one is 1 |

✓ All dimensions are compatible → **broadcasting is allowed**

</details>

#### ✘ Invalid Broadcasting Example

```
Shape A: [5, 2, 4, 1]
Shape B:     [3, 1, 1]

// Error: dim mismatch (2 ≠ 3) in position 2
```

<details>

<summary>Why is this an invalid example?</summary>

Left-padding B’s shape:

```
Shape A: [5, 2, 4, 1]
Shape B: [1, 3, 1, 1]
```

Comparison:

| Dimension | A | B | Valid?     |
| --------- | - | - | ---------- |
| 4th       | 1 | 1 | ✓ equal    |
| 3rd       | 4 | 1 | ✓ one is 1 |
| 2nd       | 2 | 3 | ✘ mismatch |
| 1st       | 5 | 1 | ✓ one is 1 |

✘ Dimension 2: `2 ≠ 3` and neither is 1 → **broadcasting not allowed**

</details>

### Additional Note

In-place operations (where input and output refer to the same tensor) are **only allowed if broadcasting does not change the input's shape**.

If a tensor needs to be **broadcast to a larger shape**, it cannot safely be used as the output - doing so would write outside its allocated memory.

**Example:**

```plaintext
A.shape = [4, 1]
B.shape = [4, 3]
C.shape = [4, 3]

modsum_ttc(A, B, 6, A);  // ❌ Error: A would be broadcast to [4, 3]
modsum_ttc(A, B, 6, B);  // ✔ Safe: A and B shape remains unchanged
modsum_ttc(A, B, 6, C);  // ✔ Safe: A, B and C shape remains unchanged
```

***

{% hint style="info" %}
Want to understand how tensors are transferred between host and accelerator?

See [**HEAL Memory and Execution Model**](/core-concepts-explained/memory-and-execution-model.md)  for the full memory model and lifecycle.
{% endhint %}

***


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://healdocs.lattica.ai/core-concepts-explained/tensor.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
