XinNUS commited on
Commit
7fe0d95
·
verified ·
1 Parent(s): 90553b8

Fix model card: align import/clone to CycleGRPO repo (projects.transformers.vq_sam2)

Browse files
Files changed (1) hide show
  1. README.md +53 -144
README.md CHANGED
@@ -16,161 +16,70 @@ rewarded by how well the model can ground it back to the region it describes
16
  stage. It produces descriptions with **interleaved segmentation masks** for the
17
  corresponding parts of the answer, decoded through the SAMTok mask tokenizer.
18
 
 
 
19
  ## Quickstart
20
- Install `transformers` (with Qwen3-VL support) and get the SAMTok mask-decoder code,
21
- which provides the `projects.samtok.models` module (`VQ_SAM2`, `SAM2Config`,
22
- `DirectResize`) used to turn the generated mask tokens into segmentation masks:
 
 
23
 
24
  ```bash
25
  pip install "transformers>=4.57"
26
- # SAMTok mask tokenizer / VQ-SAM2 decoder code (provides projects.samtok.models):
27
- git clone https://github.com/bytedance/Sa2VA.git
28
- cd Sa2VA # run your script from the repo root so `projects.samtok.models` is importable
29
  ```
30
 
31
- ### Using 🤗 Transformers to Chat
32
 
