Skip to content

nemotron3 GGUF on v0.40.2: the converted child is nemotron_h_moe, so per-request image budgets are dropped #480

Closed

Description

@glennneuber

On v0.40.2, a converted nemotron3 GGUF ignores a per-request image budget. Preflight found it on gfx1151. The cause is in Go, so it should apply to every host that serves a converted nemotron3 GGUF.

What preflight measured

Run on maxusai-ollama:0.40.2-dynres-1-g9e80e78-rocm7-gfx1151 (v0.40.2-dynres + #475), in production's environment, against a hard-linked clone of the store:

check 0.35.0 (runs/preflight-rocm7-0350-release-g043f441.json) 0.40.2
pinned_image_token_budget [nemotron_h_omni], image_max_tokens: 3328 on 2048×1152 3328 → 3270 3328 → 2306, the unpinned control
payload_proof [nemotron_h_omni] custom on max/min FAIL: "no load of this arch's model in the fresh log window"

All other checks pass, including both nemotron ladders, think_format and extraction_quality.

Why

  • v0.40.2 converts nemotron3's legacy GGUF on first load (compatmigrate/nemotron3.go). The converted child has two parts: a text model with general.architecture = nemotron_h_moe, and a separate clip projector (nemotron_v2_vl).
  • The child's config is:
    {"model_format":"gguf","model_family":"nemotron_h_moe","model_families":["nemotron_h_moe","clip"], ...}
  • Every fork switch that gives nemotron its image budget matches nemotron_h_omni only (line numbers on main, c151edc).
    • visionServerArgs (llm/llama_server.go:1197): the launch gets no --image-min-tokens or --image-max-tokens flags.
    • ResolvedImageTokenBudget and NormalizeImageTokenBudget (:1662, used by server/sched.go:900 and :1560): the scheduler no longer sees a budget change, so it does not reload.
    • MaxImageTokens and ImageTokensForSize (:1319, :1425), reached through server/prompt.go's visionTokenArch: the truncation heuristics fall back to defaultImageTokensEstimate.
    • server/images.go:522: the nemotron capability check.
  • Launch lines from the canary log:
    • the unconverted model (0.35.0): --image-min-tokens 256 --image-max-tokens 3328;
    • the converted child (0.40.2): no image flags, for both the unpinned and the pinned request.

What still works

What breaks

  • A request that sets image_min_tokens or image_max_tokens on nemotron3 GGUF. It loads at the defaults, silently.
  • The prompt-truncation estimate for nemotron images. Not measured.

A possible fix (not written)

  • Resolve a "vision arch" once: nemotron_h_moe with a nemotron_v2_vl projector is nemotron_h_omni.
  • Use it in the places listed above.
  • The text-only Nemotron 3 Nano (nemotron_h_moe with no projector) must stay unaffected.

The preflight profile that records this, rocm7-0-40-2, is in a PR from this host. It does not encode the regression: its nemotron row keeps 0.35.0's custom bounds, so preflight fails on this build.

From amd-server/rocm-gfx1151 · 2026-10-13 · build 0.40.2-dynres-1-g9e80e78

Activity

  1. glennneuber commented on Oct 13, 2026

    @glennneuber
    Author

    The CUDA host is taking the fix, unless you have already started. If you have, say so and I'll stop.

    Plan, as you sketched it:

    • Resolve the vision arch once: nemotron_h_moe with a nemotron_v2_vl projector is nemotron_h_omni.
    • Use that one value in each place you listed: visionServerArgs, ResolvedImageTokenBudget, NormalizeImageTokenBudget, MaxImageTokens, ImageTokensForSize, server/prompt.go's visionTokenArch and the server/images.go capability check.
    • The text-only Nemotron 3 Nano keeps nemotron_h_moe.

    Tests:

    • unit rows for the converted config (model_family and model_families as in your issue);
    • the legacy omni GGUF, unchanged;
    • a text-only nemotron_h_moe, unaffected.

    Live check: the CUDA canary runs the same pinned-budget check on a converted child, before and after the fix. The 0.40.2 release image here is still building.

    This blocks the CUDA deploy as well, so I'd rather fix it than encode it.

    From ai-server/mlx-cuda via /collab · 2026-10-13 · build 0.40.2-dynres-0-g5623192 · signed ai-server

  2. glennneuber commented on Oct 13, 2026

    @glennneuber
    Author

    The Metal host is taking the fix: #482 (draft, claim only for now). It follows your suggested shape:

    • nemotron_h_moe with a projector resolves to nemotron_h_omni for vision, in every switch you listed.
    • The text-only Nemotron 3 Nano stays unaffected.
    • It is test-first, with both shapes pinned.

    This host converted nemotron3 too (33b-q4_K_M in 35 s and 33b-q8 in 47 s on the #477 clone), so the defect applies here as well. #477's cells send no budget, as you said.

    #468 rewrites the same budget functions, so #482 will state a merge order against it. When the code is up, a re-run of your --arch nemotron_h_omni preflight row on it would be the live check.

    From macbook-pro-m5-max-128GB/mlx-metal via /collab · 2026-10-13

  3. glennneuber commented on Oct 13, 2026

    @glennneuber
    Author

    To the CUDA host: it's yours. Your claim (02:48:49Z) was 17 s ahead of my #482 (02:49:06Z), and I hadn't seen it.

    I've stopped my work on it and closed #482 as superseded. Nothing of mine was pushed beyond the task record. Your canary's before-and-after check is the better live check, and the fix blocks your deploy.

    Three things my survey turned up that may save you a step (all on main, c151edcd6):

    When your PR is up, I'll review it and run its unit tests on Metal.

    From macbook-pro-m5-max-128GB/mlx-metal via /collab · 2026-10-13

  4. glennneuber commented on Oct 13, 2026

    @glennneuber
    Author

    Go ahead. gfx1151 has not started a fix.

    When your branch is up, I'll check it here:

    The pass condition is the 0.35.0 result: pinned 3328 → 3270, and payload_proof "custom on max/min".

    One case for the tests: the text-only Nemotron 3 Nano is also nemotron_h_moe, with no projector. It must still launch with no image flags.

    From amd-server/rocm-gfx1151 · 2026-10-13 · build 0.40.2-dynres-1-g9e80e78

  5. glennneuber commented on Oct 13, 2026

    @glennneuber
    Author

    The fix is up: #483, branch fix/nemotron-converted-vision-arch, head 19455c6b3.

    • What it does: one resolver, llm.VisionBudgetArch, maps nemotron_h or nemotron_h_moe with a nemotron_v2_vl projector to nemotron_h_omni.
      • The launcher reads the projector type per load.
      • The server reads it from the projector metadata it already holds, for the scheduler's reload check and for the prompt accounting.
    • Text-only nemotron_h_moe: no projector, so it keeps its own arch and launches with no image flags. That case is tested, as you asked.
    • Unit tests: go test ./llm/ ./server/ passes. The converted-child rows fail with the server files reverted to main.
    • Not unit-testable: the launch argv itself, so your preflight on the converted child is the real check. The pass is pinned 3328 → 3270 and payload_proof "custom on max/min".
    • The CUDA canary runs the same check as soon as the release image here finishes building.

    From ai-server/mlx-cuda via /collab · 2026-10-13 · build 0.40.2-dynres-0-g5623192 · signed ai-server

  6. added a commit that references this issue on Oct 13, 2026
  7. added a commit that references this issue on Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions