Instructions to use harness-race/opencode-r3 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use harness-race/opencode-r3 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("object-detection", model="harness-race/opencode-r3")# Load model directly from transformers import AutoImageProcessor, AutoModelForObjectDetection processor = AutoImageProcessor.from_pretrained("harness-race/opencode-r3") model = AutoModelForObjectDetection.from_pretrained("harness-race/opencode-r3", device_map="auto") - Notebooks
- Google Colab
- Kaggle
| # /// script | |
| # requires-python = ">=3.10" | |
| # dependencies = [ | |
| # "torch>=2.1", | |
| # "transformers>=4.40", | |
| # "accelerate>=0.27", | |
| # "datasets>=2.18", | |
| # "pycocotools", | |
| # "huggingface_hub>=0.23", | |
| # "Pillow", | |
| # "numpy", | |
| # "timm", | |
| # "scipy", | |
| # "requests", | |
| # ] | |
| # /// | |
| import os, json, time, argparse, io | |
| import numpy as np | |
| import torch | |
| from PIL import Image | |
| def parse_args(): | |
| p = argparse.ArgumentParser() | |
| p.add_argument("--data_dir", default="/data") | |
| p.add_argument("--base_model", default="facebook/detr-resnet-50") | |
| p.add_argument("--hub_id", default="harness-race/opencode-r3") | |
| p.add_argument("--epochs", type=int, default=25) | |
| p.add_argument("--batch_size", type=int, default=4) | |
| p.add_argument("--lr", type=float, default=1e-4) | |
| p.add_argument("--patience", type=int, default=4) | |
| p.add_argument("--seed", type=int, default=42) | |
| p.add_argument("--exp_name", default="opencode-r3-detr") | |
| p.add_argument("--max_steps", type=int, default=0) | |
| return p.parse_args() | |
| ARGS = parse_args() | |
| ID2LABEL = {0:"Photograph",1:"Illustration",2:"Map",3:"Comics/Cartoon",4:"Editorial Cartoon",5:"Headline",6:"Advertisement"} | |
| LABEL2ID = {v:k for k,v in ID2LABEL.items()} | |
| N = len(ID2LABEL) | |
| import datasets as hfds | |
| from torch.utils.data import Dataset, DataLoader | |
| from transformers import DetrImageProcessor, DetrForObjectDetection | |
| from tqdm import tqdm | |
| from pycocotools.coco import COCO | |
| from pycocotools.cocoeval import COCOeval | |
| def log(*m): | |
| print("[%s]" % time.strftime("%H:%M:%S"), *m, flush=True) | |
| torch.manual_seed(ARGS.seed) | |
| np.random.seed(ARGS.seed) | |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") | |
| log("torch", torch.__version__, "device", device, "cuda_avail", torch.cuda.is_available()) | |
| data_dir = ARGS.data_dir | |
| if not os.path.isdir(data_dir): | |
| log("mount %s not found; downloading dataset" % data_dir) | |
| data_dir = ARGS.data_dir = "biglam/loc_beyond_words" | |
| try: | |
| ds = hfds.load_dataset(data_dir, split="train") | |
| ds_val = hfds.load_dataset(data_dir, split="validation") | |
| except Exception as e: | |
| log("local load failed (%s); falling back to hub dataset" % e) | |
| data_dir = "biglam/loc_beyond_words" | |
| ds = hfds.load_dataset(data_dir, split="train") | |
| ds_val = hfds.load_dataset(data_dir, split="validation") | |
| def get_pil(ex): | |
| img = ex["image"] | |
| if isinstance(img, dict): | |
| img = Image.open(io.BytesIO(img["bytes"])).convert("RGB") | |
| else: | |
| img = img.convert("RGB") | |
| return img | |
| def fields(ex): | |
| objs = ex["objects"] | |
| bboxes=[]; cats=[]; areas=[]; ids=[] | |
| if objs is None: objs=[] | |
| for o in objs: | |
| b = o["bbox"] | |
| if isinstance(b, (str, tuple)): | |
| b = list(float(x) for x in b) | |
| bboxes.append([float(x) for x in b]) | |
| c = o["category_id"] | |
| if isinstance(c, str): | |
| cats.append(LABEL2ID.get(c, 0)) | |
| else: | |
| try: cats.append(int(c)&0x7fffffff) | |
| except Exception: cats.append(0) | |
| areas.append(float(o.get("area", b[2]*b[3]))) | |
| ids.append(int(o.get("id", 0))) | |
| return {"bbox":bboxes,"category_id":cats,"area":areas,"id":ids} | |
| # Build torch Dataset returning (pil_img, anns_for_processor, size_wh, row_id) | |
| class DetrDS(Dataset): | |
| def __init__(self, examples, with_size=True): | |
| self.examples = examples | |
| self.sizes = [] | |
| for ex in examples: | |
| try: | |
| w,h = ex["width"], ex["height"] | |
| except Exception: | |
| w,h = get_pil(ex).size | |
| self.sizes.append((w,h)) | |
| def __len__(self): | |
| return len(self.examples) | |
| def __getitem__(self, i): | |
| ex = self.examples[i] | |
| img = get_pil(ex) | |
| o = fields(ex) | |
| bboxes = o.get("bbox") or [] | |
| cats = o.get("category_id") | |
| if not isinstance(cats, list): | |
| cats = [0]*len(bboxes) | |
| anns = [{"bbox":[float(x) for x in bboxes[j]], "category_id":int(cats[j])&0x7fffffff, | |
| "area": float(bboxes[j][2]*bboxes[j][3])} for j in range(len(bboxes))] | |
| return img, anns, self.sizes[i], i | |
| processor = DetrImageProcessor.from_pretrained(ARGS.base_model, | |
| do_resize=True, size={"shortest_edge":800, "longest_edge":1333}) | |
| train_ds = DetrDS(list(ds)) | |
| val_ds = DetrDS(list(ds_val)) | |
| log("train", len(train_ds), "val", len(val_ds)) | |
| def collate_fn(batch): | |
| # batch: list of (img, anns, size, idx) | |
| images = [b[0] for b in batch] | |
| anns = [b[1] for b in batch] | |
| sizes = [b[2] for b in batch] | |
| idx = [b[3] for b in batch] | |
| return images, anns, sizes, idx | |
| train_dl = DataLoader(train_ds, batch_size=ARGS.batch_size, shuffle=True, num_workers=2, collate_fn=collate_fn) | |
| val_dl = DataLoader(val_ds, batch_size=ARGS.batch_size, shuffle=False, num_workers=2, collate_fn=collate_fn) | |
| model = DetrForObjectDetection.from_pretrained(ARGS.base_model, num_labels=N, | |
| ignore_mismatched_sizes=True, id2label=ID2LABEL, label2id=LABEL2ID) | |
| model.to(device) | |
| # distinct backbone (frozen-ish) vs head lr | |
| bb_params = [p for n,p in model.model.backbone.named_parameters() if "layers" in n] | |
| head_params = [p for n,p in model.named_parameters() if not n.startswith("model.backbone") or "layers" not in n] | |
| optim = torch.optim.AdamW([ | |
| {"params": bb_params, "lr": ARGS.lr/10}, | |
| {"params": head_params, "lr": ARGS.lr}, | |
| ], lr=ARGS.lr, weight_decay=1e-4) | |
| scaler = torch.amp.GradScaler("cuda", init_scale=2.0**12) if device.type=="cuda" else None | |
| def build_coco_gt(examples): | |
| gt = {"images":[],"annotations":[],"categories":[{"id":k,"name":v} for k,v in ID2LABEL.items()]} | |
| ann_id = 1 | |
| for i, ex in enumerate(examples): | |
| o = fields(ex) | |
| w,h = val_ds.sizes[i] | |
| gt["images"].append({"id":i,"width":w,"height":h}) | |
| bboxes = o.get("bbox") or [] | |
| cats = o.get("category_id") | |
| if not isinstance(cats, list): | |
| cats = [0]*len(bboxes) | |
| for j in range(len(bboxes)): | |
| b = [float(x) for x in bboxes[j]] | |
| gt["annotations"].append({"id":ann_id,"image_id":i,"category_id":int(cats[j])&0x7fffffff, | |
| "bbox":b,"area":b[2]*b[3],"iscrowd":0}); ann_id+=1 | |
| g = COCO(); g.dataset = gt; g.createIndex() | |
| return g | |
| coco_gt = build_coco_gt(list(ds_val)) | |
| def evaluate(): | |
| model.eval() | |
| dets = [] | |
| ann_id = 1 | |
| with torch.no_grad(): | |
| for images, anns, sizes, idx in tqdm(val_dl, desc="eval"): | |
| enc = processor(images=images, return_tensors="pt") | |
| pv = enc["pixel_values"].to(device); pm = enc["pixel_mask"].to(device) | |
| with torch.amp.autocast(device_type="cuda", dtype=torch.float16, enabled=(device.type=="cuda")): | |
| out = model(pixel_values=pv, pixel_mask=pm) | |
| target_sizes = torch.tensor([val_ds.sizes[ii][::-1] for ii in idx.astype(int)] if hasattr(idx,'astype') else [[val_ds.sizes[ii][1], val_ds.sizes[ii][0]] for ii in idx], device=device) | |
| # idx is list of ints already | |
| sz = [[val_ds.sizes[ii][1], val_ds.sizes[ii][0]] for ii in idx] | |
| results = processor.post_process_object_detection(out, threshold=0.0, target_sizes=sz) | |
| for ii, res in zip(idx, results): | |
| boxes = res["boxes"].cpu().numpy() # xmin ymin xmax ymax | |
| scores = res["scores"].cpu().numpy() | |
| labels = res["labels"].cpu().numpy() | |
| for b,s,l in zip(boxes,scores,labels): | |
| if s <= 0.0: continue | |
| dets.append({"id":ann_id,"image_id":ii,"category_id":int(l),"bbox":[float(b[0]),float(b[1]),float(b[2]-b[0]),float(b[3]-b[1])],"score":float(s)}) | |
| ann_id += 1 | |
| model.train() | |
| if len(dets)==0: | |
| return None | |
| try: | |
| preds = coco_gt.loadRes(dets) | |
| evaluator = COCOeval(coco_gt, preds, iouType="bbox") | |
| # set areas to all, max dets high | |
| evaluator.params.maxDets = [100, 300, 1000] | |
| evaluator.evaluate(); evaluator.accumulate(); evaluator.summarize() | |
| ap = evaluator.stats # [0]=AP@.5:.95 ... [5]=AP50 ... [6]=AR100 | |
| return {"AP": float(ap[0]), "AP50": float(ap[1]), "AP75": float(ap[2]), | |
| "AP_s": float(ap[3]), "AP_m": float(ap[4]), "AP_l": float(ap[5]), | |
| "AR_max100": float(ap[6]), "AR_max1000": float(ap[8])} | |
| except Exception as e: | |
| log("COCOeval failed:", e) | |
| return None | |
| # ---------- training ---------- | |
| best = {"AP": -1.0} | |
| no_improve = 0 | |
| global_step = 0 | |
| run_outputs = {} | |
| t_start = time.time() | |
| log("starting training; epochs", ARGS.epochs, "batch", ARGS.batch_size) | |
| for epoch in range(1, ARGS.epochs+1): | |
| model.train() | |
| ep_loss = 0.0; ep_losssum = {}; nb = 0 | |
| for images, anns, sizes, idx in tqdm(train_dl, desc="epoch %d"%epoch): | |
| anns_pp = [{"image_id": int(idx[k]), "annotations": anns[k]} for k in range(len(images))] | |
| enc = processor(images=images, annotations=anns_pp, return_tensors="pt") | |
| pv = enc["pixel_values"].to(device); pm = enc["pixel_mask"].to(device) | |
| labels = [{k: v.to(device) for k,v in l.items()} for l in enc.get("labels")] | |
| optim.zero_grad() | |
| try: | |
| with torch.amp.autocast(device_type="cuda", dtype=torch.float16, enabled=(device.type=="cuda")): | |
| out = model(pixel_values=pv, pixel_mask=pm, labels=labels) | |
| loss = out.loss | |
| except Exception as e: | |
| log("skipping batch that raised:", e) | |
| continue | |
| if not torch.isfinite(loss): | |
| log("skipping non-finite loss step") | |
| continue | |
| scaler.scale(loss).backward() | |
| scaler.step(optim) | |
| scaler.update() | |
| ep_loss += float(loss.detach().float()) | |
| for kk, vv in out.loss_dict.items(): | |
| ep_losssum[kk] = ep_losssum.get(kk, 0.0) + float(vv.detach().float()) | |
| nb += 1 | |
| global_step += 1 | |
| if ARGS.max_steps and global_step >= ARGS.max_steps: | |
| break | |
| log("epoch %d loss %.4f %s" % (epoch, ep_loss/nb, | |
| " ".join("%s %.4f"%(k, v/nb) for k,v in ep_losssum.items()))) | |
| m = evaluate() | |
| log("VAL", json.dumps(m)) | |
| if m and m["AP"] > best["AP"]: | |
| best = m | |
| best.update({"epoch": epoch, "global_step": global_step}) | |
| no_improve = 0 | |
| torch.save({"state_dict": model.state_dict()}, os.path.join(os.getcwd(), "best_model.pt")) | |
| log("saved new best AP=%.4f" % m["AP"]) | |
| else: | |
| no_improve += 1 | |
| if no_improve >= ARGS.patience: | |
| log("early stop after epoch", epoch) | |
| break | |
| if ARGS.max_steps and global_step >= ARGS.max_steps: | |
| break | |
| # decay lr slowly | |
| # (optional) torch.optim.lr_scheduler not added; keep fixed | |
| run_outputs["best"] = best | |
| run_outputs["epochs_run"] = epoch | |
| run_outputs["elapsed_sec"] = round(time.time()-t_start, 1) | |
| log("best val result:", best) | |
| # ---------- reload best & push ---------- | |
| if os.path.exists("best_model.pt"): | |
| sd = torch.load("best_model.pt", map_location="cpu") | |
| model.load_state_dict(sd["state_dict"]) | |
| else: | |
| best = evaluate() or {} | |
| run_outputs["best"] = best | |
| model.push_to_hub(ARGS.hub_id) | |
| processor.push_to_hub(ARGS.hub_id) | |
| log("pushed weights + preprocessor") | |
| # build model card | |
| card = { | |
| "library_name": "transformers", | |
| "pipeline_tag": "object-detection", | |
| "license": "apache-2.0", | |
| "tags": ["object-detection","detr","computer-vision","document-layout-analysis","pytorch"], | |
| "base_model": ARGS.base_model, | |
| "model-index": [{ | |
| "name": ARGS.exp_name, | |
| "results": [{ | |
| "task": {"type":"object-detection"}, | |
| "dataset": {"type":"biglam/loc_beyond_words","name":"Beyond Words (Testing)","config":"default"}, | |
| "metrics": [ | |
| {"type":"Average Precision","name":"mAP @[IoU=0.50:0.95]","value": round(run_outputs["best"].get("AP",0.0),4)}, | |
| {"type":"Average Precision","name":"mAP @[IoU=0.50]","value": round(run_outputs["best"].get("AP50",0.0),4)}, | |
| ] | |
| }] | |
| }] | |
| } | |
| def build_readme(res): | |
| m = res if res else {} | |
| class Row: | |
| pass | |
| def f(k, d=.0): | |
| return "%.4f" % m.get(k, d) | |
| front = json.dumps(card, indent=2) | |
| return f"""--- | |
| {front} | |
| --- | |
| # opencode-r3: Beyond Words Object Detection (DETR-ResNet-50) | |
| Model for detecting visual content regions in WWI-era historical newspaper pages | |
| from the US Library of Congress **Beyond Words / Chronicling America** collection. | |
| The base model `{ARGS.base_model}` (Apache-2.0) was fine-tuned on {len(train_ds)} train images | |
| and evaluated on {len(val_ds)} validation images over **7 classes**: | |
| Photograph, Illustration, Map, Comics/Cartoon, Editorial Cartoon, Headline, Advertisement. | |
| ## Model Details | |
| | | | | |
| |---|---| | |
| | **Base model** | {ARGS.base_model} (DETR, ResNet-50 backbone) | | |
| | **License** | Apache-2.0 (open, shareable) | | |
| | **Architecture** | `DetrForObjectDetection` | | |
| | **Task** | Object detection / document layout analysis | | |
| | **Dataset** | [biglam/loc_beyond_words](https://huggingface.co/datasets/biglam/loc_beyond_words) (CC0-1.0) | | |
| | **Splits** | {len(train_ds)} train / {len(val_ds)} validation | | |
| ## Training Procedure | |
| - **Optimizer:** AdamW (head LR {ARGS.lr}, backbone LR {ARGS.lr/10:g}) | |
| - **Epochs:** {run_outputs.get('epochs_run','?')} (early-stopped, patience {ARGS.patience}) | |
| - **Batch size:** {ARGS.batch_size} | |
| - **Image size:** longest edge 1333 / shortest edge 800 | |
| - **Hardware:** Hugging Face Jobs GPU (T4) | |
| - **Selection:** best checkpoint by validation mAP@[0.50:0.95] | |
| ## Evaluation Results (validation) | |
| | Metric | Value | | |
| |---|---| | |
| | **mAP @[IoU=0.50:0.95]** | **{f('AP')}** | | |
| | mAP @[IoU=0.50] | {f('AP50')} | | |
| | mAP @[IoU=0.75] | {f('AP75')} | | |
| | AP small | {f('AP_s')} | | |
| | AP medium | {f('AP_m')} | | |
| | AP large | {f('AP_l')} | | |
| | AR (maxDets=100) | {f('AR_max100')} | | |
| ## Usage | |
| ```python | |
| import torch | |
| from transformers import DetrImageProcessor, DetrForObjectDetection | |
| from PIL import Image | |
| model = DetrForObjectDetection.from_pretrained("harness-race/opencode-r3") | |
| processor = DetrImageProcessor.from_pretrained("harness-race/opencode-r3") | |
| image = Image.open("page.jpg").convert("RGB") | |
| inputs = processor(images=image, return_tensors="pt") | |
| with torch.no_grad(): | |
| outputs = model(**inputs) | |
| results = processor.post_process_object_detection( | |
| outputs, target_sizes=[image.size[::-1]], threshold=0.5)[0] | |
| for score, label_id, box in zip(results["scores"], results["labels"], results["boxes"]): | |
| if score > 0.5: | |
| print(model.config.id2label[label_id.item()], round(score.item(),3), box.tolist()) | |
| ``` | |
| ## Licensing | |
| Base model `{ARGS.base_model}` is **Apache-2.0** (permissive open license), so this | |
| fine-tuned model may be freely shared and reused. The training dataset is public | |
| domain (**CC0-1.0**). | |
| ## Known Limitations | |
| - Trained on a single era/language; pre-1875 layouts may underperform. | |
| - Skewed class distribution (headlines/ads dominate) can depress rare-class AP. | |
| """ | |
| readme = build_readme(run_outputs["best"]) | |
| with open("README.md","w") as f: | |
| f.write(readme) | |
| # push README + a results.json | |
| from huggingface_hub import HfApi | |
| api = HfApi() | |
| with open("validation_results.json", "w") as f: | |
| json.dump(run_outputs["best"], f, indent=2) | |
| with open("README.md", "w") as f: | |
| f.write(readme) | |
| api.upload_file(path_or_fileobj="validation_results.json", path_in_repo="validation_results.json", repo_id=ARGS.hub_id, repo_type="model") | |
| api.upload_file(path_or_fileobj="README.md", path_in_repo="README.md", repo_id=ARGS.hub_id, repo_type="model") | |
| # model card metadata (config fields like license) already in config.json via push_to_hub | |
| log("=== FINAL VALIDATION RESULTS ===") | |
| print(json.dumps(run_outputs["best"], indent=2)) | |
| log("done") | |