33
  ```python
34
- import re
35
  import torch
36
- import numpy as np
37
- from PIL import Image
38
-
39
- def extract_mt_token_ids_v1(text):
40
- pattern = r"<\|mt_(\d{4})\|>"
41
- return [int(x) for x in re.findall(pattern, text)]
42
-
43
- def extract_mt_token_ids_v2(text):
44
- pattern = re.compile(r'<\|mt_start\|><\|mt_(\d{4})\|><\|mt_(\d{4})\|><\|mt_end\|>')
45
- matches = pattern.findall(text)
46
- ret_list = []
47
- for num1, num2 in matches:
48
- ret_list.append(int(num1))
49
- ret_list.append(int(num2))
50
- return ret_list
51
-
52
- def find_first_index(arr, value):
53
- indices = np.where(arr == value)[0]
54
- return indices[0] if len(indices) > 0 else -1
55
-
56
- def fix_mt_format_comprehensive(text):
57
- pattern_too_many = r'(<\|mt_start\|>)(<\|mt_\d+\|>)(<\|mt_\d+\|>)(?:<\|mt_\d+\|>)+<\|mt_end\|>'
58
- replacement_too_many = r'\1\2\3<|mt_end|>'
59
- text = re.sub(pattern_too_many, replacement_too_many, text)
60
-
61
- pattern_too_few_with_end = r'(<\|mt_start\|>)(<\|mt_\d+\|>)(<\|mt_end\|>)'
62
- replacement_too_few = r'\1\2<|mt_9999|><|mt_end|>'
63
- text = re.sub(pattern_too_few_with_end, replacement_too_few, text)
64
-
65
- pattern_too_few_no_end = r'(<\|mt_start\|>)(<\|mt_\d+\|>)(?!<\|mt_)'
66
- replacement_too_few_no_end = r'\1\2<|mt_9999|><|mt_end|>'
67
- text = re.sub(pattern_too_few_no_end, replacement_too_few_no_end, text)
68
- return text
69
-
70
  from transformers import Qwen3VLForConditionalGeneration, AutoProcessor
71
- from projects.samtok.models import DirectResize, VQ_SAM2, VQ_SAM2Config, SAM2Config
72
-
73
- CYCLEGRPO_REPO = "XinNUS/CycleGRPO-4B"
74
- SAMTOK_REPO = "zhouyik/Qwen3-VL-4B-SAMTok" # mask tokenizer / SAM2 decoder assets are reused from the base repo
75
 
76
- # build VLM (CycleGRPO weights)
77
  model = Qwen3VLForConditionalGeneration.from_pretrained(
78
- CYCLEGRPO_REPO, torch_dtype="auto"
79
- ).cuda().eval()
80
- processor = AutoProcessor.from_pretrained(CYCLEGRPO_REPO)
81
-
82
- # build SAMTok mask decoder (reused from the base model repo)
83
- CODEBOOK_SIZE = 256
84
- CODEBOOK_DEPTH = 2
85
- sam2_config = SAM2Config(
86
- ckpt_path=f"{SAMTOK_REPO}/sam2.1_hiera_large.pt",
87
- )
88
- vq_sam2_config = VQ_SAM2Config(
89
- sam2_config=sam2_config,
90
- codebook_size=CODEBOOK_SIZE,
91
- codebook_depth=CODEBOOK_DEPTH,
92
- shared_codebook=False,
93
- latent_dim=256,
94
- )
95
- vq_sam2 = VQ_SAM2(vq_sam2_config).cuda().eval()
96
- state = torch.load(f"{SAMTOK_REPO}/mask_tokenizer_256x2.pth", map_location="cpu")
97
- vq_sam2.load_state_dict(state)
98
- sam2_image_processor = DirectResize(1024)
99
-
100
- # message
101
- image_path = "figs/totoro.jpg"
102
- question = "Could you please give me a detail description of the image? Please respond with interleaved segmentation masks for the corresponding parts of the answer."
103
- image = Image.open(image_path).convert('RGB')
104
- ori_width, ori_height = image.size
105
- messages = [
106
- {
107
- "role": "user",
108
- "content": [
109
- {
110
- "type": "image",
111
- "image": image_path,
112
- },
113
- {"type": "text", "text": question},
114
- ],
115
- }
116
- ]
117
-
118
- # VLM inference
119
  inputs = processor.apply_chat_template(
120
- messages,
121
- tokenize=True,
122
- add_generation_prompt=True,
123
- return_dict=True,
124
- return_tensors="pt"
125
- )
126
- inputs = inputs.to(model.device)
127
-
128
- generated_ids = model.generate(
129
- **inputs,
130
- max_new_tokens=512,
131
- do_sample=False,
132
- top_p=1.0,
133
- )
134
- generated_ids_trimmed = [
135
- out_ids[len(in_ids) :] for in_ids, out_ids in zip(inputs.input_ids, generated_ids)
136
- ]
137
- output_text = processor.batch_decode(
138
- generated_ids_trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
139
- )
140
-
141
- # decode mask
142
- quant_ids = extract_mt_token_ids_v1(output_text[0])
143
- if len(quant_ids) % CODEBOOK_DEPTH != 0:
144
- output_text = [fix_mt_format_comprehensive(output_text[0])]
145
- quant_ids = extract_mt_token_ids_v2(output_text[0])
146
-
147
- batch_size = len(quant_ids) // CODEBOOK_DEPTH
148
- remap_quant_ids = []
149
- tags = []
150
- for bs_id in range(batch_size):
151
- chunk_quant_ids = quant_ids[bs_id*CODEBOOK_DEPTH:(bs_id+1)*CODEBOOK_DEPTH]
152
- tags.append(f"{chunk_quant_ids[0]}-{chunk_quant_ids[1]}")
153
- remap_chunk_quant_ids = [quant_id - book_id*CODEBOOK_SIZE for book_id, quant_id in enumerate(chunk_quant_ids)]
154
- code1 = remap_chunk_quant_ids[0]
155
- code2 = remap_chunk_quant_ids[1]
156
- if not (code2 >= 0 and code2 < CODEBOOK_SIZE):
157
- code2 = -1
158
- remap_chunk_quant_ids_error_handle = [code1, code2]
159
- remap_quant_ids.append(remap_chunk_quant_ids_error_handle)
160
-
161
- batch_size = len(remap_quant_ids)
162
- sam2_image = np.array(image)
163
- sam2_image = sam2_image_processor.apply_image(sam2_image)
164
- sam2_pixel_values = torch.from_numpy(sam2_image).permute(2, 0, 1).contiguous()
165
- sam2_pixel_values = sam2_pixel_values.unsqueeze(0).to(vq_sam2.dtype).to(vq_sam2.device)
166
- sam2_pixel_values = sam2_pixel_values.repeat(batch_size, 1, 1, 1)
167
-
168
- quant_ids = torch.LongTensor(remap_quant_ids).to(vq_sam2.device)
169
-
170
- with torch.no_grad():
171
- _pred_masks = vq_sam2.forward_with_codes(sam2_pixel_values, quant_ids)
172
- _pred_masks = torch.nn.functional.interpolate(_pred_masks, size=(ori_height, ori_width), mode='bilinear')
173
- _pred_masks = _pred_masks > 0.5
174
- _pred_masks = _pred_masks[:, 0, :, :].cpu().numpy().astype(np.uint8)
175
- text_token_2d_mask_mapping = {tag: _pred_mask for tag, _pred_mask in zip(tags, _pred_masks)}
176
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
16
  stage. It produces descriptions with **interleaved segmentation masks** for the
17
  corresponding parts of the answer, decoded through the SAMTok mask tokenizer.
18
 
19
+ Code: [github.com/devinxzhang/CycleGRPO](https://github.com/devinxzhang/CycleGRPO)
20
+
21
  ## Quickstart
22
+
23
+ CycleGRPO-4B is a Qwen3-VL-4B that emits **SAMTok mask tokens** (`<|mt_...|>`). Plain
24
+ text generation works with 🤗 Transformers directly; turning the mask tokens into
25
+ segmentation masks needs the VQ-SAM2 decoder from the CycleGRPO repo
26
+ (`projects.transformers.vq_sam2`), so clone and install it first:
27
 
28
  ```bash
