Image content (image_url) is dropped — multimodal chat requests are processed as text-only
#3
Closed
opened 2026-05-31 06:30:38 +00:00 by grenade
·
3 comments
No Branch/Tag Specified
main
feat/397-replicas
feat/385-acestep-memory
fix/376-models-unhealthy-node
feat/music-surprise-genres
feat/web-decisions-route
fix/cold-load-serialise
fix/music-hide-stale-on-compose
feat/audio-disk-guard
feat/music-studio-ux
fix/cortex-glibc-floor
feat/music-history-sidebar
feat/audio-flac-mp3
feat/393-heartmula-serving
feat/391-heartmula-port
feat/387-acestep-rollout
feat/383-acestep-engine
feat/390-heartmula-fixtures
feat/381-acestep-fixtures
feat/402-music-ui
feat/386-audio-rates
feat/382-audio-job-api
feat/356-systemone-playground
fix/bench-skip-decision-models
feat/341-laya-residency
feat/341-laya-rollout
feat/337-laya-perf
feat/338-neuron-systemone
feat/335-laya-prompt-builder
feat/336-laya-engine
feat/339-laya-lang-detect
feat/338-systemone-surface
feat/334-laya-reference-fixtures
feat/96-mtp-probe
fix/324-qsa-visible-set
feat/310-ple-random-advice
feat/neuron-phase-timing
fix/qwen4-ple-conv-launch-bound
feat/318-per-layer-activation-hop
fix/318-release-host-memory
feat/318-per-model-max-in-flight
fix/318-logits-last-position
fix/318-cached-experts-residency
feat/318-host-source-tensors
feat/318-host-resident-experts
fix/322-cache-telemetry
feat/322-isq-cache
test/323-parity-long-prompt
fix/323-layer-type-alias
fix/323-qsa-scores-in-f32
feat/310-mmap-ngram-table
fix/quant-fallback-match-llamacpp
fix/qwen4-exp-expert-row-width
fix/qwen4-exp-load-reporting-and-pool-trim
fix/320-isq-reachable-and-log-levels
feat/qwen4-exp-quantised-experts
feat/qwen4-exp-expert-bank
feat/qwen4-exp-mtp-head
docs/mtp-wiring-settled
feat/qwen4-exp-snapshot
feat/rustingface-always
feat/qwen4-exp-harness
feat/qwen4-exp-qsa-oracle
feat/qwen4-exp-model
feat/qwen4-exp-decoder
feat/qwen4-exp-linear-attn
feat/qwen4-exp-moe
feat/qwen4-exp-full-attn
feat/qwen4-exp-qsa-indexer
feat/qwen4-exp-config
feat/qwen4-exp-qsa-selection
feat/qwen4-exp-ple-consumption
fix/seed-cutlass-anonymously
feat/qwen4-exp-ple-hashing
fix/seed-cutlass-in-cuda-ci
feat/hf-endpoint-env
feat/qwen4-exp-hyper-connections
feat/qwen4-exp-norm-primitives
fix/alias-test-temp-catalogue-collision
chore/retire-vestigial-reasoning-ladder
fix/context-limit-honours-kv-budget
feat/server-advertised-reasoning-ladder
feat/web-chat-collapsible-working
fix/router-sync-restarts-only-on-settings-change
feat/ci-owns-the-router-config
fix/infra-setup-ships-ratelimit-to-gateway
fix/nginx-preflight-checks-every-grant
fix/restore-max-in-flight-8
fix/retire-qwen36-27b
fix/nginx-sync-must-not-block-cortex-deploy
feat/nginx-search-alias-and-sync
fix/wire-surface-in-neuron-spans
fix/295-ignore-tiny-prefill-samples
fix/300-anthropic-streaming-thinking
fix/300-return-the-models-reasoning
feat/operator-repetition-knobs
fix/292-prefix-cache-budget
feat/stamp-preserve-thinking-state
fix/296-one-step-one-assistant-turn
feat/preserve-thinking-ab-gate
fix/client-capability-misattributions
fix/291-max-in-flight-and-config-only-ci
feat/290-reasoning-rungs-from-the-model
fix/284-gdn-state-f32
feat/bench-measurement-regimes
feat/285-nonstreaming-through-engine
fix/288-bench-authenticates
feat/283-operator-sampling-override
fix/286-clippy-all-targets
fix/replay-only-the-latest-reasoning
fix/developer-role-maps-to-system
fix/223-reasoning-effort-off
fix/fetch-the-models-own-prompt-format
fix/278-advertise-the-real-limits
fix/277-responses-reasoning-roundtrip
fix/272-sampling-gaps
fix/clippy-1.98-result-large-err
feat/sampling-defaults
fix/streaming-completion-log
fix/prometheus-histogram-buckets
fix/responses-reasoning-events
fix/stream-reasoning-content
fix/cortex-stream-read-timeout
fix/sse-keepalive-during-prefill
fix/anonymous-yields-to-authenticated
fix/upstream-test-fixture-collisions
fix/docs-directory-index
feat/angels-internal-vhost
v0.1.16
v0.1.15
v0.1.14
v0.1.12
v0.1.11
v0.1.10
v0.1.9
v0.1.8
v0.1.7
v0.1.6
v0.1.5
v0.1.4
v0.1.3
v0.1.2
v0.1.1
v0.1.0
Labels
Clear labels
out-of-scope
p1-now
p2-next
p3-later
Outside helexa's constraining principles: near-frontier models on consumer hardware, served transparently and predictably
Active pain or unblocking work — pick up first
Engine levers — start once P1 baselines exist
Dependent on earlier work, or demand-driven
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: helexa/helexa#3
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
When sending an OpenAI-compatible
chat/completionsrequest that includes an image as acontentpart (type: "image_url"with adata:image/jpeg;base64,...URL), the image is not ingested. The request is processed as if it were text-only: the model never receives the pixels, and the prompt token count reflects only the text. Downstream vision consumers therefore get descriptions of "nothing" or refusals like "I cannot see an image."Endpoint / model
http://hanzalova.internal:31313/v1/chat/completionsQwen/Qwen3.6-27B(reported by/v1/modelsasloadedon nodebeast)Reproduction
POST a standard multimodal request with one small JPEG (here a 320×240 frame, ~6.9 KB → ~9 KB base64):
Observed
Response (abridged):
Two tells that the image was discarded:
prompt_tokens: 62. A genuine vision ingest of even a small frame adds hundreds-to-thousands of tokens (image patches). 62 tokens accounts for the text messages alone — theimage_urlpart contributed nothing.The same behaviour shows up through a real consumer: a request per scene returns no usable description because the model is reasoning about an absent image.
Expected
The
image_urlcontent part should be forwarded to (and ingested by) the backend so that:usage.prompt_tokensreflects the image tokens, andIf
Qwen/Qwen3.6-27Bas served is genuinely not multimodal, the router should ideally surface that (e.g. reject image content for non-vision models, or advertise vision capability in/v1/models) rather than silently dropping the image and returning a confident text-only answer.Where it might live
Unclear whether
cortex/helexa strips the structuredcontentarray when forwarding to the backend, or whether the backend engine for this model ignores image parts. Theprompt_tokens: 62figure suggests the image never reached a tokenizer that understands it. Determining the layer is part of triage.Impact
Any vision consumer of the unified endpoint (e.g. a video scene-describer sending sampled frames) cannot function — it receives text-only hallucinations instead of image-grounded output, with no error to signal the dropped modality.
Aside (not a cortex bug, noted for context)
The served model is a reasoning model: it emits a
<think>…</think>block and, atmax_tokens: 400, exhausted the budget thinking before producing any answer (finish_reason: length). Consumers will want to disable thinking (/no_think/chat_template_kwargs: {enable_thinking: false}) and raisemax_tokens— but that is orthogonal to the image-ingest problem above.Versions observed against
Pinning down the deployed versions when this was first reported, so future debugging can establish whether intervening changes affect the repro:
0.1.16-0.1.20260527185748.git249b2e5.fc43onhanzalova0.1.16-0.1.20260529094300.gitdf0abfe.fc43onbeast(commitdf0abfe)0.1.16-0.1.20260529094300.gitdf0abfe.fc43onbenjy0.1.16-0.1.20260527185748.git249b2e5.fc43onquadbrat(note: older build, since upgraded togitdf0abfe)For helexa-acp callers: this bug predates the InferenceEvent refactor on
main(commit302ccfb, pushed 2026-05-29) and is unrelated to it — the refactor only touched the streaming output path, not the request-parsing / image-ingest path. The next deploy off a build that includes302ccfb(or later) should reproduce identically until we actually wire image ingest through to the candle harness.Relevant code paths to inspect during triage:
crates/cortex-gateway/src/handlers.rs::chat_completions— does the gateway preservecontentarrays verbatim when proxying, or flatten them?crates/neuron/src/harness/candle.rs::format_qwen3_prompt— currently doesMessageContent::Parts(parts) => parts.iter().filter(text-only).join(...), which would silently dropimage_urlparts.Verified fixed from the consumer side after the neuron-harness vision work. Same endpoint/model (
http://hanzalova.internal:31313/v1,Qwen/Qwen3.6-27B):prompt_tokens: 225(was 62) — theimage_urlpart is ingested.testsrcframe).A full downstream run (a video scene-describer sending sampled frames) now produces correct per-scene, image-grounded descriptions end-to-end. 🎉
Re the aside (reasoning model emitting
<think>and exhaustingmax_tokens): handled consumer-side by sendingchat_template_kwargs: {enable_thinking: false}and raisingmax_tokens— the harness honoursenable_thinking: falsecorrectly.Thanks for the quick turnaround. Closing is at your discretion.
Closing as completed. Stage B (
24968e9) shipped end-to-end text+image chat; the reporter verified the fix from the consumer side on 2026-06-04 (prompt_tokens 62 → 225, accurate frame descriptions from Qwen3.6-27B through the gateway). The streaming path landed via Stage C (766c20b); follow-on vision work is tracked in #18 (single-GPU prefill chunking) and #15 (numerical validation).