Fix test-samsung-models-linux: accelerate dep and the datasets/torchvision collate - #22870
Conversation
🔗 Helpful Links🧪 See artifacts and rendered test results at hud.pytorch.org/pr/pytorch/executorch/22870
Note: Links to docs will display an error until the docs builds have been completed. ✅ No FailuresAs of commit 4750a57 with merge base 1f4ea61 ( This comment was automatically generated by Dr. CI and updates every 15 minutes. |
There was a problem hiding this comment.
🟢 Approval recommended
The required test dependency is added with no unresolved review issues.
Pull request overview
Adds the missing accelerate>=1.1.0 dependency required by Samsung MobileBERT QAT tests.
Changes:
- Installs
acceleratealongsideevaluate. - Documents the dependency requirement.
File summaries
| File | Description |
|---|---|
.github/workflows/trunk.yml |
Updates Samsung model-test dependencies. |
Review details
- Files reviewed: 1/1 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
…ge (#22869) Fixes the `unittest-release` failure that has been red on `main` since Sep 14, on linux, macOS and windows. ## The bug `#22663` added `NestedCallsPreserveOuterThreadNumber`, which asserts that an empty nested range never invokes its callback, and parameterized it over **both** `parallel_for` implementations. It added the guard to one of them: - `extension/threadpool/thread_parallel.cpp` — got `if (begin < end) { f(begin, end); }` ✅ - `runtime/kernel/thread_parallel_interface.h` — untouched by that PR ❌ In the second, `parallel_for_no_threadpool` does: ```cpp #ifndef NDEBUG for (const auto i : c10::irange(begin, end)) { ... f(idx, idx + 1); } // empty range -> no call #else f(begin, end); // <- unconditional #endif ``` Debug builds pass only by accident — `c10::irange(2, 2)` is empty, so the callback is skipped as a side effect. Release builds call it, and the test fails ten times with `Empty nested range invoked its callback`. That is exactly the observed CI signature: `unittest` green on all three platforms, `unittest-release` red on all three, on every affected commit. Platform-independent, as a pure `#ifdef` divergence should be. ## The fix Check `begin == end` above the `#ifdef` rather than duplicating the guard into the release arm, so the two branches agree by construction instead of by coincidence. ## Why it matters beyond the test `unittest-release` lives in the `trunk` workflow, and `trunk` is in `update-viablestrict`'s `requires` list. One red job reddens the workflow, which disqualifies the commit, so `viable/strict` has not advanced since Sep 14 (51 commits). This is one of two blockers; the other is the Samsung job, fixed separately in #22870. ## Verification Reproduced against the real header — DEBUG: 0 callback invocations, RELEASE: 1. Then built the actual `extension/threadpool/test/thread_parallel_test.cpp`: | | before | after | |---|---|---| | release | `NestedCallsPreserveOuterThreadNumber` FAILS | **26/26 pass** | | debug | 26/26 pass | **26/26 pass** | No new test is needed — `#22663`'s test already covers this and is the one that was failing. Caveat: my local harness linked without `ET_USE_THREADPOOL`, so the `/0` variant routed through the same function as `/1`. The `/1` case, which is the actual CI failure, is faithful. I did not run it on Linux or Windows. cc @JakeStevens @digantdesai — author and reviewer of `#22663`. --- Authored with Claude Code.
Update: fix confirmed working, but a second failure was hiding behind itThe So the diagnosis in this PR was right and the change is still required — it just isn't the whole story. The second issueNot a version-pin problem. From torchvision's own # In fbcode, import from the fb-only location
# For OSS, these imports would fail (video_reader not available)
try:
from pytorch.vision.fb.io import (..., VideoReader)
except ImportError:
pass
if config.TORCHVISION_AVAILABLE and "torchvision" in sys.modules:
from torchvision.io import VideoReader # assumes it existswhere The trigger on our side is tokenized_datasets.set_format(type="torch", columns=["input_ids", "attention_mask", "label"])Options, none of them obviously right
I'd lean 1, or 3 if the runtime cost is itself unwanted — but this is an owner call, so I have not CorrectionI earlier stated that this PR plus #22869 were "necessary and sufficient" to unstick |
Forward fix pushed (
|
There was a problem hiding this comment.
🔵 Needs a closer look
Scope or restore the process-wide torchvision configuration mutation.
Review details
Suppressed comments (1)
examples/samsung/scripts/mobilebert_finetune_QAT.py:151
- This mutates
datasets.config.TORCHVISION_AVAILABLEprocess-wide and leaves it disabled afterload_CSV_datasetreturns. Any later caller in the same process that uses adatasetsimage/video column will silently lose torchvision decoding, and concurrent dataset consumers can race with this flag. Please scope the workaround to this pipeline's DataLoader/Trainer lifetime (or otherwise avoid a global config mutation) and restore the prior value during cleanup.
datasets.config.TORCHVISION_AVAILABLE = False
- Files reviewed: 2/2 changed files
- Comments generated: 0 new
- Review effort level: Lite
Both fixes confirmed working —
|
| Run | test_mobilebert_qat_a8w8 |
test_mv2_fp16 |
|---|---|---|
| 6 runs before these fixes | ERROR | pass |
| this run | pass | FAIL |
I can't call it unrelated flake outright. There is an indirect path: unittest discover runs
test_mobilebert_qat before test_mobilenet_v2, and mobilebert previously aborted early. It now
runs a full QAT finetune and device export first, so the NPU has done substantially more work by
the time mv2 runs. Nothing in these commits touches mv2's code, but what precedes it on the device
did change.
The alternative reading is simply that test_mv2_fp16 is marginal at atol=0.02 and this is its
first observed flake.
I have re-run the failed jobs to discriminate. If mv2 passes on the re-run, it is flake and these
commits are complete. If it fails again at a similar magnitude, the ordering/device-state
hypothesis needs investigating and test_mv2_fp16's tolerance is probably too tight for fp16 on
hardware.
cc @digantdesai
Correction to an earlier comment of mineI described
The finetune costs roughly 20 minutes. The ~3 hours is the other 11 tests in the suite, which For the record on cadence, since I had that vague too: this job does not run per-PR. A separate issue worth a lookWhile checking the above I noticed the job's device guard: if [[ "${DEVICE_ACQUIRED:-0}" != "1" ]]; then
echo "::warning::Skipping tests - no Samsung device available"
exit 0
fiWhen no Samsung device is free this exits 0 — the job reports success having run no tests, and |
Re-run result: not flake — deterministic, and it is a test-isolation bugThe re-run failed identically. Not "similarly" — bit for bit: A flaky fp16 hardware comparison would vary in the last digits. Identical output twice means the Both fixes in this PR remain confirmed: What this means
So the most likely explanation is that the MobileBERT QAT test leaves global state behind that I have not proven that mechanism — I cannot run the Samsung device. What is proven is that it is Where that leaves this PRThe two commits here are correct and I would not change them. But they do not make the job green, @digantdesai — this is worth an Arm/Samsung owner. The narrow question: does |
Root cause found —
|
| Observation | Explained by |
|---|---|
| bit-identical failure across two runs | seed is fixed at 42, so the "different" input is the same every time |
| passed in all 6 runs where mobilebert aborted early | those aborted before Trainer, so the RNG was never reseeded |
| fails in both runs where mobilebert completed | Trainer ran, RNG reseeded |
| magnitude only just over tolerance (0.02465 vs 0.02) | a different random draw, not a systematic error |
What this means
The commits in this PR did not break test_mv2_fp16. They exposed that it was depending on whatever RNG state happened to be left by the tests before it. Note also that the six previous passes each used a different random input, so atol=0.02 holds for most draws but not for the one seed 42 produces — the test was passing partly by luck.
The fix is not mine to pick
Two parts, and the second needs hardware:
- Make the input deterministic — seed the RNG in the test (or in
SamsungTester) so it no longer depends on execution order. Unambiguously correct hygiene. - Decide the tolerance. Once the input is pinned,
atol=0.02either holds for that input or it does not, and I cannot measure that without the NPU. Picking a seed that happens to pass would be cherry-picking; the honest version is to pin the seed and set a tolerance that holds across a few of them.
I have not pushed either, because part 2 determines part 1 and both need someone who can run the device. Happy to implement whichever you choose.
@digantdesai — this is now a concrete, fully-explained bug with a stable repro, rather than a mystery flake.
test_mobilebert_qat_a8w8 builds a transformers TrainingArguments, which
requires accelerate>=1.1.0. The job installs only evaluate, so the test
has errored since it started running:
ImportError: Using the `Trainer` with `PyTorch` requires
`accelerate>=1.1.0`
It is the sole error in the suite on every affected commit -- "Ran 12
tests ... FAILED (errors=1)" -- and the non-zero exit reddens the trunk
workflow, which update-viablestrict requires. Pinned rather than bare,
because transformers raises the same error for a too-old version.
With accelerate installed the test gets as far as trainer.train() and then
dies collating the first batch:
datasets/formatting/torch_formatter.py, in _tensorize
from torchvision.io import VideoReader
ImportError: cannot import name 'VideoReader' from 'torchvision.io'
datasets takes that branch whenever torchvision merely imports:
if config.TORCHVISION_AVAILABLE and "torchvision" in sys.modules:
from torchvision.io import VideoReader
but OSS torchvision ships VideoReader only in Meta-internal builds -- its
io/__init__ wraps the import in `except ImportError: pass`, so the symbol is
absent rather than the module. timm pulls torchvision into sys.modules, so
the branch is taken and the import fails. Every datasets release through
4.3.0 does this, so there is no version to move to.
This dataset is text only and never decodes an image or a video, so the
torchvision path is dead weight. Turning the flag off before set_format
keeps the tensors and skips the branch. Reproduced against datasets 3.6.0
with a torchvision whose io has no VideoReader: ImportError before, correct
int64 tensors after.
transformers' Trainer calls set_seed(args.seed) from both __init__ and train(), which reseeds Python, NumPy and torch globally. unittest runs the suite in one process and test_mobilebert_qat sorts ahead of test_mobilenet_v2, so once this test actually reached Trainer -- which it only started doing once the two preceding commits fixed its dependencies -- everything after it drew from a different stream. test_mv2_fp16 builds its input with an unseeded torch.randn and compares against atol=0.02, so it does not survive the change of input: it failed bit-identically on both runs, 0.02465 against a 0.02 tolerance, having passed in the six earlier runs where this test aborted before Trainer. Snapshot the three generators in setUp and restore them in tearDown. Fixes the test that leaks rather than the one that noticed, and leaves test_mv2_fp16 seeing the same stream it saw before. The tolerance question is separate and still open: test_mv2_fp16 draws a fresh random input every run, so atol=0.02 holds for most draws and not for others. That wants an owner with the hardware, not a seed chosen because it passes.
ab1428e to
4750a57
Compare
Pushed the isolation fix (
|
There was a problem hiding this comment.
🔵 Needs a closer look
CUDA RNG state is not restored on CUDA-capable runners, allowing the test to affect later tests.
Review details
Suppressed comments (2)
backends/samsung/test/models/test_mobilebert_qat.py:39
- The new comment has a grammatical error in
the draw seed 42 produces lands just outside it, which makes the explanation harder to follow. Please rephrase this sentence.
# unseeded torch.randn and compares against atol=0.02, and the draw seed 42
# produces lands just outside it. Snapshot the generators here and restore
backends/samsung/test/models/test_mobilebert_qat.py:44
- The cleanup only snapshots/restores the CPU generator.
get_finetune_mobilebert()selects CUDA when it is available, and Transformers'set_seedalso reseeds CUDA generators, so on a CUDA runner this test still changes the CUDA RNG consumed by later tests. Snapshottorch.cuda.get_rng_state_all()insetUpand restore it intearDownwhen CUDA is available.
self._torch_rng_state = torch.get_rng_state()
- Files reviewed: 3/3 changed files
- Comments generated: 0 new
- Review effort level: Lite
Follow-up needed:
|
| Runs | Input | Result |
|---|---|---|
| 6 runs before this PR | fresh random draw each time | passed |
2 runs after test_mobilebert_qat began completing |
the draw following torch.manual_seed(42) |
failed, bit-identical both times |
The failing magnitude was max: 0.02465033531188965 against atol: 0.02 — about 23% over, i.e. the
same order as the tolerance, not a gross error. Mean abs error was 0.00578. So the model is
behaving normally; the tolerance simply does not cover the spread across random inputs.
The bit-identical repeat is the tell: the computation is deterministic for a given input, so the six
earlier passes were six different draws that happened to land under the bar.
How to reproduce deliberately
import torch
torch.manual_seed(42) # what transformers' Trainer leaves behind
# then run test_mv2_fp16 -- it draws the failing inputOr run the suite with test_mobilebert_qat preceding it and the RNG restore from this PR removed.
Options, for whoever picks this up
- Pin the input and set a tolerance that holds for it. Cheapest. The risk is choosing a seed
because it passes, which re-hides the spread rather than measuring it. - Pin the input and widen
atolto cover the observed spread. Needs a handful of draws
measured on the device to pick a defensible number.0.02465is one known data point; nobody has
sampled the distribution. - Use a fixed, representative input (a real image tensor rather than
randn) so the comparison
is against something meaningful instead of noise. Arguably the most correct for an accuracy test,
and the most work.
Option 2 or 3 is the honest fix. Option 1 alone would leave the same latent fragility.
This needs someone who can run the Samsung NPU — I cannot measure the spread without it, and
picking a number blind would be guessing.
@digantdesai for routing to the right owner.
|
trunk / test-samsung-models-linux / linux-job (push)Successful in 217m Job passes with this fix. Merging now |
Fixes
test-samsung-models-linux, red onmainsince the job started running on Sep 14.The bug
test_mobilebert_qat_a8w8constructs atransformersTrainingArguments, which requiresaccelerate>=1.1.0. The job installs onlyevaluate, so:It is the sole error in the suite, identically, on every affected commit:
f3c08fcf35Ran 12 tests ... FAILED (errors=1)4798a676c3Ran 12 tests ... FAILED (errors=1)500849ba5bRan 12 tests ... FAILED (errors=1)cfc96cc0e2Ran 12 tests ... FAILED (errors=1)026ca3fff3Ran 12 tests ... FAILED (errors=1)accelerateappears nowhere in the job logs, so it is absent rather than stale — but the pin isstill the right form, because
transformersraises the same error for a too-old version.Why it matters beyond the test
The non-zero exit propagates:
unittestexits 1 → the script runs underset -ex→run_with_env_secrets.pyraises → the job fails → thetrunkworkflow concludes failure.trunkis in
update-viablestrict'srequireslist, so the commit is disqualified andviable/stricthasnot advanced since Sep 14. This is one of two blockers; the other is the
parallel_forempty-rangebug, fixed separately in #22869. Both are needed — each independently reddens
trunk.Verification
Honest scope: I verified the diagnosis, not the fix. The sole error is identical across five
commits and
accelerateis never installed in the job. I cannot run this job — it needs theSamsung device and
SAMSUNG_AI_LITECORE_KEY— so the one-liner itself is untested.trunk.ymlparses and
lintrunneris clean.One note for reviewers: the
[CDI: ERROR] only support axis as 1/OP LOGSOFTMAXlines in the logappear after
FAILED (errors=1). They are teardown noise from a passing test, not a secondfailure.
cc @digantdesai
Authored with Claude Code.