Skip to content

RISC-V: fix ISMIN/IDMIN in the ZVL256B imin_vector kernel - #6169

Open
HiderX wants to merge 1 commit into
OpenMathLib:developfrom
HiderX:riscv-zvl256b-imin-mu
Open

HiderX wants to merge 1 commit into
OpenMathLib:developfrom
HiderX:riscv-zvl256b-imin-mu

Conversation

@HiderX

@HiderX HiderX commented Oct 25, 2026

Copy link
Copy Markdown

Summary

On RISCV64_ZVL256B, cblas_ismin/cblas_idmin (and ISMIN/IDMIN) return a wrong
index for many inputs longer than one vector strip. imin_vector.c has two bugs
in its RVV 1.0 intrinsics branch:

  1. The merge operand is missing. The masked vid/vadd instructions that
    record each lane's running-minimum index use the mask-agnostic
    __riscv_vid_v_*_m / __riscv_vadd_vx_*_m forms, which take no merge
    operand. A lane whose minimum was found in an earlier strip therefore loses
    its index.

    • This was introduced by the v1.0 intrinsics port (2406958).
    • In that same commit, imax_vector.c got the mask-undisturbed _mu forms.
    • The RISCV_0p10_INTRINSICS branch of imin_vector.c still passes
      v_min_index as the merge operand.

    The patch uses the _mu forms, with v_min_index as the merge operand.

  2. The tail comparison is inverted. The tail checks cur_minf > minf. That
    can never be true, because cur_minf is reduced from minf, so a minimum in
    the last partial strip is ignored. The check is now cur_minf < minf, as in
    iamin_vector.c.

Both the unit-stride and the strided paths are fixed. Not affected:

  • C910V, which uses the 0.10 intrinsics branch;
  • the targets that use imin_rvv.c.

Testing

Platform: SpacemiT K3, one X100 core, TARGET=RISCV64_ZVL256B, GCC 15.2.

  • Targeted check of i?amax, i?amin, i?max, i?min (S and D):

    • strides 1, 2 and 3;
    • 47 lengths from 1 to 32775, covering strip boundaries and tails;
    • the extreme is planted at strip boundaries, in the tail, at a random
      position, and as a tie;
    • 9624 cases in total.

    Results:

    • develop (5bab605): 1321 mismatches, all in ISMIN/IDMIN. For example,
      ISMIN with n = 65 returns 6 instead of 64.
    • patched: 0 mismatches.
  • OpenBLAS tests. The default make goal's tests give identical results
    before and after the patch: utest 141/141, extension tests 1479/1479, and all
    BLAS/CBLAS tests PASSED.

  • Performance. The patched kernel runs exactly as fast as its sibling
    imax_vector.c. At n = 1024, ISMIN/IDMIN take 472/821 ns per call, and
    ISMAX/IDMAX take 473/821 ns. The old kernel was faster on in-cache sizes
    (257/387 ns) only because dropping the merge operand also removed the
    loop-carried dependency on the index vector, at the cost of losing the
    indices. At n = 4M the patched kernel is about 3 % faster.

This bug was found while comparing the _vector and _rvv level-1 kernels on
the X100.

Disclosure: an AI coding agent (Anthropic Claude) prepared this patch, its
validation runs and this description on behalf of the account owner. All
numbers above come from archived runs on the board.

Two bugs make i?min return a wrong index on RISCV64_ZVL256B (imin_vector.c with the
v1.0 RVV intrinsics):

- The v1.0 port (2406958) maps the masked vid/vadd to the mask-agnostic
  __riscv_vid_v_*_m / __riscv_vadd_vx_*_m forms and drops the merge operand, so
  the indices of lanes whose minimum was found in an earlier strip are lost.
  Use the mask-undisturbed _mu forms with v_min_index as the merge operand, as
  imax_vector.c does and as the RISCV_0p10_INTRINSICS branch already does.
- The tail compares cur_minf > minf, which can never hold because cur_minf is
  reduced from minf; a minimum in the tail was ignored. Compare with <, as
  iamin_vector.c does.

This branch has not been deployed

No deployments
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