Skip to content

Unfinished code#244

Merged
tastelikefeet merged 27 commits into
modelscope:devfrom
tastelikefeet:feat/emb_utility
Jul 9, 2026
Merged

Unfinished code#244
tastelikefeet merged 27 commits into
modelscope:devfrom
tastelikefeet:feat/emb_utility

Conversation

@tastelikefeet

Copy link
Copy Markdown
Collaborator

PR type

  • Bug Fix
  • New Feature
  • Document Updates
  • More Models or Datasets Support

PR information

Write the detail information belongs to this PR.

Experiment results

Paste your experiment result here(if needed).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a comprehensive suite of scripts and modules for building and evaluating a RAG index of thinking traces, training embedding models, and running GRPO reinforcement learning on math datasets. It also includes framework-level updates to the twinkle library, such as Ray timeout support and double-sided clipping in GRPO loss. The review feedback highlights several critical issues, including potential TypeErrors due to unhandled None returns from template.encode, thread-safety concerns during concurrent LanceDB writes, silent data loss in Cosmopedia parsing, a logical bug overriding valid 0 timeouts in Ray collection, and missing resource cleanup in background thread pools.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +76 to +77
for f in $OUTDIR/ablation_*_65k.jsonl $OUTDIR/ablation_*_24k.jsonl; do
n=$(wc -l < "$f")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since some ablation runs are commented out in this script, the glob patterns (e.g., ablation_*_24k.jsonl) might not match any files. In bash, unmatched globs are left unexpanded as literal strings. Since set -euo pipefail is active, trying to read from a non-existent literal glob path will cause wc to fail and the script to exit prematurely. We should check if the file exists before processing it.

Suggested change
for f in $OUTDIR/ablation_*_65k.jsonl $OUTDIR/ablation_*_24k.jsonl; do
n=$(wc -l < "$f")
for f in $OUTDIR/ablation_*_65k.jsonl $OUTDIR/ablation_*_24k.jsonl; do
[ -f "$f" ] || continue
n=$(wc -l < "$f")

Comment on lines +783 to +824
def _embed_and_insert(kept_rows: List[Dict[str, Any]]) -> None:
"""Phase 2+3: embed compressed texts and insert into LanceDB."""
if not kept_rows:
return
anchor_emb = get_embeddings(
emb_model, emb_template, [r['query_compressed'] for r in kept_rows], role='anchor')
positive_emb = get_embeddings(
emb_model, emb_template, [r['cot_compressed'] for r in kept_rows], role='positive')
sims = (anchor_emb * positive_emb).sum(axis=1).astype(np.float32)
to_insert: List[Dict[str, Any]] = []
for idx, (r, sim_val) in enumerate(zip(kept_rows, sims)):
tag = 'KEEP' if sim_val >= SIM_THRESHOLD else 'DROP'
print(f'[{tag} sim={sim_val:.4f}] {r["source"][:24]} '
f'q={_short(r["query_raw"], 60)!r} '
f'cot={_short(r["cot_raw"], 60)!r}', flush=True)
if sim_val < SIM_THRESHOLD:
nonlocal_counters['n_dropped_sim'] += 1
_log_miss(misses_path, misses_lock, {
'id': r['id'], 'source': r['source'], 'reason': 'sim_low',
'sim': float(sim_val),
'query_raw': r['query_raw'],
'cot_raw': r['cot_raw'],
'query_compressed': r['query_compressed'],
'cot_compressed': r['cot_compressed'],
})
continue
to_insert.append({
'id': r['id'],
'vector': positive_emb[idx].tolist(),
'thinking_raw': r['cot_raw'],
'query_raw': r['query_raw'],
'cot_compressed': r['cot_compressed'],
'query_compressed': r['query_compressed'],
'source': r['source'],
'domain': DOMAIN_MAP.get(r['source'], 'mixed'),
'language': _detect_lang(r['cot_raw']),
'sim': float(sim_val),
})
if to_insert:
tbl.add(to_insert)
nonlocal_counters['n_kept'] += len(to_insert)
indexed.update(r['id'] for r in to_insert)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since _process_batch is submitted to a ThreadPoolExecutor with multiple workers, _embed_and_insert will be executed concurrently by multiple threads. LanceDB's Table.add is not thread-safe and concurrent writes can lead to database corruption or write conflicts. Additionally, mutating indexed (a Python set) and nonlocal_counters concurrently from multiple threads is not thread-safe. We should serialize the embedding, database insertion, and counter updates using a threading.Lock.

    write_lock = threading.Lock()

    def _embed_and_insert(kept_rows: List[Dict[str, Any]]) -> None:
        """Phase 2+3: embed compressed texts and insert into LanceDB."""
        if not kept_rows:
            return
        with write_lock:
            anchor_emb = get_embeddings(
                emb_model, emb_template, [r['query_compressed'] for r in kept_rows], role='anchor')
            positive_emb = get_embeddings(
                emb_model, emb_template, [r['cot_compressed'] for r in kept_rows], role='positive')
            sims = (anchor_emb * positive_emb).sum(axis=1).astype(np.float32)
            to_insert: List[Dict[str, Any]] = []
            for idx, (r, sim_val) in enumerate(zip(kept_rows, sims)):
                tag = 'KEEP' if sim_val >= SIM_THRESHOLD else 'DROP'
                print(f'[{tag} sim={sim_val:.4f}] {r["source"][:24]} '
                      f'q={_short(r["query_raw"], 60)!r} '
                      f'cot={_short(r["cot_raw"], 60)!r}', flush=True)
                if sim_val < SIM_THRESHOLD:
                    nonlocal_counters['n_dropped_sim'] += 1
                    _log_miss(misses_path, misses_lock, {
                        'id': r['id'], 'source': r['source'], 'reason': 'sim_low',
                        'sim': float(sim_val),
                        'query_raw': r['query_raw'],
                        'cot_raw': r['cot_raw'],
                        'query_compressed': r['query_compressed'],
                        'cot_compressed': r['cot_compressed'],
                    })
                    continue
                to_insert.append({
                    'id': r['id'],
                    'vector': positive_emb[idx].tolist(),
                    'thinking_raw': r['cot_raw'],
                    'query_raw': r['query_raw'],
                    'cot_compressed': r['cot_compressed'],
                    'query_compressed': r['query_compressed'],
                    'source': r['source'],
                    'domain': DOMAIN_MAP.get(r['source'], 'mixed'),
                    'language': _detect_lang(r['cot_raw']),
                    'sim': float(sim_val),
                })
            if to_insert:
                tbl.add(to_insert)
                nonlocal_counters['n_kept'] += len(to_insert)
                indexed.update(r['id'] for r in to_insert)

