Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions benchmarks/bench.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@ def upstream_time(bundle, image, prompt, mode, max_new=256):
tokenizer=tok, max_new_tokens=max_new, generation_mode=mode, use_cache=True)
dt = time.time() - t0
ids = tok(s, add_special_tokens=False)["input_ids"]
grid = inputs["image_grid_hws"][0].tolist()
boxes = parse_boxes_from_ids(ids, grid[1]*14, grid[0]*14)
from PIL import Image
boxes = parse_boxes_from_ids(ids, *Image.open(image).size) # ORIGINAL dims (reference convention)
return dt, boxes


Expand Down
4 changes: 2 additions & 2 deletions benchmarks/demo/dgx_measure.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ def gen():
if device == "cuda": torch.cuda.synchronize()
dt = time.time() - t0
ids = tok(s, add_special_tokens=False)["input_ids"]
g = inputs["image_grid_hws"][0].tolist()
boxes = _dedup(parse_boxes_from_ids(ids, g[1]*14, g[0]*14))
from PIL import Image
boxes = _dedup(parse_boxes_from_ids(ids, *Image.open(IMG).size)) # ORIGINAL dims
del model; torch.cuda.empty_cache() if device == "cuda" else None
return round(dt, 2), len(ids), boxes

Expand Down
3 changes: 2 additions & 1 deletion benchmarks/demo/dgx_official.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,8 @@ def measure_upstream(model, tok, proc, images):
res = {}
for img, cats in images:
inputs = up_inputs(proc, img, cats)
g = inputs["image_grid_hws"][0].tolist(); tw, th = g[1]*14, g[0]*14
from PIL import Image as _PILImage
tw, th = _PILImage.open(img).size # ORIGINAL dims (reference convention)
# --- greedy (deterministic; parity + fair-work timing, max_new=256) ---
s = gen_call(model, tok, inputs, max_new_tokens=256, do_sample=False); torch.cuda.synchronize()
tg = []
Expand Down
12 changes: 6 additions & 6 deletions scripts/diff_upstream.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
* Boxes are parsed from upstream's generated token stream with the SAME
token-id -> coord -> pixel logic as ``dump_slow_reference.py`` (which our
C++ ``boxes.cpp`` is gated to reproduce). Both sides denormalize against the
preprocessed grid size (gw*14 x gh*14), so the comparison is apples-to-apples.
ORIGINAL image size — the reference convention (the model card demo projects
<0>..<1000> onto image.size) — so the comparison is apples-to-apples.
* Both sides use ``max_new_tokens=256`` (the C++ engine/CLI default) so the
hybrid degenerate tail is the same length on both.

Expand Down Expand Up @@ -99,7 +100,7 @@ def build_inputs_for(model_bundle, image_path, prompt, device="cpu"):


