# Default replaysim normalization recipe. # # This is a data-juicer YAML config (https://github.com/modelscope/data-juicer). # It runs CPU-only ops that filter and clean DPO pairs produced by # composer_replication.teacher_replay.extract_dpo_pairs. # # The op-graph operates on records of shape: # # { # "state_id": "...", # "messages": [{"role": "user", "content": "..."}], # context # # --- flat-string shape (consumed by length/word/special-char/dedup filters) --- # "chosen": "the chosen response as a plain string", # "rejected": "the rejected response as a plain string", # # --- chat-messages shape (preserved for chat-aware ops + round-trip) --- # "chosen_messages": [{"role": "assistant", "content": "..."}], # "rejected_messages": [{"role": "assistant", "content": "..."}], # "n_teachers_agreeing": 2 # } # # IMPORTANT — field-key contract: # data-juicer's `text_length_filter`, `words_num_filter`, # `special_characters_filter` and `document_deduplicator` all read a SINGLE # string field named by `text_key` (singular). They expect plain strings. # Pointing them at a list-of-dicts (the chat-messages shape) crashes or # silently no-ops. We therefore keep two parallel representations: # * `chosen` / `rejected` — plain strings, fed to filter ops below. # * `chosen_messages` / `rejected_messages` — chat-messages list, preserved # untouched for downstream chat-aware consumers and the round-trip. # # data-juicer caveat: each filter op accepts only ONE `text_key`. To filter # both `chosen` AND `rejected`, we duplicate each op — once with # `text_key: chosen`, once with `text_key: rejected`. The top-level # `text_keys: chosen` below also satisfies data-juicer's dataset-load # validation (the formatter checks the global text_key exists in the dataset). # # Ops listed in `process` are applied in order. Each op operates on the # full record but reads/writes one field. # Project & I/O are filled in by DJNormalizer at runtime; we only # specify the op pipeline here. # --- Global text-key contract (see header note) ----------------------- # data-juicer validates this exists on the dataset before any op runs, and # uses it as the default text_key for ops that don't specify their own. text_keys: chosen # --- Op pipeline (applied in order) ----------------------------------- process: # 1. Length filter on the assistant response. # Drops pairs where either the chosen or rejected response is shorter # than 8 chars or longer than 32k chars (likely garbled / overflow). - text_length_filter: min_len: 8 max_len: 32000 text_key: chosen - text_length_filter: min_len: 8 max_len: 32000 text_key: rejected # 2. Word-count filter on response. # Drops pairs with absurdly low (< 2 words) or high (> 4096 words) # response counts. - words_num_filter: min_num: 2 max_num: 4096 text_key: chosen - words_num_filter: min_num: 2 max_num: 4096 text_key: rejected # 3. Special-character filter. # Drops responses where >50% of characters are non-alphabetic # special chars (likely encoding errors or junk). - special_characters_filter: max_ratio: 0.5 text_key: chosen - special_characters_filter: max_ratio: 0.5 text_key: rejected # 4. Per-conversation deduplication. # Within the batch, drop records where the `chosen` field is a # duplicate of another record's `chosen`. (data-juicer's # document_deduplicator is per-batch hashing — full-corpus dedup is # a separate op family.) - document_deduplicator: lowercase: true ignore_non_character: true text_key: chosen # Notes: # - We DO NOT run `pair_preference_mapper` because its default config may # re-synthesize the rejected text via an LLM call — we already have # real disagreement-derived rejected text and don't want to pay another # API call. (See ADR-004 § "One-day spike before merge.") # - Language detection is intentionally not in the default — it requires # downloading a fasttext model and adds startup latency. Add the # `language_id_score_filter` op to a custom recipe if needed. # - Semantic-similarity dedup is GPU-bound (NeMo-Curator ops); not in # the default.