{"record":{"id":"f107127063b199d3","repo":"xai-org/x-algorithm","slug":"enable-candidate-tower-linear-proj-and-feature-pre","errorCode":null,"errorMessage":"enable_candidate_tower_linear_proj and feature_prep_enabled (candidate project-then-sum) are mutually exclusive; enable at most one (or neither for mean-pool on the candidate tower).","messagePattern":"enable_candidate_tower_linear_proj and feature_prep_enabled \\(candidate project-then-sum\\) are mutually exclusive; enable at most one \\(or neither for mean-pool on the candidate tower\\)\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"phoenix/xrex/configs/xrecsys_two_tower.py","lineNumber":517,"sourceCode":"    use_user_features = _has_user_features_token(mparams)\n\n    dataset = _make_dataset(mparams, dataset_type, hash_table, config_name)\n    evals = []\n    for _build_evals in config_registry.RETRIEVAL_EVAL_BUILDERS:\n        evals += _build_evals(mparams, dataset, _has_user_features_token)\n\n    raw_checkpoint_datasets = mparams.get(\"checkpoint_dataset_names\", None)\n    if isinstance(raw_checkpoint_datasets, str):\n        checkpoint_dataset_names = [\n            s.strip() for s in raw_checkpoint_datasets.split(\",\") if s.strip()\n        ]\n    else:\n        checkpoint_dataset_names = raw_checkpoint_datasets\n\n    if mparams.get(\"enable_candidate_tower_linear_proj\") and mparams.get(\n        \"feature_prep_enabled\", False\n    ):\n        raise ValueError(\n            \"enable_candidate_tower_linear_proj and feature_prep_enabled \"\n            \"(candidate project-then-sum) are mutually exclusive; enable at most one \"\n            \"(or neither for mean-pool on the candidate tower).\"\n        )\n\n    assert mparams[\"emb_size\"] % 128 == 0\n    assert mparams[\"emb_table_width\"] % 128 == 0\n    hl = mparams[\"history_seq_len\"]\n    use_user_embedding = mparams.get(\"use_user_embedding\", True)\n    scale_config = _default_recsys_scaling()\n    num_user_prefix_tokens = _num_user_prefix_tokens(mparams, use_user_embedding, scale_config)\n    total_seq = num_user_prefix_tokens + hl\n    use_seqpack = mparams.get(\"use_seqpack\", False)\n\n    if num_user_prefix_tokens > 1:\n        assert (total_seq & (total_seq - 1)) == 0, (\n            f\"total sequence length ({total_seq} = {num_user_prefix_tokens} + {hl}) must be a power of 2\"\n        )","sourceCodeStart":499,"sourceCodeEnd":535,"githubUrl":"https://github.com/xai-org/x-algorithm/blob/24c60942c5c5fdad3a6addffb4c6e6d2f228f04f/phoenix/xrex/configs/xrecsys_two_tower.py#L499-L535","documentation":"The two-tower xrecsys config validator rejects any ModelParams that enables both enable_candidate_tower_linear_proj and feature_prep_enabled. Both flags select a (mutually exclusive) reduction strategy for the candidate tower embeddings (linear projection vs project-then-sum feature prep), so enabling both makes the intended architecture ambiguous. The check deliberately runs before the emb_size % 128 assertion so misconfiguration fails fast at config-parse time rather than mid-training.","triggerScenarios":"Setting both mparams['enable_candidate_tower_linear_proj']=True and mparams['feature_prep_enabled']=True in the two-tower config (or via CLI overrides / YAML merge that turns on feature prep globally while the model flags linear proj).","commonSituations":"Copy-pasting a candidate-tower config that uses linear projection into a pipeline config where feature_prep_enabled is already on; flipping feature_prep_enabled for the query tower and forgetting it also affects the candidate tower; defaults changing between config versions so an old flag now conflicts.","solutions":["If you want a projection on the candidate tower, keep enable_candidate_tower_linear_proj=True and set feature_prep_enabled=False.","If you want project-then-sum feature prep, set feature_prep_enabled=True and remove/False enable_candidate_tower_linear_proj.","If you want neither (mean-pooling on the candidate tower), set both flags to False.","Audit your YAML/CLI override chain to find where feature_prep_enabled gets enabled globally and scope it correctly."],"exampleFix":"# before\nmparams = dict(\n    enable_candidate_tower_linear_proj=True,\n    feature_prep_enabled=True,\n)\n\n# after (project-then-sum prep on candidate tower)\nmparams = dict(\n    enable_candidate_tower_linear_proj=False,\n    feature_prep_enabled=True,\n)","handlingStrategy":"validation","validationCode":"def validate_two_tower_mparams(mparams):\n    if mparams.get(\"enable_candidate_tower_linear_proj\") and mparams.get(\n        \"feature_prep_enabled\", False\n    ):\n        raise ValueError(\n            \"pick one candidate-tower reduction: \"\n            \"enable_candidate_tower_linear_proj OR feature_prep_enabled (or neither)\"\n        )\n\nvalidate_two_tower_mparams(mparams)  # before building the trainer","typeGuard":"def has_valid_candidate_tower_flags(m) -> bool:\n    return not (m.get(\"enable_candidate_tower_linear_proj\") and m.get(\"feature_prep_enabled\", False))","tryCatchPattern":null,"preventionTips":["Add a unit test asserting the config combo you ship does not set both flags.","Document the three valid modes (linear proj / project-then-sum / mean-pool) next to the flags in the config template."],"tags":["config","two-tower","mutually-exclusive-flags","validation"],"backgroundTag":"mutually-exclusive-config-options","analyzedSha":"24c60942c5c5fdad3a6addffb4c6e6d2f228f04f","analyzedAt":"2026-08-28T11:40:14.686Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}