How to migrate asymmetric Sentence Transformers models to Router

Asymmetric Sentence Transformers models keep query text and document text on separate processing paths so retrieval code can tune each side for its role. Older projects may still wrap those paths with Asym, but current Sentence Transformers uses Router to select the query or document route during encoding.

The migration keeps the existing query and document module stacks and changes the dispatch layer around them. Router.for_query_document() names the two routes, while encode_query() and encode_document() pass the matching task value to the router at inference time.

Run the change in a branch or scratch copy when the old model was trained with different query and document towers. A smoke test should prove that both routes exist and that query and document embeddings keep the dimension expected by the retrieval index.

Steps to migrate an asymmetric Sentence Transformers model to Router:

  1. Locate the old Asym construction in the model-loading code.
    from sentence_transformers import SentenceTransformer
    from sentence_transformers.models import Asym
     
    model = SentenceTransformer(
        modules=[
            Asym(
                {
                    "query": query_modules,
                    "document": document_modules,
                }
            )
        ]
    )

    Keep the actual query_modules and document_modules lists from the existing model; those modules are the state being migrated.

  2. Replace the deprecated Asym import with Router and any shared post-processing modules.
    from sentence_transformers import SentenceTransformer
    from sentence_transformers.sentence_transformer.modules import Normalize, Router

    sentence_transformers.models remains a compatibility import in recent releases, but it raises deprecation warnings. Use the current module import path in migrated code.

  3. Wrap the existing query and document stacks with Router.for_query_document().
    router = Router.for_query_document(
        query_modules=query_modules,
        document_modules=document_modules,
    )
    model = SentenceTransformer(modules=[router, Normalize()])

    Only keep Normalize() after the router when the old pipeline applied normalization after both routes. Route-specific modules should stay inside their matching list.

  4. Change routed inference calls to the query and document encoding methods.
    query_embedding = model.encode_query(["How do I migrate Asym routes?"])
    document_embeddings = model.encode_document(
        [
            "Use Router.for_query_document and encode_query for retrieval queries.",
            "Set the cache directory before loading a local model.",
        ]
    )

    encode_query() selects the query route, and encode_document() selects the document route. Avoid passing {“query”: …} or {“document”: …} dictionaries to encode() after the migration.

  5. Create a small route smoke test beside the migrated code.
    router_migration_check.py
    from sentence_transformers import SentenceTransformer
    from sentence_transformers.sentence_transformer.modules import Normalize, Router
     
    query_embedder = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
    document_embedder = SentenceTransformer("sentence-transformers/all-MiniLM-L6-v2")
     
    router = Router.for_query_document(
        query_modules=list(query_embedder.children()),
        document_modules=list(document_embedder.children()),
    )
    model = SentenceTransformer(modules=[router, Normalize()])
     
    query_embedding = model.encode_query(["How do I migrate Asym routes?"])
    document_embeddings = model.encode_document(
        [
            "Use Router.for_query_document and encode_query for retrieval queries.",
            "Set the cache directory before loading a local model.",
        ]
    )
     
    print("Router routes:", ", ".join(sorted(model[0].sub_modules)))
    print("Query embedding shape:", query_embedding.shape)
    print("Document embedding shape:", document_embeddings.shape)

    The smoke test uses two copies of one public model so the route API can be checked quickly. Replace those module lists with the real query and document towers when testing a production migration.

  6. Run the smoke test and confirm both route names and embedding shapes.
    $ python router_migration_check.py
    Router routes: document, query
    Query embedding shape: (1, 384)
    Document embedding shape: (2, 384)
  7. Remove the temporary smoke-test file after the migrated application test passes.
    $ rm router_migration_check.py

    Keep a project test that exercises encode_query() and encode_document() if the model is part of a retrieval service or scheduled index build.