Files
transformers/docs/source/ja/llm_tutorial.md
Tarek ZiadeandClaude Opus 5 c694707483 Synthetic test assets (#48589)
* Use synthetic CC0 assets instead of third-party licensed media in tests

* work file

* Fix `_format_py_obj` crashing on values it has no branch for

`_format_py_obj` assigns `output` inside a `str`/`int`/`float`/`list`/`tuple`/`dict`
chain with no trailing `else`, so any other type raises
`UnboundLocalError: local variable 'output' referenced before assignment`.

A `torch.Size` hits this: it is a `tuple` subclass, but the branch tests
`__class__.__name__`, which is `"Size"`. Any test whose captured assertion
argument is a shape then loses its report — the CI value capture dies inside
the reporting helper instead of printing the actual value, which is exactly
what the failing test needs.

Fall back to `repr`, which for `torch.Size` gives the copy-pastable
`torch.Size([1, 736, 736])`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh integration expectations recorded against the old third-party assets

Values taken from the A10 run-slow run on this branch
(https://github.com/huggingface/transformers/actions/runs/34103194667), read out
of each job's `captured_info.txt`. Single-GPU and multi-GPU agreed everywhere
they overlapped.

Refreshed: aya_vision, chameleon, chinese_clip, cohere2_vision, cosmos3_omni,
diffusion_gemma (the two minified `generate_with_image_batched*` tests),
fast_vlm, higgs_audio_v2, idefics2, instructblip.

For instructblip both assertions are updated: the run stopped at the token-id
comparison, so the decoded-text expectations come from running the new ids back
through each checkpoint's tokenizer.

Where every key of an `Expectations` dict was recorded against the old asset,
the dict collapses to a single `(None, None)`: only cuda 8.x could be
re-measured, and a stale `("xpu", 3)` / `("cuda", 7)` value is worse than
falling back to the one value we verified.

chmv2 keeps its TODO — the new asset changes the output resolution
(448 -> 736), and the depth slice cannot be re-recorded until a run gets past
the shape assertion.

Left untouched, per the run's own `git bisect` results: edgetam (timm
`FeatureListNet.forward()` signature), the four diffusion_gemma tests that fail
identically on the base commit, and cohere2_vision's OOM / unchanged-fixture
drift / local-path-checkpoint failures.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Fix two more `captured_info` corruptions: backslashes and integer-tensor dtype

Both silently produced a report that cannot be read back, which is the whole
point of the capture:

* `_quote_string` escapes quotes but not backslashes, so a value containing one
  emits a literal that means something else -- and a value *ending* in one
  swallows the closing quote, leaving an unterminated literal. mllama's decoded
  output ends in `\`, so its captured value was unusable.

* `_format_tensor` strips `tensor(` and `)` but a non-default dtype is repr'd as
  a trailing kwarg, e.g. `tensor([83, 362], dtype=torch.int16)`. Stripping only
  the wrapper leaves `dtype=torch.int16` stranded *inside* the literal. Every
  integer tensor hit this (the pp_ocrv5/v6 detection boxes).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh the remaining expectations from the full-coverage GPU run

Round 1 only exercised 16 models -- `get_pr_run_slow_jobs.py` caps its
suggestion at `MAX_NUM_JOBS_TO_SUGGEST = 16` and sorts alphabetically, so
everything from `internvl` on was never run. This is the second pass, over all
58 model dirs plus `pipelines` and `cli`
(https://github.com/huggingface/transformers/actions/runs/34150746428).

Of the 173 failures there, the run's own `git bisect` attributes 87 to this
branch; 41 fail identically on the base commit (edgetam's timm signature
mismatch, four diffusion_gemma tests, cohere2_vision's local-path checkpoints)
and 15 are flaky. 83 of the 87 had a captured value; all of those are refreshed
here.

Two notes on how the values were placed, since both went wrong on the first
attempt:

* Targets are resolved by enclosing test function *and* the assertion's line.
  A file usually holds several `EXPECTED_*` blocks and a test that checks
  several outputs rebinds the same name once per output, so "first match in the
  file" and "last assignment in the function" both write into the wrong test.
  This also fixes a bad round-1 edit that landed the multi-speaker value in
  higgs_audio_v2's `test_single_speaker_smart_voice`.

* Which captured side is the expectation is decided structurally, not from the
  argument name: `assertEqual(EXPECTED, computed)` puts the stale expectation in
  the slot named `first`. Three tests are written that way.

Where every key of an `Expectations` dict was recorded against the old asset the
dict collapses to a single `(None, None)`; only cuda 8.x could be re-measured,
and a stale xpu/rocm value is worse than the one value we verified.

Still marked TODO, awaiting the next run: cosmos3_omni's batched test (OOM),
diffusion_gemma's image-token count and qwen3_asr's timestamps (assertions the
capture does not instrument), qwen2_5_vl's second batch element and
pp_ocrv6_medium_det's boxes (never reached), and instructblip plus
prompt_depth_anything, whose assets were just regenerated:

* `lavis_confusing_pictures.jpg` was flat geometric shapes standing in for the
  LAVIS "man ironing on a taxi" photo, and instructblip-flan-t5 answered "What
  is unusual about this image?" by repeating one sentence until `max_length`.
* `promptda_arkit_depth.png` was saved as RGB, but a prompt-depth map is a
  single-channel metric image -- PromptDepthAnything failed outright with
  "expected input[1, 3, 27, 36] to have 1 channels".

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* qwen3_asr: use real Mandarin speech instead of a synthetic sine tone

`audio/mandarin_asr.wav` in the synthetic-assets repo is a 520 Hz sine tone, so
Qwen3-ASR transcribed it as nothing: the expected tokens for the second batch
element collapsed to `[11528, 2240, 151704, 151645, ...]` -- language tag, then
immediate EOS. The test stayed deterministic but stopped exercising
transcription at all.

Point both references at `mandarin_voxcpm_zh.wav`, mirrored into
`hf-internal-testing/dummy-audio-samples` from
`adarshxs/voxcpm2-native-generated-audio-user-ref` (`ref_zh.wav`): 4s of
apache-2.0 Mandarin TTS, transcript "你好世界,人工智能正在改变我们的生活方式。"
per the source's `metadata.json`. Same source repo and licence as the
already-GREEN `zs_short.wav`/`zs_medium.wav` there, and recorded in that repo's
`provenance.jsonl` with the same fields.

Both fixture tests need re-recording against the new clip, so the batched one is
marked again; `test_fixture_timestamps_batched` already was.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Repoint the qwen2_audio/qwen3_asr docs at the real Mandarin clip too

The previous commit only fixed the test and the CI prefetch list, but five doc
snippets still pointed at the sine tone. These are copy-paste examples, so a
reader running them would get an empty transcription and reasonably conclude the
model was broken.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Collapse two pixel-identical duplicate assets

The migration replaced third-party URLs one-for-one, so several assets are the
same picture under a name that records which test it came from. Two pairs are
*exactly* identical, so pointing the tests at a single copy cannot change a
tensor:

  images/gemma_golden_gate.png  == images/dreamstime_golden_gate_flowers.jpg
  images/invoice_docquery_b.png == images/invoice_docquery_a.png  (byte-identical)

Both duplicates are deleted from the assets repo and their entries in the
manifest now resolve to the survivor, so the old-URL mapping stays complete.
Their prefetch entries go too, since the survivors are already listed.

Four further pairs are the same content re-encoded rather than identical
(mistral3_view / mistral3_statue_of_liberty / mistral3_golden_gate against their
originals, and paddle_ocr_demo2 against paddle_general_ocr_001). Collapsing
those would take the count from 40 to 36, but they differ by up to 48/255 per
channel, so each one risks another expectation re-record and is better done as
one deliberate batch than mixed into this refresh.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Collapse the four re-encoded duplicate assets

These are the same picture as an existing asset, re-encoded rather than
byte-identical, so unlike the previous pair they do shift the input tensor
slightly:

  mistral3_view.jpg              -> llava_view.jpg                        (max 8/255,  3.1% of pixels)
  mistral3_statue_of_liberty.jpg -> statue_of_liberty.jpg                 (max 9/255,  2.5%)
  mistral3_golden_gate.jpg       -> dreamstime_golden_gate_flowers.jpg    (max 27/255, 9.8%)
  paddle_ocr_demo2.jpg           -> paddle_general_ocr_001.png            (max 48/255, 100%)

The six tests that consume them are marked for re-recording: two in mistral3,
and all four in `PaddleOCRVLIntegrationTest`, whose URL lives in `setUp`.

`paddle_ocr_demo2.jpg` also appeared in a paddleocr_vl docstring, so the change
went into `modular_paddleocr_vl.py` and `modeling_paddleocr_vl.py` was
regenerated from it.

With this the assets repo is 36 files, every one referenced and every reference
resolving. Two duplicate groups are deliberately kept: `azure_slide_1..4` are
0.998-1.000 correlated but differ in ~2.5% of pixels, and phi4_multimodal's
multi-image test wants four distinct slides -- those want regenerating with more
variety, not deduplicating.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh expectations from the round 3 GPU run

27 of the 33 round-3 failures that `git bisect` attributed to this branch,
across 19 files. Values come from each job's captured_info.txt, not from
re-deriving them.

Stale `Expectations` dicts collapse to a single `(None, None)` key: every
hardware key was recorded against an asset this branch replaces, and only
cuda 8.x can be re-measured on this CI, so a stale ("xpu", 3) is worse than
the one value we verified.

Three pixel_values expectations (qwen2_vl, qwen2_5_omni, qwen3_omni_moe) failed
on a bare `assert torch.allclose(...)`, which the capture helper does not
instrument. That assertion is CPU-only image-processor preprocessing, so those
values are computed off-CI instead; each test's own expected_input_ids
reproduces exactly, qwen2_5_omni's slice agrees with qwen2_vl's independently
computed one, and each recomputes within the test's own atol=3e-3.

Two pre-existing inconsistencies fixed on the way through:

- instructblip::test_inference_vicuna_7b had a refreshed token-id expectation
  next to a stale text expectation, so the two contradicted each other. The
  ids decode to the new text exactly.
- pp_formulanet's expectation held a literal backspace byte, from `\backslash`
  written in a non-raw string.

qwen2_5_vl::test_small_model_integration_test_batch_different_resolutions
needed element [1], not element [0] as its TODO claimed: the loop reaches i=1,
so [0] already passes.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Make the last two blocked expectations reportable, and drop a stray file

Three of the open TODOs were not waiting on an asset or a measurement -- they
were written as assertion forms the capture helper cannot instrument, so every
round reports "AssertionError: False is not true" and no value, and the next
round would have reported exactly the same thing:

- prompt_depth_anything's two tests used
  `assertTrue(torch.allclose(a, b, atol=1e-3))`. Now
  `torch.testing.assert_close(a, b, atol=1e-3, rtol=1e-5)` -- rtol spelled out
  because that is torch.allclose's default, so the tolerance is unchanged.
- diffusion_gemma's image-token check used `assertTrue(a == b)`, which hides
  both operands. Now `assertEqual`, which also reports them.

Round 3 incidentally disproves what these TODOs claimed: prompt_depth_anything
asserts `predicted_depth.shape == (1, 756, 1008)` *before* the value check, and
that passed, so the single-channel depth map was already in place. The stale
asset was never the problem there.

Also removes src/transformers/utils/hub.py.save, a 40K editor backup committed
by accident in 7a367e7.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh the second DQA expectation layer from round 4

The four DocumentQuestionAnswering tests each hold three or four sequential
expectation blocks against the same invoice. A test aborts at its first failing
assertion, so a round's `captured_info.txt` only ever reports the *first* stale
block per test -- round 3 refreshed layer 1, and round 4 (`34213965306`) reached
and reported layer 2. Bisect confirms all four as ours; the other 24 pipelines
rows in that run are pre-existing and untouched.

Round 4 also settles the question left open when these values were first
recorded. `INVOICE_URL` moved `invoice_docquery_b.png` -> `_a.png` in 1660b7f,
and `_b` is now deleted from the Hub, so the two could not be compared. But both
were ours: `make_document_assets()` in
`.ai/generated_assets/assemble_transformers_synthetic_assets.py` is one
deterministic path whose only per-file variable is `title`, and both entries pass
"INVOICE". Re-running it reproduces `_a` and `_b` with the same sha256, and that
output is byte-identical to the live `_a`. The layer-1 values measured against
`_b` were therefore valid, which is why every one of them passed this round.

Two blocks still hold the old values and are marked; both need measuring rather
than inferring, and `pipelines` is in the next round anyway:

- `test_large_model_pt` line 238 -- the batched form
- `test_large_model_pt_layoutlm` line 354 -- the `word_boxes` path, image=None

`test_large_model_pt_donut` needed nothing: it already asserted
`{"answer": "syn-2026-001"}` and passed.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Two more `_format_py_obj` bugs that corrupt the value it reports

Same family as the three already fixed in this helper: each one silently
destroys the value the capture report exists to provide.

- A 1-element tuple was rendered without its trailing comma, so `(5,)` came out
  as `(5)` -- which parses back as the int 5, not a tuple. Pasting that into an
  expectation gives `assertEqual(some.shape, 5)`, an assertion that can never
  hold. Found because round 4 reported `pp_ocrv6_medium_det`'s
  `assertEqual(results[0]["scores"].shape, (5,))` expectation as `(5)`.
- An empty list, tuple or dict raised `IndexError` from `element_types[0]` in
  `use_one_line_repr`, taking the whole report down. Plausible input: a
  detection test that finds nothing -- and detection counts in this branch do
  change (pp_ocrv6 went 5 -> 4).

Both round-trip through `ast.literal_eval` now, including nested cases such as
`((1,),)`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh 13 expectations from round 4

Round 4 (`34213965306`) bisect: 20 rows ours, 31 pre-existing, 2 ambiguous,
1 flaky. This lands the 13 ours-rows that produced a usable capture, across
12 files.

Every one of round 3's refreshed tests advanced to a later assertion in this
run and none failed where it had been refreshed, which confirms two things that
had been taken on trust:

- the three `expected_pixel_slice` values derived off-CI from the image
  processor (qwen2_vl 542 -> 550, qwen2_5_omni 653 -> 669, qwen3_omni_moe
  773 -> 789 all cleared the pixel assertion), and
- the invoice byte-identity argument behind the pipelines values.

`prompt_depth_anything` reported real numbers for the first time, which is what
converting its `assertTrue(torch.allclose(...))` was for. So did
`diffusion_gemma`'s image-token count, now 260; its TODO is removed.

Deliberately not refreshed, on the bisect verdict:

- `mllama::test_11b_model_integration_multi_image_generate` is pre-existing. It
  pins the *wrong* answer on purpose -- the comment above it records that the
  quantized model says "dock" where it should say "stop sign". Round 4 returns
  the correct stop-sign answer, so refreshing the string would delete a tracked
  bug and leave the comment contradicting the assertion. Whether the synthetic
  asset has stopped exercising that bug is a separate question.
- `mllama::test_11b_model_integration_batched_generate` is ambiguous, and its
  new value is padded-batch gibberish ("This image showst's a photo of a person
  named ...") -- the same objection as `chameleon::test_model_7b_batched`.
- `instructblip::test_inference_flant5_xl` is ambiguous.
- `diffusion_gemma`'s three `test_minified_*` rows and
  `test_diffusion_gemma_chat_template` are pre-existing; the model is red on
  `main`.
- `qwen2_vl::test_small_model_integration_test_batch` is flaky (CUDA OOM).
- `qwen3_asr::test_fixture_batch_matches` needs a human: both captured sides
  resolve to computed values, so which one is the expectation is not decidable
  structurally.

`pp_ocrv6_medium_det` gets its scores *shape* only (5 -> 4). The scores tensor
still holds five values and the labels shape still asserts `(5,)`, so its TODO
stays: like the DQA tests, it peels one assertion per round.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Clear 6 resolved TODOs, and refresh paddleocr_vl's processor expectations

Round 4 shows six `# TODO(synthetic-assets)` markers no longer describe
anything outstanding. Three guard tests that now simply pass -- cosmos3_omni's
batched integration test (it used to OOM), mistral3's
`test_mistral3_integration_batched_generate`, and paddleocr_vl's
`test_small_model_integration_test_batch`. Three were refreshed from round 4's
capture in the preceding commit: mistral3's multi-image test and both
prompt_depth_anything tests.

paddleocr_vl's remaining failure was `assert 211 == 1389`, a bare assert on
`len(inputs.input_ids[0])`. Everything it checks before the generation is
processor-only work, so it is reproducible off the CI, and two independent
checks say the local run matches this CI: the length comes out 1389, exactly
what the run reported, and the `expected_input_ids` first-ten already in the
file reproduce unchanged. So the token count and the pixel slice are refreshed,
and the three bare asserts become `assertEqual` / `torch.testing.assert_close`
so any later drift is reported with a value instead of a bare "False is not
true".

The same edits apply to `test_small_model_integration_test_flashatt2`, which
feeds the same processor the same messages and so asserts the same numbers; it
did not run this round for want of flash-attn hardware, and would otherwise
have failed identically. `test_small_model_integration_test_batch_flashatt2`
loses its marker for the same reason its non-flash twin did.

`EXPECTED_DECODED_TEXT` is a generation, not preprocessing, so it still needs a
GPU round; both markers now say that rather than blaming the asset.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Two more capture-helper bugs, and instrument assertSequenceEqual

`use_one_line_repr` compared `type(obj) is not type(obj[0])`, but on a dict
`obj[0]` is a *key* lookup rather than positional, so any dict expectation
holding a container value raised `KeyError: 0` and took down the whole
`captured_info.txt` report, not just that one value. Use the dict's single
value as the element to compare. Latent so far: the DQA expectations are lists
of flat dicts, and `word_boxes` is an input, never an expectation.

`assertSequenceEqual` was not patched, so it reported nothing at all. That is
the assertion `pp_doclayout_v2`/`v3` use for `expected_labels`, whose detection
counts change 14 -> 8 and 13 -> 8, so those values were unreachable by any
number of GPU rounds.

Both verified end to end by running pytest with
`patch_testing_methods_to_collect_info()` active and reading the report.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh 7 expectations from round 5

Round 5 (34244551302) brought 7 dirs back green, including all three
`expected_pixel_slice` values that were derived off-CI in round 3 -- those now
clear the generation assertions past them too.

Refreshed:

- pp_ocrv6_medium_det: scores 5 -> 4 values, and the `labels` shape (5,) -> (4,).
  `boxes (4,4,2)` and `scores (4,)` already passed, so three assertions in the
  test agree on 4 detections.
- pp_doclayout_v2 / v3: scores 14 -> 8 and 13 -> 8.
- omdet_turbo: only the third of three batched scores moved, matching the fact
  that exactly one of its three images was repointed.
- paddleocr_vl: the OCR text on all four integration tests. Its `_batch`
  sibling, refreshed in an earlier round, already held the same text.
- pipelines DQA x2: `us-001` -> `SYN-2026-001`, which is what the asset
  generator's deterministic `make_document_assets()` draws.
- qwen3_asr `expected_results_batched.json`: the Mandarin row. This fixture had
  been self-contradictory since round 2, which refreshed its `token_ids` to the
  sine-tone result (`language None`, empty) but left `transcriptions` at the
  original clip's sentence. Both halves now decode to each other, and the text
  matches the transcript recorded in the audio's provenance.jsonl.

The two paddleocr_vl `flashatt2` values are mirrored from the eager tests rather
than measured: `@require_flash_attn` keeps them out of run_models_gpu, so no
round can reach them. Same model, image, prompt, generation args and decode
path; the pairs held identical expectations before the asset swap. Noted in the
code as wanting a local flash-attn run.

`pp_doclayout_v2`/`v3` gain markers for their `expected_labels` next layer,
which had none and was unreportable until assertSequenceEqual was instrumented.
`qwen3_asr::test_fixture_timestamps_batched`'s marker now records the real
blocker: its stale value is an *input* to the forced aligner, so it must be
corrected before timestamps can be measured, and `assertAlmostEqual` is not
instrumented either.

Round 5's bisect matrix died in `actions/download-artifact` with a 403 before
bisecting anything, so these rows were filtered on per-test causal evidence
instead: the test reads an asset this branch repointed, the failure is an
expectation mismatch, and the captured value cross-checks against an
already-passing assertion or the asset's known content.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh 3 expectations from round 6

Round 6 (34262517871) validated every round-5 refresh. Four went green
(pp_ocrv6_medium_det, paddleocr_vl, both pipelines DQA rows, and the qwen3_asr
JSON fixture); the other three advanced past the assertion they were refreshed
at, which is the progress signal rather than "still failing":

- omdet_turbo: scores held, now `expected_slice_boxes`. Only the third of three
  rows changed, the same one-of-three-images signature as its scores.
- pp_doclayout_v2 / v3: scores held, now `expected_labels` -- reported for the
  first time, since assertSequenceEqual had never been instrumented. Both give
  [6, 22, 22, 22, 21, 22, 22, 22]: 8 entries matching the 8 detections, and
  identical between v2 and v3, as expected for one asset through one model
  family.

Bisect was healthy again this round (round 5's 403 was transient) and confirms
the substitute filter used there made no wrong calls: every row it admitted has
gone green or advanced, and every row it excluded is still pre-existing.

Markers retargeted onto `expected_slice_boxes` in v2/v3, the one remaining stale
block in each -- its four rows are the first four of the old detection set.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Never let the capture helper replace a test's own failure

Instrumenting `assertSequenceEqual` in the previous commit was unsafe: it is not
a leaf. `unittest`'s `assertListEqual` delegates to it as
`self.assertSequenceEqual(list1, list2, msg, seq_type=list)`, so a failing
`assertListEqual` enters the patched `assertSequenceEqual` with four positional
args while the parsed call-site expressions still describe the outer call's two.
`positional_args[arg_pos]` then raised `IndexError`, and since the patch wraps
the assertion, that replaced the test's real failure and took down the whole
`captured_info.txt` report. `omdet_turbo::test_inference_object_detection_head_batched`
produced no report at all because of it.

Guarding the `IndexError` alone would be worse than the crash: the indices stay
off by one, so `seq1` would be labelled with `seq2`'s expression and the report
would attribute values to the wrong names, silently.

So reconcile the counts and report nothing when the call-site expressions cannot
be matched to this call, and skip writing a record with no values. Direct
`assertSequenceEqual` calls still capture correctly; delegated ones are skipped,
and the outer `assertListEqual` -- already patched -- reports the same values
with the right expressions.

Note for reproducing: this needs `CI=true`. Without it the inner patched method
swallows the `AssertionError` and defers it to `tearDown`, so the outer patched
method never sees it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh 3 expectations from round 7

Round 7 (34268218309) advanced all three rows again, each past the assertion
refreshed in round 6:

- pp_doclayout_v2 / v3: `expected_slice_boxes`, the last stale block in each.
  Scores and labels from rounds 5-6 both held.
- omdet_turbo: `expected_text_labels`, third of three entries, 6 -> 7 labels.
  Its value came from the unittest diff in `test_outputs.txt` rather than a
  capture, because the report died on the IndexError fixed in the previous
  commit. The diff pins both sides and its context lines prove entries 1-2 are
  unchanged, and the count is independently corroborated: the
  `assertListEqual([len(result["scores"]) ...], [4, 4, 7])` above it passed, so
  image 3 really has 7 detections against a stale expectation listing 6.

Markers are down to 2, and neither is a refresh job: qwen3_asr's batched
timestamps need their aligner input corrected first, and instructblip is waiting
on the lavis_confusing_pictures.jpg re-record.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh pp_doclayout_v3's polygon points from round 8

Round 8 (34320893899) brought omdet_turbo and pp_doclayout_v2 green on both
devices -- every expectation in those two files is now refreshed.

pp_doclayout_v3 advanced once more, into `expected_slice_polygon_points`, a
block no earlier round had reached because it only exists in v3. The new value
needs no further round to trust: `expected_slice_boxes[1]`, refreshed last round
and now passing, is [115.5250, 223.8100, 479.4319, 261.9810], and the polygon is
exactly that box's four corners as integers, within the test's own atol=2. It is
also a shape change, (5,2) -> (4,2), so the old 5-point polygon was not merely
reordered.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh test_load_img_url for the synthetic invoice's size

`tests/utils/test_image_utils.py` imports `INVOICE_URL` from the document QA
pipeline tests, and this branch repointed that constant from the impira/docquery
invoice to the synthetic `invoice_docquery_a.png`. The synthetic asset is
900x1200, so `np.array(img).shape` is (1200, 900, 3) where the test still
asserted the original's (1061, 750, 3). Measured directly off the Hub; this is
pure `load_image`, so it needs no GPU run.

Nine slow-CI rounds could not have caught this: `run-slow` job names are
directories, so a round only ever covers tests/models/* plus pipelines and cli.
Tests under tests/utils surface in PR CI's tests_non_model shards instead.

A sweep for other instances is clean. Of the 44 cross-test imports in the repo,
this is the only one pulling from a file this PR changed; the rest import
test_modeling_common, test_tokenization_common or test_audio_utils, none of
which this branch touches. The four non-model test files referencing the assets
directly (cli/test_serve.py and three pipelines files) are already in the rounds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh 10 expectations the AMD round measured

Round 10 (34352147816) was the first full-coverage AMD round, and its bisect
blames this branch for 11 rows. They split two ways.

Five are `@require_flash_attn` tests no NVIDIA round can reach, so their values
were never refreshed for the synthetic images at all — MI300 is the only CI that
runs them. In each case ROCm returns exactly what the eager sibling already
expects, so a single value serves both devices rather than a new hardware key:

  qwen2_vl      batch_flashatt2, batch_wo_image_flashatt2
  qwen2_5_vl    batch_flashatt2, batch_wo_image_flashatt2 (element 0)
  qwen2_5_omni  batch_flashatt2 (collapses cuda 8.6 / rocm 9.4, both stale)

The other five are genuine cuda/ROCm divergence that the new images exposed:
collapsing those dicts to `(None, None)` left MI300 reading an A10-recorded
value that used to agree and no longer does. They get a `("rocm", (9, 4))` key.

  fast_vlm  test_small_model_integration_test  (plain str -> Expectations)
  internvl  batched_generate_multi_image, interleaved_images_videos
  mistral3  mistral3_integration_batched_generate_multi_image
  qwen3_vl  test_small_model_integration_test

AMD's reusable workflow lives in `hf-workflows` and does not run
`utils/get_test_reports.py`, so there is no `captured_info.txt` for any of this
— every value here came out of the traceback instead. Each was verified by
feeding the reconstruction back through `unittest.util._common_shorten_repr`
and requiring it to reproduce the report's abbreviation byte for byte,
character counts included. That check is what confirms the two qwen2_vl rows,
whose diffs the report truncated at 1408 and 881 characters.

The 16 remaining AMD rows fail on `main` too and are left alone.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Drop llava_next's comparison against the original image's dumps

`test_small_model_integration_test` verified the processor output against
`nielsr/test-image::llava_1_6_{input_ids,pixel_values}.pt`, dumps recorded from
the original third-party radar image. Synthetic pixels can never reproduce
them, so the test aborted there and its generation expectation — still the old
"radar chart / MMM-Vet" text — was never reached by any round.

Re-recording the dumps from this very processor would only compare it against
itself, so the check is dropped rather than repointed. The generation assertion
below it still exercises the whole pipeline; it is marked stale for the next
round to measure.

Round 3 read this row as pre-existing and round 10 reads it as ours because
`main` has since added `atol=0.1` to the pixel comparison, which is what made
it start passing there.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Align qwen3_asr's batched timestamps against the right transcript

Entry 1's `text` is an *input* to the forced aligner, not an expectation, and it
still held the original third-party clip's transcript — so the test aligned
甚至出现交易几乎停滞的情况。 (13 characters) against audio that says something
else entirely, which is where the 0.0 != 0.4 came from. Re-recording timestamps
against a wrong transcript would have pinned meaningless numbers, so the
transcript is corrected first: it now matches what the ASR test already pins in
`expected_results_batched.json`. The entry count moves 13 -> 19 with it.

`assertAlmostEqual` is uninstrumented, so no CI round can ever report these
values; `Qwen3-ForcedAligner-0.6B-hf` is small enough to measure off-CI, which
is the path taken here (CPU, bfloat16, same batched call the test makes).

Two checks stand behind the numbers:

- entry 0, untouched, reproduces the existing fixture **byte for byte** under
  the same local run — so the measurement agrees with the CI hardware wherever
  the two can be compared;
- the new alignment spans 0.00s to 3.92s, against a clip that is 4.00s long and
  whose speech energy runs 0.00s to 3.90s.

Closes the last row round 10 bisected to this branch on both CUDA and AMD.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Refresh instructblip's flan-t5-xl text, the last ambiguous row

This row was filed ambiguous in rounds 3, 4 and 10, and round 10's capture
settles it: on this branch the token-ids assertion already **passes** and only
the text one fails — the classic half-refreshed pair, one side re-recorded after
the `lavis_confusing_pictures.jpg` regeneration and the other left describing
the flat-geometric-shapes stand-in that preceded it.

The "DIFFERENT error message" bisect verdict is what that looks like from the
outside: `main` still fails on the ids, this branch fails one assertion later.
Read that status as a layer already peeled, not as an undecidable row.

Cross-checked the way this plan recommends for id/text pairs — decoding one into
the other. `AutoTokenizer.from_pretrained("Salesforce/instructblip-flan-t5-xl")`
over the 110 ids the test already expects reproduces the captured
`generated_text` exactly, so the two sides now agree by construction.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Address the review: drop dead helpers, unify glass_breaking, kill the collision

Three review threads, all in `fetch_hub_objects_for_ci.py`.

**`get_synthetic_asset_url` / `get_synthetic_asset_path` (ydshieh).** They have
no callers anywhere in the tree — the tests spell the URL out. Removed rather
than documented.

**"should we use one of these breaking glasses then in all tests?" (zucchini).**
Yes, and it turned out to cost nothing. There were two different
`glass_breaking.mp3`: the synthetic CC0 one this PR added, and
`dummy-audio-samples/glass_breaking.mp3`, whose provenance record reads
`verdict: REVIEW, license: unknown` ("no licence declared; an undeclared licence
is not a permission") — exactly the kind of file this PR exists to remove. Its
only consumer left was `pe_audio`, whose two tests are `@unittest.skip`ped and
assert nothing, so repointing them needs no re-recording and no round. The whole
repo now uses one glass_breaking.

**"doesn't that mean `000000039769` will be overshadowed?" (zucchini).** It
would, and the allowlist entry documented the trap instead of removing it. The
collision is between `coco_panoptic/000000039769.png` and
`val2017/000000039769.png`, both **pre-existing on `main`** and both unused:
nothing resolves either through `url_to_local_path`, and every consumer of that
basename opens the in-repo `tests/fixtures/tests_samples/COCO/000000039769.png`.
The widely used cats image is the `.jpg`, which never collided. So the panoptic
entry is dropped rather than renamed on the Hub.

With both causes gone, `KNOWN_BASENAME_COLLISIONS` goes too and
`_check_basename_collisions` now guards the full 111-URL list unconditionally —
which is the point of having it. It passes with no exemptions.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Round 11: llava_next measured, two more ROCm layers, and a reportable aligner test

Round 11 (`34449486455` CUDA / `34449486637` AMD, on `28fce388`, 10 dirs) took
CUDA from 11 ours-rows to 2 and AMD from 27 to 5 — of the AMD five, `fast_vlm`'s
batch row and `instructblip` fail on `main` too.

- `llava_next` — first measurement this value has ever had; the input checks
  used to abort the test before generation. It matches the wording three sibling
  tests in the same file already expect for this image.
- `internvl::interleaved_images_videos` and
  `mistral3::batched_generate_multi_image` — the next ROCm layer, exactly where
  the previous commit predicted: refreshing one `Expectations` block only
  reveals the next, since a test stops at its first failing assertion. Both
  values verified against the report's abbreviation.

Worth noting for the haiku: ROCm returns `calm lake` here but an entirely
different haiku in `batched_generate_multi_image`, so the two are not
transferable — the test's own comment about batching altering the output is
right, and mirroring between them would have been wrong.

`qwen3_asr::test_fixture_timestamps_batched` is not a hardware split: **CUDA and
AMD report the identical `0.24`**, and it is the off-CI measurement that
disagrees by two frames on one boundary. So the fixture wants one CI recording,
not a tolerance and not device keys.

Getting it needs the assertion to be reportable. Element-wise `assertAlmostEqual`
is uninstrumented, so a round yields exactly one of the 38 numbers and names
neither the sample nor the word; `zip` also truncated a short prediction
silently, which is what the separate length check was there to catch. Comparing
each sample as one list subsumes both and lets a single round capture everything.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Pin the aligner timestamps to the values CI measures

Round 12 confirmed llava_next, internvl and mistral3 green, and left exactly one
ours-row on each device: this fixture.

The previous commit made the assertion reportable, and that paid off immediately
— one round captured all 19 timestamps instead of the single number
`assertAlmostEqual` was willing to print. 13 of the 19 disagreed with the off-CI
measurement, every one of them by a single frame or two (0.04-0.08s); the words,
the ordering and the 0.00-3.92s span are identical, so it is boundary placement
drifting between CPU and GPU, not a different alignment.

Sample 0 needed no change: it passed on CI, so no capture was produced for it,
which is its own confirmation.

The fixture now holds GPU-measured values and so no longer reproduces on CPU.
That is the right trade for a `@slow` integration test — CUDA and ROCm report
identical numbers, so one recording covers both devices, exactly as it did on
`main` before this branch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Regenerate the azure slides, which were four copies of one slide

`azure_slide_1..4` came out of one code path whose only per-file variable was the
header title, so they were 0.994-0.997 correlated while phi4_multimodal's
multi-image test shows them to the model as four distinct slides and asks it to
"Summarize the deck of slides." The deck had one slide in it, four times.

Each is now a different layout of the same subject — a title card, a bulleted
service list, a three-stage diagram with arrows, and a bar chart with axes.
Pairwise correlation drops to 0.04-0.70, the remainder being the shared banner
and background. Still 1280x720, still drawn with PIL, still CC0.

Uploaded as `2414d981` in the dataset repo, with the reasoning recorded in
`metadata/asset_manifest.json` (`7fab93ea`) alongside the other regenerations.

Checked the rest of the set rather than only the pair already flagged: correlating
all 31 images pairwise, the azure slides were the *only* group above 0.90. The
four document assets that share the same generator body sit at 0.77-0.86 —
close, but the model reads different text off each, and their expectations are
settled, so they are left alone.

The generator's other instance of the same flaw is fixed too: it still emitted
`invoice_docquery_b.png` with `_a`'s title, which is how those two became
byte-identical before being collapsed. The earlier description-driven generator
shows the intent was a distinct receipt page; the assembler's single code path is
what lost it. Nothing references `_b`, and the manifest already maps both
impira/docquery revisions to `_a`, so the generator now matches what ships.

phi4_multimodal's expectation is marked stale for the next round to measure.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

* Round 14: the regenerated slides measured, and one more ROCm key

The full-coverage round (61 dirs, `34465008568` / `34465008470`, on `2c12499b`)
leaves **1 ours-row on CUDA and 3 on AMD**, and two of those are the same test.

- `phi4_multimodal::test_multi_image_vision_text_generation` — the value the
  regenerated slides were always going to need. CUDA and AMD return it
  identically, so it stays a plain string.

  It also confirms the regeneration did what it was for: the model now answers
  "It outlines a four-part..." — picked up from the title card's subtitle, which
  exists only on the new slide 1. It is reading four slides, not one slide four
  times.

- `qwen3_omni_moe::test_small_model_integration_test_multiturn` — a cuda/ROCm
  split (ROCm carries the answer on into ", and the dog is a Labrador
  Retriever."), so it becomes an `Expectations` with a `("rocm", (9, 4))` key.

**`pp_doclayout_v2::test_inference_equivalence_for_static_and_dynamic_anchors_0_float32`
is left alone even though bisect blames this branch.** It builds its inputs from
`prepare_config_and_inputs_for_common()` — random weights, random inputs, no
asset of any kind — and the branch does not touch it. Its max diff wanders
across runs (0.00125, 0.00134, 0.00186, 0.00203) against an `atol=1e-4`, so it
sits 12-20x over tolerance on MI300 whichever commit runs it; rounds 10 and 13
saw it fail on `main` too. Which side a given round blames is a coin flip.

A bisect verdict is evidence, not a conclusion: check the failing test actually
reads something this branch changed before refreshing anything.

The other 14 AMD rows fail on `main` too, and `mllama`'s CUDA row is the
documented padded-batch won't-fix.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-10 13:41:42 +00:00

14 KiB
Raw Permalink Blame History

Generation with LLMs

open-in-colab

LLM、またはLarge Language Models(大規模言語モデル)は、テキスト生成の鍵となる要素です。要するに、これらは大規模な事前訓練済みトランスフォーマーモデルで、与えられた入力テキストに基づいて次の単語(または、より正確にはトークン)を予測するように訓練されています。トークンを1つずつ予測するため、モデルを呼び出すだけでは新しい文を生成するために何かより精巧なことをする必要があります。自己回帰生成を行う必要があります。

自己回帰生成は、推論時の手続きで、いくつかの初期入力を与えた状態で、モデルを反復的に呼び出す手法です。🤗 Transformersでは、これは[~generation.GenerationMixin.generate]メソッドによって処理され、これは生成能力を持つすべてのモデルで利用可能です。

このチュートリアルでは、以下のことを示します:

  • LLMを使用してテキストを生成する方法
  • 一般的な落とし穴を回避する方法
  • LLMを最大限に活用するための次のステップ

始める前に、必要なライブラリがすべてインストールされていることを確認してください:

pip install transformers bitsandbytes>=0.39.0 -q

Generate text

因果言語モデリングのためにトレーニングされた言語モデルは、テキストトークンのシーケンスを入力として受け取り、次のトークンの確率分布を返します。

"Forward pass of an LLM"

LLM(Language Model)による自己回帰生成の重要な側面の1つは、この確率分布から次のトークンを選択する方法です。このステップでは、次のイテレーションのためのトークンが得られる限り、何でも可能です。これは、確率分布から最も可能性の高いトークンを選択するだけのシンプルな方法から、結果の分布からサンプリングする前に数々の変換を適用するほど複雑な方法まで、あらゆる方法が考えられます。

"Autoregressive generation iteratively selects the next token from a probability distribution to generate text"

上記のプロセスは、ある停止条件が満たされるまで反復的に繰り返されます。理想的には、停止条件はモデルによって指示され、モデルは終了シーケンス(EOS)トークンを出力するタイミングを学習すべきです。これがそうでない場合、生成はあらかじめ定義された最大長に達したときに停止します。

トークン選択ステップと停止条件を適切に設定することは、モデルがタスクで期待どおりに振る舞うために重要です。それが、各モデルに関連付けられた [~generation.GenerationConfig] ファイルがある理由であり、これには優れたデフォルトの生成パラメータ化が含まれ、モデルと一緒に読み込まれます。

コードについて話しましょう!

基本的なLLMの使用に興味がある場合、高レベルの Pipeline インターフェースが良い出発点です。ただし、LLMはしばしば量子化やトークン選択ステップの細かい制御などの高度な機能が必要であり、これは [~generation.GenerationMixin.generate] を介して最良に行われます。LLMとの自己回帰生成はリソースが多く必要であり、適切なスループットのためにGPUで実行する必要があります。

まず、モデルを読み込む必要があります。

>>> from transformers import AutoModelForCausalLM, BitsAndBytesConfig

>>> model = AutoModelForCausalLM.from_pretrained(
...     "openlm-research/open_llama_7b", device_map="auto", quantization_config=BitsAndBytesConfig(load_in_4bit=True)
... )

from_pretrained 呼び出しで2つのフラグがあることに注意してください:

  • device_map はモデルをあなたのGPUに移動させます
  • load_in_4bit は4ビットの動的量子化を適用してリソース要件を大幅に削減します

モデルを初期化する他の方法もありますが、これはLLMを始めるための良い基準です。

次に、トークナイザを使用してテキスト入力を前処理する必要があります。

>>> from transformers import AutoTokenizer

>>> tokenizer = AutoTokenizer.from_pretrained("openlm-research/open_llama_7b")
>>> model_inputs = tokenizer(["A list of colors: red, blue"], return_tensors="pt").to(model.device)

model_inputs 変数は、トークン化されたテキスト入力とアテンションマスクを保持しています。 [~generation.GenerationMixin.generate] は、アテンションマスクが渡されていない場合でも、最善の努力をしてそれを推測しようとしますが、できる限り渡すことをお勧めします。最適な結果を得るためです。

最後に、[~generation.GenerationMixin.generate] メソッドを呼び出して生成されたトークンを取得し、それを表示する前にテキストに変換する必要があります。

>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'A list of colors: red, blue, green, yellow, black, white, and brown'

これで完了です!わずかなコード行数で、LLM(Large Language Model)のパワーを活用できます。

Common pitfalls

生成戦略はたくさんあり、デフォルトの値があなたのユースケースに適していないことがあります。出力が期待通りでない場合、最も一般的な落とし穴とその回避方法のリストを作成しました。

>>> from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig

>>> tokenizer = AutoTokenizer.from_pretrained("openlm-research/open_llama_7b")
>>> tokenizer.pad_token = tokenizer.eos_token  # Llama has no pad token by default
>>> model = AutoModelForCausalLM.from_pretrained(
...     "openlm-research/open_llama_7b", device_map="auto", quantization_config=BitsAndBytesConfig(load_in_4bit=True)
... )

Generated output is too short/long

[~generation.GenerationConfig] ファイルで指定されていない場合、generate はデフォルトで最大で 20 トークンまで返します。我々は generate コールで max_new_tokens を手動で設定することを強くお勧めします。これにより、返される新しいトークンの最大数を制御できます。LLM(正確には、デコーダー専用モデル)も出力の一部として入力プロンプトを返すことに注意してください。

>>> model_inputs = tokenizer(["A sequence of numbers: 1, 2"], return_tensors="pt").to(model.device)

>>> # By default, the output will contain up to 20 tokens
>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'A sequence of numbers: 1, 2, 3, 4, 5'

>>> # Setting `max_new_tokens` allows you to control the maximum length
>>> generated_ids = model.generate(**model_inputs, max_new_tokens=50)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'A sequence of numbers: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,'

Incorrect generation mode

デフォルトでは、 [~generation.GenerationConfig] ファイルで指定されていない限り、generate は各イテレーションで最も可能性の高いトークンを選択します(貪欲デコーディング)。タスクに応じて、これは望ましくないことがあります。チャットボットやエッセイのような創造的なタスクでは、サンプリングが有益です。一方、音声の転写や翻訳のような入力に基づくタスクでは、貪欲デコーディングが有益です。do_sample=True でサンプリングを有効にできます。このトピックについての詳細は、このブログポストで学ぶことができます。

>>> # Set seed or reproducibility -- you don't need this unless you want full reproducibility
>>> from transformers import set_seed
>>> set_seed(0)

>>> model_inputs = tokenizer(["I am a cat."], return_tensors="pt").to(model.device)

>>> # LLM + greedy decoding = repetitive, boring output
>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'I am a cat. I am a cat. I am a cat. I am a cat'

>>> # With sampling, the output becomes more creative!
>>> generated_ids = model.generate(**model_inputs, do_sample=True)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'I am a cat.\nI just need to be. I am always.\nEvery time'

Wrong padding side

LLM(Large Language Models)はデコーダー専用のアーキテクチャであり、入力プロンプトを繰り返し処理することを意味します。入力が同じ長さでない場合、それらをパディングする必要があります。LLMはパッドトークンからの続きを学習していないため、入力は左パディングする必要があります。また、生成に対して注目マスクを渡し忘れないようにしてください!

>>> # The tokenizer initialized above has right-padding active by default: the 1st sequence,
>>> # which is shorter, has padding on the right side. Generation fails.
>>> model_inputs = tokenizer(
...     ["1, 2, 3", "A, B, C, D, E"], padding=True, return_tensors="pt"
... ).to(model.device)
>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids[0], skip_special_tokens=True)[0]
''

>>> # With left-padding, it works as expected!
>>> tokenizer = AutoTokenizer.from_pretrained("openlm-research/open_llama_7b", padding_side="left")
>>> tokenizer.pad_token = tokenizer.eos_token  # Llama has no pad token by default
>>> model_inputs = tokenizer(
...     ["1, 2, 3", "A, B, C, D, E"], padding=True, return_tensors="pt"
... ).to(model.device)
>>> generated_ids = model.generate(**model_inputs)
>>> tokenizer.batch_decode(generated_ids, skip_special_tokens=True)[0]
'1, 2, 3, 4, 5, 6,'

Further resources

オートリグレッシブ生成プロセスは比較的簡単ですが、LLMを最大限に活用することは多くの要素が絡むため、挑戦的な試みとなります。LLMの使用と理解をさらに深めるための次のステップについては以下のリソースをご覧ください。

Advanced generate usage

  1. ガイド:異なる生成方法を制御する方法、生成構成ファイルの設定方法、出力のストリーミング方法についてのガイド;
  2. [~generation.GenerationConfig]、[~generation.GenerationMixin.generate]、および生成関連クラスに関するAPIリファレンス。

LLM leaderboards

  1. Open LLM リーダーボード:オープンソースモデルの品質に焦点を当てたリーダーボード;
  2. Open LLM-Perf リーダーボード:LLMのスループットに焦点を当てたリーダーボード。

Latency and throughput

  1. ガイド:ダイナミッククオンタイズに関するガイド。これによりメモリ要件を劇的に削減する方法が示されています。
  1. text-generation-inference:LLM用の本番向けサーバー;
  2. optimum:特定のハードウェアデバイス向けに最適化された🤗 Transformersの拡張。