Views
No views yet
| Model Size | Model Name | Base Model | Huggingface Link |
|---|---|---|---|
| 2B | NuExtract-2.0-2B | InternVL2_5-2B | NuExtract-2-2B |
| 4B | NuExtract-2.0-4B | InternVL2_5-4B | NuExtract-2-4B |
| 8B | NuExtract-2.0-8B | InternVL2_5-8B | NuExtract-2-8B |
verbatim-string - instructs the model to extract text that is present verbatim in the input.string - a generic string field that can incorporate paraphrasing/abstraction.integer - a whole number.number - a whole or decimal number.date-time - ISO formatted date.["string"])enum - a choice from set of possible answers (represented in template as an array of options, e.g. ["yes", "no", "maybe"]).multi-label - an enum that can have multiple possible answers (represented in template as a double-wrapped array, e.g. [["A", "B", "C"]]).null or [] (for arrays and multi-labels).1{
2 "first_name": "verbatim-string",
3 "last_name": "verbatim-string",
4 "description": "string",
5 "age": "integer",
6 "gpa": "number",
7 "birth_date": "date-time",
8 "nationality": ["France", "England", "Japan", "USA", "China"],
9 "languages_spoken": [["English", "French", "Japanese", "Mandarin", "Spanish"]]
10}1{
2 "first_name": "Susan",
3 "last_name": "Smith",
4 "description": "A student studying computer science.",
5 "age": 20,
6 "gpa": 3.7,
7 "birth_date": "2005-03-01",
8 "nationality": "England",
9 "languages_spoken": ["English", "French"]
10}1import torch
2import torchvision.transforms as T
3from PIL import Image
4from torchvision.transforms.functional import InterpolationMode
5
6IMAGENET_MEAN = (0.485, 0.456, 0.406)
7IMAGENET_STD = (0.229, 0.224, 0.225)
8
9def build_transform(input_size):
10 MEAN, STD = IMAGENET_MEAN, IMAGENET_STD
11 transform = T.Compose([
12 T.Lambda(lambda img: img.convert('RGB') if img.mode != 'RGB' else img),
13 T.Resize((input_size, input_size), interpolation=InterpolationMode.BICUBIC),
14 T.ToTensor(),
15 T.Normalize(mean=MEAN, std=STD)
16 ])
17 return transform
18
19def find_closest_aspect_ratio(aspect_ratio, target_ratios, width, height, image_size):
20 best_ratio_diff = float('inf')
21 best_ratio = (1, 1)
22 area = width * height
23 for ratio in target_ratios:
24 target_aspect_ratio = ratio[0] / ratio[1]
25 ratio_diff = abs(aspect_ratio - target_aspect_ratio)
26 if ratio_diff < best_ratio_diff:
27 best_ratio_diff = ratio_diff
28 best_ratio = ratio
29 elif ratio_diff == best_ratio_diff:
30 if area > 0.5 * image_size * image_size * ratio[0] * ratio[1]:
31 best_ratio = ratio
32 return best_ratio
33
34def dynamic_preprocess(image, min_num=1, max_num=12, image_size=448, use_thumbnail=False):
35 orig_width, orig_height = image.size
36 aspect_ratio = orig_width / orig_height
37
38 # calculate the existing image aspect ratio
39 target_ratios = set(
40 (i, j) for n in range(min_num, max_num + 1) for i in range(1, n + 1) for j in range(1, n + 1) if
41 i * j <= max_num and i * j >= min_num)
42 target_ratios = sorted(target_ratios, key=lambda x: x[0] * x[1])
43
44 # find the closest aspect ratio to the target
45 target_aspect_ratio = find_closest_aspect_ratio(
46 aspect_ratio, target_ratios, orig_width, orig_height, image_size)
47
48 # calculate the target width and height
49 target_width = image_size * target_aspect_ratio[0]
50 target_height = image_size * target_aspect_ratio[1]
51 blocks = target_aspect_ratio[0] * target_aspect_ratio[1]
52
53 # resize the image
54 resized_img = image.resize((target_width, target_height))
55 processed_images = []
56 for i in range(blocks):
57 box = (
58 (i % (target_width // image_size)) * image_size,
59 (i // (target_width // image_size)) * image_size,
60 ((i % (target_width // image_size)) + 1) * image_size,
61 ((i // (target_width // image_size)) + 1) * image_size
62 )
63 # split the image
64 split_img = resized_img.crop(box)
65 processed_images.append(split_img)
66 assert len(processed_images) == blocks
67 if use_thumbnail and len(processed_images) != 1:
68 thumbnail_img = image.resize((image_size, image_size))
69 processed_images.append(thumbnail_img)
70 return processed_images
71
72def load_image(image_file, input_size=448, max_num=12):
73 image = Image.open(image_file).convert('RGB')
74 transform = build_transform(input_size=input_size)
75 images = dynamic_preprocess(image, image_size=input_size, use_thumbnail=True, max_num=max_num)
76 pixel_values = [transform(image) for image in images]
77 pixel_values = torch.stack(pixel_values)
78 return pixel_values
79
80def prepare_inputs(messages, image_paths, tokenizer, device='cuda', dtype=torch.bfloat16):
81 """
82 Prepares multi-modal input components (supports multiple images per prompt).
83
84 Args:
85 messages: List of input messages/prompts (strings or dicts with 'role' and 'content')
86 image_paths: List where each element is either None (for text-only) or a list of image paths
87 tokenizer: The tokenizer to use for applying chat templates
88 device: Device to place tensors on ('cuda', 'cpu', etc.)
89 dtype: Data type for image tensors (default: torch.bfloat16)
90
91 Returns:
92 dict: Contains 'prompts', 'pixel_values_list', and 'num_patches_list' ready for the model
93 """
94 # Make sure image_paths list is at least as long as messages
95 if len(image_paths) < len(messages):
96 # Pad with None for text-only messages
97 image_paths = image_paths + [None] * (len(messages) - len(image_paths))
98
99 # Process images and collect patch information
100 loaded_images = []
101 num_patches_list = []
102 for paths in image_paths:
103 if paths and isinstance(paths, list) and len(paths) > 0:
104 # Load each image in this prompt
105 prompt_images = []
106 prompt_patches = []
107
108 for path in paths:
109 # Load the image
110 img = load_image(path).to(dtype=dtype, device=device)
111
112 # Ensure img has correct shape [patches, C, H, W]
113 if len(img.shape) == 3: # [C, H, W] -> [1, C, H, W]
114 img = img.unsqueeze(0)
115
116 prompt_images.append(img)
117 # Record the number of patches for this image
118 prompt_patches.append(img.shape[0])
119
120 loaded_images.append(prompt_images)
121 num_patches_list.append(prompt_patches)
122 else:
123 # Text-only prompt
124 loaded_images.append(None)
125 num_patches_list.append([])
126
127 # Create the concatenated pixel_values_list
128 pixel_values_list = []
129 for prompt_images in loaded_images:
130 if prompt_images:
131 # Concatenate all images for this prompt
132 pixel_values_list.append(torch.cat(prompt_images, dim=0))
133 else:
134 # Text-only prompt
135 pixel_values_list.append(None)
136
137 # Format messages for the model
138 if all(isinstance(m, str) for m in messages):
139 # Simple string messages: convert to chat format
140 batch_messages = [
141 [{"role": "user", "content": message}]
142 for message in messages
143 ]
144 else:
145 # Assume messages are already in the right format
146 batch_messages = messages
147
148 # Apply chat template
149 prompts = tokenizer.apply_chat_template(
150 batch_messages,
151 tokenize=False,
152 add_generation_prompt=True
153 )
154
155 return {
156 'prompts': prompts,
157 'pixel_values_list': pixel_values_list,
158 'num_patches_list': num_patches_list
159 }
160
161def construct_message(text, template, examples=None):
162 """
163 Construct the individual NuExtract message texts, prior to chat template formatting.
164 """
165 # add few-shot examples if needed
166 if examples is not None and len(examples) > 0:
167 icl = "# Examples:\n"
168 for row in examples:
169 icl += f"## Input:\n{row['input']}\n## Output:\n{row['output']}\n"
170 else:
171 icl = ""
172
173 return f"""# Template:\n{template}\n{icl}# Context:\n{text}"""1IMG_START_TOKEN='<img>'
2IMG_END_TOKEN='</img>'
3IMG_CONTEXT_TOKEN='<IMG_CONTEXT>'
4
5def nuextract_generate(model, tokenizer, prompts, generation_config, pixel_values_list=None, num_patches_list=None):
6 """
7 Generate responses for a batch of NuExtract inputs.
8 Support for multiple and varying numbers of images per prompt.
9
10 Args:
11 model: The vision-language model
12 tokenizer: The tokenizer for the model
13 pixel_values_list: List of tensor batches, one per prompt
14 Each batch has shape [num_images, channels, height, width] or None for text-only prompts
15 prompts: List of text prompts
16 generation_config: Configuration for text generation
17 num_patches_list: List of lists, each containing patch counts for images in a prompt
18
19 Returns:
20 List of generated responses
21 """
22 img_context_token_id = tokenizer.convert_tokens_to_ids(IMG_CONTEXT_TOKEN)
23 model.img_context_token_id = img_context_token_id
24
25 # Replace all image placeholders with appropriate tokens
26 modified_prompts = []
27 total_image_files = 0
28 total_patches = 0
29 image_containing_prompts = []
30 for idx, prompt in enumerate(prompts):
31 # check if this prompt has images
32 has_images = (pixel_values_list and
33 idx < len(pixel_values_list) and
34 pixel_values_list[idx] is not None and
35 isinstance(pixel_values_list[idx], torch.Tensor) and
36 pixel_values_list[idx].shape[0] > 0)
37
38 if has_images:
39 # prompt with image placeholders
40 image_containing_prompts.append(idx)
41 modified_prompt = prompt
42
43 patches = num_patches_list[idx] if (num_patches_list and idx < len(num_patches_list)) else []
44 num_images = len(patches)
45 total_image_files += num_images
46 total_patches += sum(patches)
47
48 # replace each <image> placeholder with image tokens
49 for i, num_patches in enumerate(patches):
50 image_tokens = IMG_START_TOKEN + IMG_CONTEXT_TOKEN * model.num_image_token * num_patches + IMG_END_TOKEN
51 modified_prompt = modified_prompt.replace('<image>', image_tokens, 1)
52 else:
53 # text-only prompt
54 modified_prompt = prompt
55
56 modified_prompts.append(modified_prompt)
57
58 # process all prompts in a single batch
59 tokenizer.padding_side = 'left'
60 model_inputs = tokenizer(modified_prompts, return_tensors='pt', padding=True)
61 input_ids = model_inputs['input_ids'].to(model.device)
62 attention_mask = model_inputs['attention_mask'].to(model.device)
63
64 eos_token_id = tokenizer.convert_tokens_to_ids("<|im_end|>\n".strip())
65 generation_config['eos_token_id'] = eos_token_id
66
67 # prepare pixel values
68 flattened_pixel_values = None
69 if image_containing_prompts:
70 # collect and concatenate all image tensors
71 all_pixel_values = []
72 for idx in image_containing_prompts:
73 all_pixel_values.append(pixel_values_list[idx])
74
75 flattened_pixel_values = torch.cat(all_pixel_values, dim=0)
76 print(f"Processing batch with {len(prompts)} prompts, {total_image_files} actual images, and {total_patches} total patches")
77 else:
78 print(f"Processing text-only batch with {len(prompts)} prompts")
79
80 # generate outputs
81 outputs = model.generate(
82 pixel_values=flattened_pixel_values, # will be None for text-only prompts
83 input_ids=input_ids,
84 attention_mask=attention_mask,
85 **generation_config
86 )
87
88 # Decode responses
89 responses = tokenizer.batch_decode(outputs, skip_special_tokens=True)
90
91 return responses1import torch
2from transformers import AutoModelForCausalLM, AutoTokenizer
3
4model_name = ""
5
6tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True, padding_side='left')
7model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True,
8 torch_dtype=torch.bfloat16,
9 attn_implementation="flash_attention_2" # we recommend using flash attention
10 ).to("cuda")1template = """{"names": ["verbatim-string"]}"""
2text = "John went to the restaurant with Mary. James went to the cinema."
3
4input_messages = [construct_message(text, template)]
5
6input_content = prepare_inputs(
7 messages=input_messages,
8 image_paths=[],
9 tokenizer=tokenizer,
10)
11
12generation_config = {"do_sample": False, "num_beams": 1, "max_new_tokens": 2048}
13
14with torch.no_grad():
15 result = nuextract_generate(
16 model=model,
17 tokenizer=tokenizer,
18 prompts=input_content['prompts'],
19 pixel_values_list=input_content['pixel_values_list'],
20 num_patches_list=input_content['num_patches_list'],
21 generation_config=generation_config
22 )
23for y in result:
24 print(y)
25# {"names": ["John", "Mary", "James"]}1template = """{"names": ["verbatim-string"], "female_names": ["verbatim-string"]}"""
2text = "John went to the restaurant with Mary. James went to the cinema."
3examples = [
4 {
5 "input": "Stephen is the manager at Susan's store.",
6 "output": """{"names": ["STEPHEN", "SUSAN"], "female_names": ["SUSAN"]}"""
7 }
8]
9
10input_messages = [construct_message(text, template, examples)]
11
12input_content = prepare_inputs(
13 messages=input_messages,
14 image_paths=[],
15 tokenizer=tokenizer,
16)
17
18generation_config = {"do_sample": False, "num_beams": 1, "max_new_tokens": 2048}
19
20with torch.no_grad():
21 result = nuextract_generate(
22 model=model,
23 tokenizer=tokenizer,
24 prompts=input_content['prompts'],
25 pixel_values_list=input_content['pixel_values_list'],
26 num_patches_list=input_content['num_patches_list'],
27 generation_config=generation_config
28 )
29for y in result:
30 print(y)
31# {"names": ["JOHN", "MARY", "JAMES"], "female_names": ["MARY"]}<image> placeholder instead of text and image paths should be provided in a list in order of appearance in the prompt (in this example 0.jpg will be for the in-context example and 1.jpg for the true input).1template = """{"store": "verbatim-string"}"""
2text = "<image>"
3examples = [
4 {
5 "input": "<image>",
6 "output": """{"store": "Walmart"}"""
7 }
8]
9
10input_messages = [construct_message(text, template, examples)]
11
12images = [
13 ["0.jpg", "1.jpg"]
14]
15
16input_content = prepare_inputs(
17 messages=input_messages,
18 image_paths=images,
19 tokenizer=tokenizer,
20)
21
22generation_config = {"do_sample": False, "num_beams": 1, "max_new_tokens": 2048}
23
24with torch.no_grad():
25 result = nuextract_generate(
26 model=model,
27 tokenizer=tokenizer,
28 prompts=input_content['prompts'],
29 pixel_values_list=input_content['pixel_values_list'],
30 num_patches_list=input_content['num_patches_list'],
31 generation_config=generation_config
32 )
33for y in result:
34 print(y)
35# {"store": "Trader Joe's"}1inputs = [
2 # image input with no ICL examples
3 {
4 "text": "<image>",
5 "template": """{"store_name": "verbatim-string"}""",
6 "examples": None,
7 },
8 # image input with 1 ICL example
9 {
10 "text": "<image>",
11 "template": """{"store_name": "verbatim-string"}""",
12 "examples": [
13 {
14 "input": "<image>",
15 "output": """{"store_name": "Walmart"}""",
16 }
17 ],
18 },
19 # text input with no ICL examples
20 {
21 "text": "John went to the restaurant with Mary. James went to the cinema.",
22 "template": """{"names": ["verbatim-string"]}""",
23 "examples": None,
24 },
25 # text input with ICL example
26 {
27 "text": "John went to the restaurant with Mary. James went to the cinema.",
28 "template": """{"names": ["verbatim-string"], "female_names": ["verbatim-string"]}""",
29 "examples": [
30 {
31 "input": "Stephen is the manager at Susan's store.",
32 "output": """{"names": ["STEPHEN", "SUSAN"], "female_names": ["SUSAN"]}"""
33 }
34 ],
35 },
36]
37
38input_messages = [
39 construct_message(
40 x["text"],
41 x["template"],
42 x["examples"]
43 ) for x in inputs
44]
45
46images = [
47 ["0.jpg"],
48 ["0.jpg", "1.jpg"],
49 None,
50 None
51]
52
53input_content = prepare_inputs(
54 messages=input_messages,
55 image_paths=images,
56 tokenizer=tokenizer,
57)
58
59generation_config = {"do_sample": False, "num_beams": 1, "max_new_tokens": 2048}
60
61with torch.no_grad():
62 result = nuextract_generate(
63 model=model,
64 tokenizer=tokenizer,
65 prompts=input_content['prompts'],
66 pixel_values_list=input_content['pixel_values_list'],
67 num_patches_list=input_content['num_patches_list'],
68 generation_config=generation_config
69 )
70for y in result:
71 print(y)
72# {"store_name": "WAL*MART"}
73# {"store_name": "Trader Joe's"}
74# {"names": ["John", "Mary", "James"]}
75# {"names": ["JOHN", "MARY", "JAMES"], "female_names": ["MARY"]}1def generate_template(description):
2 input_messages = [description]
3 input_content = prepare_inputs(
4 messages=input_messages,
5 image_paths=[],
6 tokenizer=tokenizer,
7 )
8
9 generation_config = {"do_sample": True, "temperature": 0.4, "max_new_tokens": 256}
10
11 with torch.no_grad():
12 result = nuextract_generate(
13 model=model,
14 tokenizer=tokenizer,
15 prompts=input_content['prompts'],
16 pixel_values_list=input_content['pixel_values_list'],
17 num_patches_list=input_content['num_patches_list'],
18 generation_config=generation_config
19 )
20 return result[0]
21
22xml_template = """<SportResult>
23 <Date></Date>
24 <Sport></Sport>
25 <Venue></Venue>
26 <HomeTeam></HomeTeam>
27 <AwayTeam></AwayTeam>
28 <HomeScore></HomeScore>
29 <AwayScore></AwayScore>
30 <TopScorer></TopScorer>
31</SportResult>"""
32result = generate_template(xml_template)
33
34print(result)
35# {
36# "SportResult": {
37# "Date": "date-time",
38# "Sport": "verbatim-string",
39# "Venue": "verbatim-string",
40# "HomeTeam": "verbatim-string",
41# "AwayTeam": "verbatim-string",
42# "HomeScore": "integer",
43# "AwayScore": "integer",
44# "TopScorer": "verbatim-string"
45# }
46# }1text = """Give me relevant info about startup companies mentioned."""
2result = generate_template(text)
3
4print(result)
5# {
6# "Startup_Companies": [
7# {
8# "Name": "verbatim-string",
9# "Products": [
10# "string"
11# ],
12# "Location": "verbatim-string",
13# "Company_Type": [
14# "Technology",
15# "Finance",
16# "Health",
17# "Education",
18# "Other"
19# ]
20# }
21# ]
22# }