def parse_boxes_from_ids(ids, img_w, img_h):
"""Token-id -> [(label, [x1,y1,x2,y2]) ...] in grid-pixel space.
"""Token-id -> [(label, [x1,y1,x2,y2]) ...] in original-image pixel space.
Identical logic to dump_slow_reference.py / src/boxes.cpp (x1,y1,x2,y2)."""
boxes, i, n, cur_label = [], 0, len(ids), ""
while i < n:
Expand Down Expand Up @@ -141,8 +142,7 @@ def upstream_boxes(model_bundle, image_path, prompt, mode, max_new=256):
attention_mask=inputs["attention_mask"], image_grid_hws=inputs["image_grid_hws"],
tokenizer=tok, max_new_tokens=max_new, generation_mode=mode, use_cache=True)
ids = tok(s, add_special_tokens=False)["input_ids"]
grid = inputs["image_grid_hws"][0].tolist()
img_w, img_h = grid[1] * 14, grid[0] * 14 # grid = [h_patches, w_patches]
img_w, img_h = Image.open(image_path).size # ORIGINAL dims (reference convention)
return parse_boxes_from_ids(ids, img_w, img_h), (img_w, img_h)


Expand Down Expand Up @@ -234,7 +234,7 @@ def main():
img, prompt, mode = c["image"], c["prompt"], c["mode"]
name = Path(img).name
try:
up, (gw, gh) = upstream_boxes(bundle, img, prompt, mode, args.max_new)
up, (iw, ih) = upstream_boxes(bundle, img, prompt, mode, args.max_new)
cp = cpp_boxes(args.cli, args.gguf, img, prompt, mode)
except Exception as e:
print(f"[{idx}] {name:24s} {mode:6s} ERROR: {e}")
Expand All @@ -248,7 +248,7 @@ def main():
tag = "OK " if ok else "DIFF"
print(f"[{idx}] {name:24s} {mode:6s} {tag} up={len(up)} cpp={len(cp)} "
f"matched={len(matched)} unmatched(up/cpp)={un_up}/{un_cp} "
f"minIoU={min_iou:.3f} maxCoordDiff={max_cd:.1f}px grid={gw}x{gh}")
f"minIoU={min_iou:.3f} maxCoordDiff={max_cd:.1f}px img={iw}x{ih}")
if not ok:
up_s = [(l, [round(x, 1) for x in b]) for l, b in up]
cp_s = [(l, [round(x, 1) for x in b]) for l, b in cp]
Expand Down
6 changes: 4 additions & 2 deletions scripts/dump_fast_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ def main():
ids = tok(s, add_special_tokens=False)["input_ids"]
assert tok.decode(ids, skip_special_tokens=False) == s, "id round-trip mismatch"
ids = np.array(ids, dtype=np.int64)
grid = inputs["image_grid_hws"][0].tolist() # [h_patches, w_patches]
img_h, img_w = grid[0]*14, grid[1]*14 # patches * patch_size
from PIL import Image
# ORIGINAL image dims — the reference denormalizes <0>..<1000> against
# image.size, not the 28px-padded preprocess canvas (grid*14).
img_w, img_h = Image.open(ROOT / spec["image"]).size
boxes, labels = [], []
i, n, cur_label = 0, len(ids), ""
while i < n:
Expand Down
7 changes: 5 additions & 2 deletions scripts/dump_preproc_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ def main():

img = Image.open(IMAGE_PATH).convert("RGB")
print(f"image {IMAGE_PATH} size(w,h)={img.size}")
# Boxes denormalize against the ORIGINAL image dims (reference convention),
# not the padded target canvas.
orig_w, orig_h = img.size

# =================== Manual _preprocess (capture resized_chw) ===========
# Mirror LocateAnythingImageProcessor._preprocess exactly:
Expand Down Expand Up @@ -170,8 +173,8 @@ def main():
if len(coords) == 4:
x1, y1, x2, y2 = coords # x1,y1,x2,y2
boxes.append([
x1 / 1000 * target_w, y1 / 1000 * target_h,
x2 / 1000 * target_w, y2 / 1000 * target_h,
x1 / 1000 * orig_w, y1 / 1000 * orig_h,
x2 / 1000 * orig_w, y2 / 1000 * orig_h,
])
labels.append(cur_label)
i = j + 1
Expand Down
6 changes: 4 additions & 2 deletions scripts/dump_slow_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@ def main():
ids = tok(s, add_special_tokens=False)["input_ids"]
assert tok.decode(ids, skip_special_tokens=False) == s, "id round-trip mismatch"
ids = np.array(ids, dtype=np.int64)
grid = inputs["image_grid_hws"][0].tolist() # [h_patches, w_patches]
img_h, img_w = grid[0]*14, grid[1]*14 # patches * patch_size
from PIL import Image
# ORIGINAL image dims — the reference denormalizes <0>..<1000> against
# image.size, not the 28px-padded preprocess canvas (grid*14).
img_w, img_h = Image.open(ROOT / spec["image"]).size
boxes, labels = [], []
i, n, cur_label = 0, len(ids), ""
while i < n:
Expand Down
8 changes: 5 additions & 3 deletions src/engine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ std::vector<Box> Engine::locate_image(const Image& img, const std::string& query
/*early_stop=*/!std::getenv("LA_NO_EARLYSTOP"));
if(!ok) return empty;

// 6) parse boxes. Coords denormalize against the preprocessed target size
// (img_w = target_w = gw*14, img_h = target_h = gh*14).
return parse_boxes(gen_ids, P.target_w, P.target_h,
// 6) parse boxes. Coords denormalize against the ORIGINAL image dims — the
// reference implementation projects <0>..<1000> tokens onto image.size,
// not the padded/downscaled preprocess canvas (which overshoots by up to
// one 28px pad and can exceed the image bounds).
return parse_boxes(gen_ids, img.w, img.h,
[&](const std::vector<int32_t>& t){ return tok_.decode(t); });
}

Expand Down
15 changes: 15 additions & 0 deletions tests/test_engine.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
#include "engine.hpp"
#include "image_io.hpp"
#include "parity.hpp"
#include <cstdlib>
#include <vector>
#include <cstdio>
#include <cmath>

// Boxes denormalize against the ORIGINAL image dims (reference convention).
// Regression guard: they were previously projected onto the 28px-padded
// preprocess canvas and could overshoot the image on non-aligned inputs.
static int boxes_within(const std::vector<la::Box>& bs, float w, float h){
int ok = 1;
for(const auto& b : bs)
ok &= (b.x1>=-0.5f && b.y1>=-0.5f && b.x2<=w+0.5f && b.y2<=h+0.5f);
return ok;
}
int main(){
const char* gguf=std::getenv("LA_TEST_GGUF"); const char* slow=std::getenv("LA_TEST_SLOW");
if(!gguf||!slow){std::fprintf(stderr,"skip\n");return 77;}
Expand All @@ -23,6 +34,7 @@ int main(){
std::printf(" box[%d] %s [%.1f %.1f %.1f %.1f] ref [%.1f %.1f %.1f %.1f]\n",
k, boxes[k].label.c_str(), boxes[k].x1,boxes[k].y1,boxes[k].x2,boxes[k].y2, r[0],r[1],r[2],r[3]);
}
ok &= boxes_within(boxes, 448.f, 448.f);

// Variable-grid (non-448) box gate on bus.jpg. The reference boxes + labels live
// in reference_preproc.gguf (slow_boxes / la_preproc.box_labels). JPEG-decode noise
Expand Down Expand Up @@ -60,6 +72,9 @@ int main(){
} else {
std::printf("bus.jpg: no reference boxes in LA_TEST_PREPROC; smoke only (%zu boxes)\n", bb.size());
}
la::Image bimg;
if(la::load_image_rgb(bpath.empty()?std::string(bus):bpath, bimg))
bok &= boxes_within(bb, (float)bimg.w, (float)bimg.h);
} else {
std::printf("bus.jpg: skip (LA_TEST_PREPROC unset or image missing)\n");
}
Expand Down