Skip to content

Add commonly-used methods on tensors - #164

Merged
marcelluethi merged 12 commits into
dimwit-dev:mainfrom
marcelluethi:more_elementary_jax_methods
Sep 24, 2026
Merged

marcelluethi merged 12 commits into
dimwit-dev:mainfrom
marcelluethi:more_elementary_jax_methods

Conversation

@marcelluethi

Copy link
Copy Markdown
Contributor

DimWits wrapping of the basic tensor methods supported in jax is rather patchy and was introduced on a per need basis.
This PR proposes to add some more, frequently used tensor operations. Each of them is just a one-liner, wrapping the underlying jax function.

The methods added are:

  • sort
  • cumsum, cumprod
  • diff
  • floor, ceil, round
  • arcsin, arccos, arctan
  • isnan, isfinite, nanToNum
  • mod (with a % operator)

Comment thread core/src/main/scala/dimwit/tensor/tensorops/ReductionOps.scala Outdated
Comment thread core/src/main/scala/dimwit/tensor/tensorops/ReductionOps.scala Outdated
Comment thread core/src/main/scala/dimwit/tensor/tensorops/ElementWiseOps.scala
Comment thread core/src/main/scala/dimwit/tensor/tensorops/ElementWiseOps.scala
Comment thread core/src/main/scala/dimwit/tensor/tensorops/ElementWiseOps.scala Outdated
Comment thread core/src/main/scala/dimwit/tensor/tensorops/ReductionOps.scala Outdated
@benikm91

benikm91 commented Sep 6, 2026

Copy link
Copy Markdown
Collaborator

@marcelluethi I made some targeted comments for specific lines of code. Overall, my view is this: JAX has many functions that are conceptually functions on Tensor1, but are defined for higher-order tensors, taking an axis parameter and having different default behaviors (sometimes the last axis, sometimes flattening the tensor).

An illustrative example for this is cross (not, yet in DimWit). Mathematically, an operation: vector x vector -> vector, so an operation on vectors. In JAX, we can provide two higher-dimensional tensors, and two (optional) axis arguments to say which vector(s) within these tensors, and even an output axis where to put the resulting vector(s). In DimWit the user "provides" these axis arguments with vmap or vapply, with cross being a function (Tensor1, Tensor1) => Tensor1, representing the mathematical scope.
This: When lifting JAX methods to DimWit, we should always be very skeptical about axis arguments. They are often a design flaw by JAX/numpy, that we can fix in DimWit.

image

https://docs.jax.dev/en/latest/_autosummary/jax.numpy.cross.html

@benikm91

Copy link
Copy Markdown
Collaborator

Maybe this approach is the best of both worlds (let's discuss tomorrow 👍 ):

Operations, which allow no or multiple axes in JAX, are define on Tensor like sum, mean, ... (as currently in main)

extension (t: Tensor[...])
  def sum(...)
t.sum
t.sum(Axis[A])

Operations, which require exactly one axis in JAX, are functions on vectors and should be defined on Tensor1.
However, we add an overload on Tensor that calls the function with vapply.
This achives both the right function scope and JAX-esk API:

// main branch
extension (t: Tensor1[...])
  def softmax: Tensor1[...] = ... // correct scoping

// not yet on main branch
extension (t: Tensor[...])
  def softmax(axis: Axis[A]): Tensor[...] = t.vapply(axis)(softmax) // syntax sugar
  
t.vapply(Axis[A])(softmax) // current
t.softmax(Axis[A]) // new option to do this

The implementation might be difficult due to Tensor1 being a Tensor, but should be possible with type classes. Let's first consider the API itself.

This consideration applys to sort, etc. from this PR, so let's decide here.

@marcelluethi

marcelluethi commented Sep 15, 2026 •

Copy link
Copy Markdown
Contributor Author

So I understand that there are operations like diff, cumprod, softmax that conceptually are mappings from Tensor1 to Tensor1. These we could put in a object Tensor1Transform, e.g.:

object Tensor1Transform:
    def softmax[V : Floating](t : Tensor1[A, V]) : Tensor1[A, V] = ???  
    def diff[V](t : Tensor1[A, V]) : Tensor1[A, V] = ???

and in Tensor1Transform we have the convenience extension method:

extension t : Tensor[T, V : Floating]
    def softmax[L](axis : Axis[L]) : Tensor[T, V] = vapply(axis)(Tensor1Transform.softmax)

To use the methods, the user has two options:

  1. Use t.softmax(Axis[L]) on an tensor of arbitrary dimension
  2. Write vapply(Axis[L])(Tensor1Transform.softmax)

@marcelluethi

Copy link
Copy Markdown
Contributor Author

@benikm91 I started doing the refactoring discussed above and incorporating your comments.

def isfinite: Tensor[T, Bool] = Tensor(Jax.jnp.isfinite(t.jaxValue))

/** replaces NaN by `nan`, +inf by `posInf` and -inf by `negInf`.
* By default, NaN becomes 0 and ±inf become the largest/smallest finite value of the dtype.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment out-of-date.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

/** sorts the tensor `t` along the specified axis */
def sort[L: Label](axis: Axis[L])(using AxisIndex[T, L]): Tensor[T, V] =
t.vapply(axis)(Tensor1.sort)
def sort: Tensor[T, V] = Tensor(Jax.jnp.sort(t.jaxValue))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove default sort (last axis by convention).

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I also removed argsort and I added tests that these methods do not exists (for Tensor2 objects) as we don't support a default axis if none is provided. We may support t1.sort (for Tensor1) in the future; tests are all written with Tensor2 for this case.

/** replaces NaN by `nan`, +inf by `posInf` and -inf by `negInf`.
* By default, NaN becomes 0 and ±inf become the largest/smallest finite value of the dtype.
*/
def nanToNum(using

@benikm91 benikm91 Sep 20, 2026 •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This IsFloating[V] can be removed. IsFloating evidence already in extention method.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tested it, it fails without it. Reason: The V: IsFloating is added after this parameter group, so IsFloating is not in scope for the default. So keep as is here.

// activation functions
def sigmoid: Tensor[T, V] = Tensor(Jax.jnn.sigmoid(t.jaxValue))
def relu: Tensor[T, V] = Tensor(Jax.jnn.relu(t.jaxValue))
def gelu: Tensor[T, V] = Tensor(Jax.jnn.gelu(t.jaxValue))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This changed syntax to t.relu. We need relu(t), as this is more natural for activation functions. I propose to put this in DimWit, here, but I am fine to move this also to DeepWit only.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a bigger issue: In Tensor1 we have some functions, e.g., for softmax, but they are missing for ElementWiseOps and ReductionOps, etc. This is a inconsitency we should address. But maybe in a separate PR.

@benikm91 benikm91 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See new comments.

Only the one def sort that defaults to axis=-1 must be removed, the other changes are optional, but consider them. If sort is removed I will approve.

@benikm91
benikm91 self-requested a review September 24, 2026 07:30

@benikm91 benikm91 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. I done the last few bits myself as discussed with @marcelluethi in our train ride 👍

@marcelluethi
marcelluethi merged commit 35ba602 into dimwit-dev:main Sep 24, 2026
1 check passed
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.

2 participants