Comment on lines +570 to +576
if role == 'anchor':
feat = template.encode({'messages': _wrap_anchor(text)})
feat['labels'] = [1]
else:
feat = template.encode({'messages': _wrap_positive(text)})
feat['labels'] = [0]
features.append(feat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since template.encode can now return None (as updated in src/twinkle/template/base.py), executing feat['labels'] = ... without a None check will raise a TypeError. We must guard against None before mutating feat and only append valid features.

Suggested change
if role == 'anchor':
feat = template.encode({'messages': _wrap_anchor(text)})
feat['labels'] = [1]
else:
feat = template.encode({'messages': _wrap_positive(text)})
feat['labels'] = [0]
features.append(feat)
if role == 'anchor':
feat = template.encode({'messages': _wrap_anchor(text)})
if feat is not None:
feat['labels'] = [1]
else:
feat = template.encode({'messages': _wrap_positive(text)})
if feat is not None:
feat['labels'] = [0]
if feat is not None:
features.append(feat)

Comment on lines +907 to +910
problems: List[str] = None,
decontam_threshold: float = 0.0,
) -> List[List[Dict[str, str]]]:
thinking_field = 'thinking_raw' if use_thinking_raw else 'cot_compressed'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since template.encode can now return None (as updated in src/twinkle/template/base.py), executing feat['labels'] = [1] without a None check will raise a TypeError. We must guard against None before mutating feat and only append valid features.

    for t in padded:
        feat = template.encode({'messages': _wrap_anchor(t or ' ')})
        if feat is not None:
            feat['labels'] = [1]
            features.append(feat)

Comment on lines +52 to +55
for t in padded:
feat = template.encode({'messages': _wrap_anchor(t or ' ')})
feat['labels'] = [1]
features.append(feat)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since template.encode can now return None (as updated in src/twinkle/template/base.py), executing feat['labels'] = [1] without a None check will raise a TypeError. We must guard against None before mutating feat and only append valid features.

Suggested change
for t in padded:
feat = template.encode({'messages': _wrap_anchor(t or ' ')})
feat['labels'] = [1]
features.append(feat)
for t in padded:
feat = template.encode({'messages': _wrap_anchor(t or ' ')})
if feat is not None:
feat['labels'] = [1]
features.append(feat)