29
  pip install "transformers>=4.57"
30
+ git clone https://github.com/devinxzhang/CycleGRPO.git
31
+ cd CycleGRPO # run from the repo root so `projects.transformers.vq_sam2` imports
32
+ pip install -e .
33
  ```
34
 
35
+ ### Generate (text + mask tokens)
36
 
37
  ```python
 
38
  import torch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  from transformers import Qwen3VLForConditionalGeneration, AutoProcessor
 
 
 
 
40
 
41
+ model_id = "XinNUS/CycleGRPO-4B"
42
  model = Qwen3VLForConditionalGeneration.from_pretrained(
43
+ model_id, dtype="auto", device_map="auto"
44
+ ).eval()
45
+ processor = AutoProcessor.from_pretrained(model_id)
46
+
47
+ messages = [{
48
+ "role": "user",
49
+ "content": [
50
+ {"type": "image", "image": "figs/totoro.jpg"},
51
+ {"type": "text", "text": "Describe the image with interleaved segmentation "
52
+ "masks for the corresponding parts of the answer."},
53
+ ],
54
+ }]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  inputs = processor.apply_chat_template(
56
+ messages, tokenize=True, add_generation_prompt=True,
57
+ return_dict=True, return_tensors="pt",
58
+ ).to(model.device)
59
+
60
+ out = model.generate(**inputs, max_new_tokens=512, do_sample=False)
61
+ text = processor.batch_decode(out[:, inputs["input_ids"].shape[1]:], skip_special_tokens=True)[0]
62
+ print(text) # answer text interleaved with <|mt_start|><|mt_XXXX|><|mt_YYYY|><|mt_end|> mask tokens
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  ```
64
+
65
+ ### Decode mask tokens → segmentation masks
66
+
67
+ The `<|mt_...|>` tokens are decoded to masks by the VQ-SAM2 mask tokenizer. Use the
68
+ **reference implementation in the CycleGRPO repo** rather than re-deriving it — see
69
+ `evaluation/groundingsuite/qwen3vl_groundingsuite_infer.py` (or
70
+ `evaluation/dlc_bench/inference.py`), which build the decoder and run the decode loop:
71
+
72
+ ```python
73
+ from projects.transformers.vq_sam2 import VQ_SAM2, VQ_SAM2Config, SAM2Config
74
+ # Those scripts also contain the `DirectResize` preprocessor, the mt-token parsing
75
+ # (extract_mt_token_ids / fix_mt_format), and the `VQ_SAM2.forward_with_codes(...)`
76
+ # decode step (codebook size 256, depth 2). Reuse them directly.
77
+ ```
78
+
79
+ The decoder weights — `mask_tokenizer_256x2.pth` and `sam2.1_hiera_large.pt` — come
80
+ from the base model [Qwen3-VL-4B-SAMTok](https://huggingface.co/zhouyik/Qwen3-VL-4B-SAMTok).
81
+
82
+ ## License
83
+
84
+ Released under Apache-2.0. Derived from Qwen3-VL-4B-SAMTok; use is also subject to the
85
+ base model's license and terms.