Repository navigation
fix(jit): ARM64 bundle and tail encodings; v_random honours its seed (Refs #989) - #1215
Merged
Merged
Conversation
…(Refs #989) S12 of #988. Probing the capability acceptance commands, the VM example printed "f0 = -1.0000 (expected ~1.0)" for cos(v0, unbind(bind(v0, v1), v1)). Two defects were behind it, and the tests had been bent around both. src/jit_arm64.zig, two encodings: - cmgt_16b_zero emitted 0x4E20A800, which is CMLT #0, so the SIMD bundle returned 0 for every positive sum (371 of 1024 trits wrong on random input). Now 0x4E208800. - ldrsbReg emitted LDRSB Wt, which zeroes bits 63:32, while the scalar tails do 64-bit MUL, ADD, CMP and SCVTF on the result: a -1 trit past the last full 16 became 4294967295. The fused cosine was wrong for every vector with a negative trit in its tail -- 59049 % 16 = 9, so every MAX_TRITS vector -- and so was the bundle tail. Now LDRSB Xt; SMULL, which the dot products use, reads the same low 32 bits. The existing tests used all-ones or one fixed vector, and the bundle test checked no result. Each JIT file now has a differential test against a scalar reference over random trits and ten dimensions with and without a tail. It fails with either encoding restored; the x86-64 ones fail on a planted setg/setl swap. On x86-64 every kernel already agreed. src/vsa_jit.zig: the fused kernel's 0/0 for a zero vector is NaN; return 0, as the three-dot fallback and vsa.cosineSimilarity do. src/vm.zig: v_random ignored its immediate ("TODO: actually use seed") and drew seed 0, so v_random 111 and v_random 222 were one vector, and every bind/unbind program bound a vector with itself. It uses the seed now. Tests that worked around the cosine bug are restored: two VM tests ran with the JIT disabled ("has bug in cosineSimilarity"), and two e2e checks took |f0| ("known sign issue"). They assert the sign again, the roundtrip f0 > 0.7 (sqrt(2/3), about 0.82, since v1 is zero on a third of its trits). examples/vm.zig expects about 0.82 and prints 0.8155. specs/reproduce/headless.t27: what skips depends on the machine, so the floors are per architecture -- x86_64 2773 passed and at most 3 skipped, aarch64 2763 and 13 -- and the judge picks them by the record's host and refuses another architecture (ARCH). reproduce.yml runs the profile on both, aarch64 on ubuntu-24.04-arm, one artifact each. Исправлены кодировки ARM64 для bundle и хвостов; v_random учитывает seed. S12 эпика #988. При проверке acceptance-команд пример VM напечатал «f0 = -1.0000 (expected ~1.0)» для cos(v0, unbind(bind(v0, v1), v1)). За этим стояли две ошибки, и тесты были подогнаны под обе. src/jit_arm64.zig, две кодировки: - cmgt_16b_zero выдавал 0x4E20A800, то есть CMLT #0, и SIMD-bundle возвращал 0 для любой положительной суммы (371 из 1024 тритов неверно на случайных данных). Теперь 0x4E208800. - ldrsbReg выдавал LDRSB Wt, который обнуляет биты 63:32, а скалярные хвосты делают над результатом 64-битные MUL, ADD, CMP и SCVTF: трит -1 после последней полной шестнадцатки становился 4294967295. Fused cosine ошибался для любого вектора с отрицательным тритом в хвосте -- 59049 % 16 = 9, то есть для каждого вектора MAX_TRITS, -- как и хвост bundle. Теперь LDRSB Xt; SMULL, которым пользуются скалярные произведения, читает те же младшие 32 бита. Прежние тесты брали векторы из единиц или один фиксированный вектор, а тест bundle вообще не проверял результат. В каждом JIT-файле теперь есть дифференциальный тест против скалярного эталона на случайных тритах и десяти размерностях с хвостом и без. Он падает при возврате любой из двух кодировок; x86-64 тесты падают на подложенной перестановке setg/setl. На x86-64 все ядра и так совпадали. src/vsa_jit.zig: 0/0 fused-ядра для нулевого вектора -- это NaN; возвращаем 0, как запасной путь с тремя скалярными произведениями и vsa.cosineSimilarity. src/vm.zig: v_random игнорировал аргумент («TODO: actually use seed») и брал seed 0, так что v_random 111 и v_random 222 были одним вектором, и каждая программа bind/unbind связывала вектор сам с собой. Теперь seed учитывается. Тесты, обходившие ошибку косинуса, возвращены: два теста VM шли с выключенным JIT («has bug in cosineSimilarity»), две проверки e2e брали |f0| («known sign issue»). Они снова проверяют знак, roundtrip -- f0 > 0.7 (sqrt(2/3), около 0.82, потому что v1 нулевой на трети тритов). examples/vm.zig ждёт около 0.82 и печатает 0.8155. specs/reproduce/headless.t27: что пропускается, зависит от машины, поэтому нижние границы заданы по архитектурам -- x86_64: 2773 пройдено и не больше 3 пропусков, aarch64: 2763 и 13, -- судья выбирает их по host записи и отказывается судить другую архитектуру (ARCH). reproduce.yml гоняет профиль на обеих, aarch64 на ubuntu-24.04-arm, по артефакту на каждую. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
S12 of #988 (Refs #989). While probing the capability cards' acceptance commands for the capability index, the VM example printed this for
cos(v0, unbind(bind(v0, v1), v1)):Two defects were behind it, and the tests had been bent around both.
1. Two ARM64 encodings in
src/jit_arm64.zigcmgt_16b_zero0x4E20A800ldrsbReg0x38E06800The second one broke the fused cosine for every vector with a negative trit in its tail. Since 59049 % 16 = 9, that means every
MAX_TRITSvector, and it is why the VM'sv_cosinecalled two identical vectors −1.0. It also broke the bundle tail.The fixes are
0x4E208800(CMGT) and0x38A06800(LDRSB Xt). SMULL, which the dot products use, reads the same low 32 bits, so nothing that already worked can change.The existing tests used all-ones vectors or one fixed vector, and the bundle test checked no result at all ("to be verified via integration tests"). Each JIT file now has a differential test against a scalar reference, over random trits and ten dimensions (1 to 59049, with and without a tail):
src/jit_arm64.zig: dot (scalar, SIMD, hybrid), hamming, fused cosine, bind (direct, SIMD), bundle. It fails with either encoding restored.src/jit_x86_64.zigandsrc/jit.zig: dot, bind, bundle. Every x86-64 kernel already agreed; the test fails on a plantedsetg/setlswap.Off its own architecture each test skips (
error.SkipZigTest) instead of returning early as a pass.2.
v_randomignored its seedexecVRandomhad_ = seed; // TODO: actually use seedand drew seed 0. Sov_random 111andv_random 222were the same vector, and every bind/unbind program bound a vector with itself. It now uses the seed.The tests that had been bent around both
src/vm.zig, two testsvm.jit_enabled = false; // Disable JIT (has bug in cosineSimilarity)src/e2e_test.zig, VM roundtrip@abs(f0) > 0.3("JIT engine may return negative cosine (known sign issue)")f0 > 0.7. The expected value is √(2/3) ≈ 0.82, because v1 is zero on a third of its tritssrc/e2e_test.zig, VERDICT check 4@abs(f0) > 0.99f0 > 0.99examples/vm.zigsrc/vsa_jit.zig: for a zero vector the fused kernel computes 0/0 = NaN, the only way it can produce NaN. It now returns 0, as the three-dot fallback andvsa.cosineSimilaritydo.The gate
What skips now depends on the machine, so
specs/reproduce/headless.t27sets its floors per architecture:The judge picks the floors by the record's host and refuses to judge another architecture (
ARCH); the self-check plants both architectures and a third.reproduce.ymlnow runs the headless profile on both architectures, aarch64 onubuntu-24.04-arm, with one artifact each. With x86_64 alone, no runner could execute the ARM64 kernels; this is how both encodings survived.Measured: Linux aarch64 in a container with empty caches gives build 94/94 with 46/46 installs, and test 135/135 steps with 2763/2776 passed and 13 skipped, no violation. x86_64 is this PR's CI.
{ "version": 1, "head_sha": "acaad5eeef91846f921eea84eaff50198231830d", "summary": "The ARM64 JIT computed wrong bundles and cosines and the VM drew one random vector for every seed; both are fixed, the tests that had been bent around them assert the right values again, and the profile now runs on aarch64 as well as x86_64.", "changes": [ "Fixed two encodings in src/jit_arm64.zig: CMGT #0 was emitted as CMLT #0, and LDRSB loaded into W under 64-bit tail arithmetic.", "Added differential tests against a scalar reference to src/jit_arm64.zig, src/jit_x86_64.zig and src/jit.zig, over random trits and dimensions with and without a tail; each skips off its own architecture.", "Made v_random use its seed, mapped the fused cosine's 0/0 to 0, and restored the VM and e2e assertions that had disabled the JIT or taken the absolute value of the cosine.", "Made the headless floors per architecture and ran the profile on ubuntu-24.04-arm as well as ubuntu-latest." ], "tests": [ { "command": "zig test src/jit_arm64.zig on Linux aarch64", "status": "passed", "result": "25 of 25 tests, including the differential test; with CMLT restored it reports bundle_simd wrong at every dimension, with LDRSB Wt restored it reports cosine and bundle tails wrong.", "evidence": "Ubuntu 24.04 aarch64 container, Zig 0.15.2, run locally before the commit" }, { "command": "zig test src/jit_x86_64.zig and src/jit.zig -target x86_64-linux-gnu, run in an amd64 container", "status": "passed", "result": "6 of 6 and 7 of 7 tests; a planted setg/setl swap in jit.zig makes the differential test report bundle wrong at nine dimensions.", "evidence": "Run locally under emulation before the commit" }, { "command": "python3 tools/reproduce.py headless --zig zig-0.15.2 in ubuntu:24.04 aarch64 with empty caches", "status": "passed", "result": "Build 94/94 with 46/46 installs; test 135/135 steps, 2763/2776 passed and 13 skipped, 66 run steps with tests and one empty, no compile failure, both commands exit 0; no violation against the aarch64 floors.", "evidence": "Linux aarch64 container, head acaad5eeef91846f921eea84eaff50198231830d, fresh zig caches, 2 min 36 s" } ], "limitations": [ "The x86_64 floors (2773 passed, 3 skipped) are derived from the counts of the added tests and the previous x86_64 run; this PR's x86_64 job measures them.", "Most older tests in src/jit_arm64.zig and src/jit_x86_64.zig still return early off their architecture and count as passed; the new differential tests skip instead.", "The aarch64 leg depends on GitHub's ubuntu-24.04-arm runners." ], "tags": [ "Engineering", "Verification", "CI" ], "blog": { "title": "One opcode bit made two identical vectors look opposite", "summary": "An ARM64 CMLT where a CMGT was meant, a 32-bit load under 64-bit arithmetic, and a random instruction that ignored its seed, hidden for months by tests bent around them.", "outline": [ "The symptom: the VM said cos(v, v) = -1.0, and the tests had been relaxed to accept it.", "The causes: two ARM64 encodings and a seed that was never read, found by testing every kernel against a scalar reference.", "The gate: differential tests that skip off their architecture, floors per architecture, and an ARM64 runner." ] } }Dmitrii Fedorov (@dmitrii-f-t27)