DeepSeek request images on the published token grid
DeepSeek 请求图片按官方 token 网格投影
The harness projected every DeepSeek request image under a 640,000 total-pixel budget, a value chosen for the retired V4 vision model and kept unchanged when the [token estimator moved to the `v41` calculator](2026-09-10-deepseek-image-token-calculator-v41.md). The current Flash model retains far more: it pads each edge to whole 14px patches, groups 3×3 patches into one token cell, and keeps the largest aspect-preser
English
Problem
The harness projected every DeepSeek request image under a 640,000 total-pixel budget, a value chosen for the retired V4 vision model and kept unchanged when the token estimator moved to the v41 calculator. The current Flash model retains far more: it pads each edge to whole 14px patches, groups 3×3 patches into one token cell, and keeps the largest aspect-preserving grid whose token count rows × (columns + 1) + 2 fits 1024. A square image keeps 1302×1302 pixels, a 16:9 image keeps a 1708×966 grid, and extreme aspect ratios keep up to about 1.8 million pixels. The 640,000-pixel projection therefore sent a 2000×2000 screenshot as 800×800, roughly 38% of the pixels the model would have used, and the estimator priced that reduced version at 422 tokens instead of the 994 the model charges for the full grid. The Vision guide's "about 1300×1300 total pixels" describes only the square case; the exact rule is the token grid.
Two smaller gaps sat beside it. The request version had no per-side cap: the provider rejects any image over 4096 pixels per side once a request carries 15 or more images, while normalization admits an 8192-pixel long edge, so a many-image session could fail on one thin image. The 1 MiB encoded-byte target was sized for 640,000-pixel outputs and would push a 1302×1302 photograph down the JPEG quality ladder.
Decision
The route chooses each request image's dimensions; the attachment provider only resizes and encodes to them. ImageRequestPolicy in dsh-attachment becomes ImageRequestTarget: a width, a height, and the byte target for one attachment. readImageRequest resizes by the source long edge alone without enlargement, so the encoder derives the short edge as the route predicted, and keys its cache by the attachment id, target dimensions, byte target, encoder settings, and the new request-image-v6 transform version, so no earlier cache entry or upload mapping is reused. dsh-attachment keeps two provider-neutral geometry exports: requestImageDimensions for a total-pixel budget and longEdgeDimensions for an exact long edge with a rounded short edge.
llm-deepseek owns the provider rule. image-tokens.ts keeps the verbatim v41 solver and adds deepSeekRequestImageDimensions: the source itself when its patch-padded grid fits the cap, otherwise the source aspect ratio at the solved grid's long edge, so a 3840×2160 source is sent as 1708×961 and the provider pads it to its 1708×966 grid. resolveRequestImageTarget applies that solver when imagePixelBudget is omitted, requestImageDimensions for a positive integer or the 512×512 low preset, then a 4096-pixel per-side cap on every request image so the image count never changes a target, and the route's 2 MiB byte target. Pricing prices deepSeekImageTokens of the same target, so the estimator and the sent image come from one solver. The pi-ai route derives its targets from its unchanged 2048×2048 pixel budget. Small images are never enlarged because the provider scales up below 544×544 pixels itself.
Alternatives considered
Raise the pixel budget to 1302×1302. A total-pixel budget is right only for squares: a 16:9 source would be sent at 1.69 million pixels when the grid keeps 1.65 million, and a 4:1 source when it keeps 1.59 million, while extreme ratios would lose detail the grid keeps. One rule that reproduces the provider removes the guesswork.
A token-grid projection kind on the attachment policy, with the solver in dsh-attachment. This was built first: the policy became a closed union of pixel-budget and token-grid, the solver moved into request-projection.ts, and deepSeekImageTokens imported it back. It put one provider's layout formula and patch constants into the provider-neutral package under a generic-looking name, needed an unscaled flag so the store could tell "send the source" from "send the solved size", and would grow a new union member for every provider rule. Handing the store a finished target keeps the provider rule beside the provider's pricing and leaves the store with no projection vocabulary at all.
Send the solver's exact grid dimensions with a fill resize. The solved grid edges are whole patches and differ from the source aspect ratio by under one patch. Filling that box would distort the image slightly even though the provider does the same on its side; preserving the source aspect ratio can change how many token cells the rounded short edge covers. A 1224×1429 source is sent as 1187×1386: the published calculator gives 959 tokens for the source and 992 for the sent dimensions. Request generation and pricing share the target dimensions, and pricing applies the published calculator to that target.
Keep the 1 MiB target. The target is not a cap: an output over it is still sent at the smallest ladder quality. At 1302×1302 a JPEG photograph at quality 85 lands between 400 KB and 1.2 MB, so 2 MiB keeps most images at the top quality and lets more PNG screenshots pass through losslessly, while the inline base64 fallback still holds about seven such images under its 20 MiB bound.
Consequences
A square source now reaches the model at up to 1302×1302 pixels and 994 tokens instead of 800×800 and 422, so image-heavy sessions reach compaction pressure sooner and the estimator applies the published token rules to the sent target dimensions. Every existing request-image cache entry and DeepSeek Files API mapping is regenerated on the next request. Thin images keep their full grid until the per-side cap applies: an 8192×78 source costs 396 tokens under the grid but is sent as 4096×39. llm-replay does not project images, so keyless snapshots cannot record the sent dimensions; the llm-deepseek adapter tests pin the resolved targets and the projected handle text against a mock server, and the local store tests resize real images to targets.
中文
问题
harness 此前把每张 DeepSeek 请求图片投影到 640,000 总像素预算内。这个值是为已下线的 V4 视觉模型选的,token 预估器改用 v41 计算器时没有改动它。当前 Flash 模型保留的远多于此:它把每条边补齐到整数个 14 px patch,把 3×3 个 patch 归为一个 token 格,再保留 token 数 rows × (columns + 1) + 2 不超过 1024 的最大等比网格。正方形图片保留 1302×1302 像素,16:9 图片保留 1708×966 的网格,极端宽高比最多保留约 180 万像素。因此 640,000 像素投影把一张 2000×2000 的截图缩成 800×800 发出,只有模型本可使用像素的约 38%,预估器为这个缩小版计 422 token,而模型对完整网格收 994 token。图像理解指南里的「约 1300×1300 总像素」只描述正方形的情况,确切规则是 token 网格。
旁边还有两个较小的缺口。请求版本没有单边上限:请求包含 15 张及以上图片时,提供方拒绝任何单边超过 4096 像素的图片,而规范化允许 8192 像素长边,多图会话可能因一张细长图失败。1 MiB 编码字节目标是按 640,000 像素输出定的,会把 1302×1302 的照片压到 JPEG 质量阶梯的低档。
决策
请求图片的尺寸由路由决定,附件提供方只负责缩放和编码。dsh-attachment 里的 ImageRequestPolicy 改为 ImageRequestTarget,即一张附件的目标宽、高和字节目标。readImageRequest 只按源图长边缩放且不放大,编码器按路由预测的方式推出短边;缓存按附件 id、目标尺寸、字节目标、编码参数和新的 request-image-v6 变换版本取键,因此之前的缓存条目和上传映射都不会被复用。dsh-attachment 保留两个提供方无关的几何导出:按总像素预算的 requestImageDimensions,以及长边精确、短边四舍五入的 longEdgeDimensions。
提供方规则归 llm-deepseek。image-tokens.ts 保留逐字移植的 v41 求解器,并新增 deepSeekRequestImageDimensions:补齐 patch 后的网格在上限内就发源图本身,否则按源图宽高比取求解网格的长边,于是 3840×2160 的源图以 1708×961 发送,提供方再把它补齐到 1708×966 的网格。resolveRequestImageTarget 在省略 imagePixelBudget 时用这个求解器,正整数或 512×512 的 low 预设用 requestImageDimensions,然后对每张请求图片加 4096 像素单边上限,使图片数量不会改变目标,最后带上路由的 2 MiB 字节目标。计价对同一个目标算 deepSeekImageTokens,预估器和发出的图片来自同一个求解器。pi-ai 路由从它不变的 2048×2048 像素预算推导目标。小图不放大,因为提供方自己会放大 544×544 像素以下的图片。
备选方案
把像素预算提高到 1302×1302。 总像素预算只对正方形正确:16:9 的源图会按 169 万像素发送而网格只保留 165 万,4:1 的源图网格只保留 159 万,极端比例又会丢掉网格本会保留的细节。一条复现提供方的规则消除了猜测。
在附件策略上加 token-grid 投影种类,求解器放进 dsh-attachment。 最初就是这样做的:策略变成 pixel-budget 和 token-grid 的封闭联合,求解器搬进 request-projection.ts,deepSeekImageTokens 再从那里引回来。这把一家提供方的布局公式和 patch 常量放进了提供方无关的包,还起了个看似通用的名字;存储层需要一个 unscaled 标志来区分「发源图」和「发求解尺寸」;以后每多一家提供方规则,联合就要多长一个分支。把算好的目标交给存储层,提供方规则和它的计价放在一起,存储层不需要任何投影词汇。
用填充缩放发送求解器的精确网格尺寸。 求解出的网格边长是整数个 patch,与源图宽高比相差不到一个 patch。填充到这个框会轻微变形,尽管提供方那侧也会这样做;保持源图宽高比可能改变取整后短边覆盖的 token 格数。1224×1429 的源图以 1187×1386 发送,官方计算器对源图计 959 token,对发送尺寸计 992 token。请求生成和定价共享目标尺寸,定价按目标尺寸应用官方计算规则。
保留 1 MiB 目标。 目标不是上限:超过它的输出仍会以阶梯最小质量发送。1302×1302 的 JPEG 照片在质量 85 时约 400 KB 到 1.2 MB,2 MiB 让多数图片停在最高质量,也让更多 PNG 截图无损直发,而内联 base64 回退在 20 MiB 上界内仍能容纳约七张这样的图片。
后果
正方形源图现在最多以 1302×1302 像素、994 token 到达模型,而不是 800×800 和 422,因此图片密集的会话更早触及 compaction 压力,预估器按发送目标尺寸应用官方 token 计算规则。所有已有的请求图片缓存条目和 DeepSeek Files API 映射在下次请求时重新生成。细长图在单边上限生效前保留完整网格:8192×78 的源图在网格下计 396 token,但以 4096×39 发送。llm-replay 不投影图片,keyless 快照记录不到发送尺寸;llm-deepseek 适配器测试对着 mock 服务器固定了解析出的目标和投影后的句柄文本,本地存储测试把真实图片缩放到目标尺寸。