Skip to content

[Tokenizer] Qualify the multi-tokenizer args shm name with the PID namespace - #41362

Open
zidanehuang001 wants to merge 1 commit into
sgl-project:mainfrom
zidanehuang001:fix/multi-tokenizer-args-shm-pid-namespace
Open

zidanehuang001 wants to merge 1 commit into
sgl-project:mainfrom
zidanehuang001:fix/multi-tokenizer-args-shm-pid-namespace

Conversation

@zidanehuang001

@zidanehuang001 zidanehuang001 commented Sep 26, 2026 •

Copy link
Copy Markdown

Motivation

With --tokenizer-worker-num > 1, the main process writes (port_args, server_args, scheduler_info) to the shared memory segment multi_tokenizer_args_<main pid>, and every HTTP tokenizer worker (and Engine.attach_tokenizer_worker) reads it back by the parent's pid. A pid is unique only within its PID namespace. Containers that share /dev/shm (for example docker run --ipc=host, a common setting for NCCL and CUDA IPC) each run their server as pid 1, so two servers on one host both use /dev/shm/multi_tokenizer_args_1. write_to_shared_memory reuses an existing segment of that name, so the second server overwrites the first server's args. Whichever server's workers read after the other server's write start up with the other server's args.

Reproduced on a build from nightly-dev-20260912-a2077862: two servers (ports 30011 and 30012) in two containers on one host, both with --ipc host --tokenizer-worker-num 2, started a few seconds apart:

  • The HTTP workers of the server on 30011 read the other server's whole args tuple, including its port_args (IPC endpoints in the other container's /tmp). Their post-warm-up /freeze_gc went to http://127.0.0.1:30012.
  • Server 30011 never became healthy. Health check failed. Server couldn't get a response from detokenizer for last 20 seconds kept repeating until the test stopped it about 9 minutes later. /health returned 503 and /generate timed out. Server 30012 served normally.

Modifications

  • multi_tokenizer_args_shm_name(main_pid) in multi_tokenizer_mixin.py puts the PID namespace in the name: multi_tokenizer_args_<pid-namespace inode>_<pid>, where the inode comes from os.stat("/proc/self/ns/pid").st_ino. Where /proc is unavailable (e.g. macOS) the name stays multi_tokenizer_args_<pid>.
  • The writer (write_data_for_multi_tokenizer) and both readers (init_multi_tokenizer in http_server.py and Engine.attach_tokenizer_worker) use the helper.
  • Readers can still find the segment from a pid, so attach_tokenizer_worker(parent_pid) keeps working. A process that can address parent_pid is in the same PID namespace as that process, so it derives the same name. A random name handed to the workers through the environment would also fix the collision, but a process that is not a child of the server could not find that name.
  • The shm sweep in stale_shm_cleanup.py needs no change. _creator_pid still reads the pid from the last _ field, and a test now covers the new name format.

Accuracy Tests

No change to model outputs. The only change is where the tokenizer workers read their startup args.

  • New unit tests in test/registered/unit/managers/test_multi_tokenizer_mixin.py:
    • The same pid in two PID namespaces gets two names, and each server reads back its own args.
    • Without /proc, the name falls back to the pid-only form.
  • test_stale_shm_cleanup.py checks that _creator_pid parses the new name. Both files pass (9 tests and 8 tests; test_orphan_family_sweep is skipped because it is CI-only).
  • End to end, with the same two-container setup and this change applied to the build above: both servers passed /health and served /generate. The segments were multi_tokenizer_args_4026543358_1 and multi_tokenizer_args_4026543383_1, matching the PID-namespace inodes of the two containers. Each server logged two Health check failed warnings before its first request, the same as the healthy server in the unpatched run.
  • That build has no Engine.attach_tokenizer_worker, so test_engine_multi_tokenizer.py was not run here. That test attaches from a spawned child, which is in the parent's PID namespace, so it derives the same name.

Speed Tests and Profiling

Not applicable. The only extra work is one stat call at startup per process.

Related

Checklist

🤖 Generated with Claude Code


CI States

Latest PR Test (Base): ❌ Run #36261501594
Latest PR Test (Extra): ❌ Run #36261501322
Latest PR Test (AMD ROCm 10): ❌ Run #36261501492

…mespace

The main process writes the multi-tokenizer args to
multi_tokenizer_args_<pid>, and the tokenizer workers and
Engine.attach_tokenizer_worker read them back by pid. A pid is unique only
within its PID namespace: containers that share /dev/shm (e.g. docker
--ipc=host) each run their server as pid 1, so two servers on one host write
the same segment, and one server's workers start with the other server's
args. That server never passes its health check.

Put the PID namespace (inode of /proc/self/ns/pid) in the name. Readers
address the pid from the same namespace, so attaching by pid keeps working;
without /proc the name keeps the pid-only form.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
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