Comment on lines +393 to +396
features.append(feat)
out = model.forward_only(inputs=features, task='embedding', return_logits=True)
emb = out['embeddings']
if isinstance(emb, torch.Tensor):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since template.encode can now return None (as updated in src/twinkle/template/base.py), executing feat['labels'] = [1] without a None check will raise a TypeError. We must guard against None before mutating feat and only append valid features.

Suggested change
features.append(feat)
out = model.forward_only(inputs=features, task='embedding', return_logits=True)
emb = out['embeddings']
if isinstance(emb, torch.Tensor):
for t in padded:
feat = template.encode({'messages': _wrap_anchor(t or ' ')})
if feat is not None:
feat['labels'] = [1]
features.append(feat)

Comment on lines +221 to +222
# Pad to EMB_GPUS to avoid dispatch starvation.
pad_n = EMB_GPUS - 1

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Since template.encode can now return None (as updated in src/twinkle/template/base.py), executing feat['labels'] = [1] without a None check will raise a TypeError. We must guard against None before mutating feat and only append valid features.

Suggested change
# Pad to EMB_GPUS to avoid dispatch starvation.
pad_n = EMB_GPUS - 1
feat = template.encode({'messages': _wrap_anchor(text)})
if feat is not None:
feat['labels'] = [1]

Comment on lines +524 to +535
parts = body.split('\n\n', 1)
first_para = parts[0].strip()
rest = parts[1].strip() if len(parts) > 1 else ''
if len(first_para) < 256 or len(rest) < 256:
continue
query = f'{title}\n\n{first_para}' if title else first_para
out.append({
'id': _hash_id('cosmopedia', f'{title}\n{first_para[:200]}'),
'source': 'cosmopedia-v1',
'query': query,
'cot': rest,
'response': '',

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If a section in Cosmopedia consists of only a single long paragraph (or does not contain \n\n), rest will be empty (''). This causes len(rest) < 256 to evaluate to True, and the entire high-quality textbook section is silently discarded. We should handle single-paragraph sections gracefully by using the title as the query and the entire body as the cot when rest is empty or too short.

Suggested change
parts = body.split('\n\n', 1)
first_para = parts[0].strip()
rest = parts[1].strip() if len(parts) > 1 else ''
if len(first_para) < 256 or len(rest) < 256:
continue
query = f'{title}\n\n{first_para}' if title else first_para
out.append({
'id': _hash_id('cosmopedia', f'{title}\n{first_para[:200]}'),
'source': 'cosmopedia-v1',
'query': query,
'cot': rest,
'response': '',
parts = body.split('\n\n', 1)
first_para = parts[0].strip()
rest = parts[1].strip() if len(parts) > 1 else ''
if len(first_para) < 256 or (rest and len(rest) < 256):
continue
if not rest:
query = f'Explain {title}' if title else 'Explain the concept'
cot = body
else:
query = f'{title}\n\n{first_para}' if title else first_para
cot = rest
out.append({
'id': _hash_id('cosmopedia', f'{title}\n{first_para[:200]}'),
'source': 'cosmopedia-v1',
'query': query,
'cot': cot,
'response': '',
})

result = execute_method(func.__name__, _workers_and_args)
# This is a result future, call it to get the actual result
result_func = RayHelper.do_get_and_collect_func(_collect_func, collect, result, device_mesh)
_rgt = getattr(self, '_ray_get_timeout', None) or timeout

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

If self._ray_get_timeout is set to 0 or 0.0 (which is a valid timeout for non-blocking polling), 0 or timeout will evaluate to timeout because 0 is falsy in Python. We should explicitly check if _rgt is None to avoid overriding valid 0 or 0.0 timeouts.

                        _rgt = getattr(self, '_ray_get_timeout', None)
                        _rgt = _rgt if _rgt is not None else timeout

Comment on lines +1318 to +1321
from concurrent.futures import Future
prefetch_pool = ThreadPoolExecutor(max_workers=1)
# Prepare first batch synchronously
cur_result = _prepare_rag_batch(batch_starts[0])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is no try...finally block around the prefetch loop. If any exception occurs during generation or evaluation (e.g., CUDA out of memory, keyboard interrupt, or API error), the prefetch_pool will not be shut down, leaving background threads hanging. We should wrap the loop in a try...finally block to ensure prefetch_pool is always shut down.

Example:

        try:
            # Prepare first batch synchronously
            cur_result = _prepare_rag_batch(batch_starts[0])
            for bi, batch_start in enumerate(batch_starts):
                ...
        finally:
            prefetch_pool.shutdown(wait=True)

@tastelikefeet tastelikefeet merged commit 627869b into modelscope:dev Jul 9, 2026
0 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant