Skip to content

Fix Tensor::normalize()/norm() and PHP 8.4/8.5 deprecations - #106

Open
vendelev wants to merge 5 commits into
CodeWithKyrian:mainfrom
vendelev:fix/tensor-normalize-and-deprecations
Open

Fix Tensor::normalize()/norm() and PHP 8.4/8.5 deprecations#106
vendelev wants to merge 5 commits into
CodeWithKyrian:mainfrom
vendelev:fix/tensor-normalize-and-deprecations

Conversation

@vendelev

@vendelev vendelev commented Sep 1, 2026

Copy link
Copy Markdown

What:

  • Bug Fix
  • New Feature

Description:

Three bugs found while integrating TransformersPHP into a PHP-only semantic
search pipeline (E5 embeddings + pgvector).

  1. Tensor::normalize() / Tensor::norm() (axis branch) silently no-op.
    TensorBuffer implements ArrayAccess/Countable but not
    Iterator/IteratorAggregate. foreach ($this->buffer as $i => $value)
    on such an object iterates its public properties instead of throwing —
    for TensorBuffer that means zero iterations. So norm()'s
    accumulation loop never ran and normalize() never divided anything.
    Confirmed via feature-extraction/embeddings pipeline with
    normalize: true: the returned vector was not unit-length at all
    (‖v‖² ≈ 20.5 instead of 1.0) for a real 384-dim E5 embedding.
    Fix: iterate over toBufferArray() instead, same pattern already used
    by the axis === null branch a few lines above.

  2. norm() never takes the root for L2 (and any $ord != 1).
    if ($ord === 1) { $result = $mo->op($result, '**', 1 / $ord); } — for
    $ord = 1 this is a no-op anyway (1/1 = 1), and for $ord = 2 (the
    common L2 norm) the square root is never applied, so norm()
    returned the sum of squares instead of the norm. Condition should be
    $ord !== 1.

  3. ord() deprecation on PHP 8.4+ in Precompiled::DoubleArray::commonPrefixSearch().
    mb_str_split($key) can yield multi-byte graphemes (e.g. Cyrillic),
    and ord() on a string longer than one byte is deprecated since
    PHP 8.4. Fixed with ord($c[0]), as suggested by the deprecation
    notice itself (same first-byte behaviour, no warning).

  4. curl_close() deprecation on PHP 8.5 in Downloader.php.
    curl_close() has been a no-op since PHP 8.0 (the handle is closed by
    GC) and triggers a deprecation notice on PHP 8.5. Removed the three
    calls.

All four are isolated, one commit per bug. Verified end-to-end against
Xenova/multilingual-e5-small: before the fix, normalize: true produced
non-unit vectors; after, ‖v‖² = 1.000000 exactly.

Added regression tests in tests/tensors/TensorTest.php, verified they fail against the pre-fix code.

Related:

TensorBuffer implements ArrayAccess/Countable but not Iterator or
IteratorAggregate. `foreach ($this->buffer as ...)` on such an object
iterates its public properties instead of raising an error, which for
TensorBuffer means zero iterations. As a result, norm()'s axis-reduction
loop never accumulated anything and normalize() never divided any value,
silently turning both into no-ops for real (non-scalar) tensors.

Use toBufferArray() instead, matching the pattern already used by the
axis === null branch in norm().
The condition was inverted: raising the accumulated sum to the power of
1/ord was gated on `$ord === 1`, where 1/1 = 1 makes it a no-op anyway.
For the common case $ord = 2 (L2 norm) the square root was never taken,
so norm() returned the sum of squares instead of the actual norm.
mb_str_split() can return multi-byte graphemes (e.g. Cyrillic), and
ord() on PHP 8.4+ is deprecated when given a string longer than one
byte. Use ord($c[0]) as suggested by the deprecation notice itself,
which preserves the previous (first-byte) behaviour.
curl_close() has been a no-op since PHP 8.0 (CurlHandle is closed by
the garbage collector) and is deprecated since PHP 8.5.
Adds regression tests for the two bugs fixed in the preceding commits:
- norm() with an axis now actually accumulates values (buffer iteration)
  and takes the 1/ord root for L2.
- normalize() actually divides every element by the computed norm,
  producing a unit-length vector.

All four new tests fail against the pre-fix implementation.
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