Skip to content

GLM CUDA: the latent split across ranks (#1313 step 2), with a working reference at TP=2 #1431

Open

Description

@wojo

Version: native main 8c36359d5. Model: GLM-5.3-Flash at TP > 1. Follow-up to #1313, whose step 2 (the latent split across ranks) was left as design text when the issue closed with step 1 (#1322).

What happens

TP does not split the latent today. Every rank keeps every row: 13,056 bytes a token on each rank, 12,288 of them latent (#1142). At TP=2, the pool holds the same tokens as one Spark's memory would, so two GPUs buy no extra context.

A working reference

The GLM Zig engine in Mia's recipe and the native DeepSeek port both run a split latent ("KV sharding", from my GLM Python patch 0118). It keeps a conversation's latent rows on one owner part. The pieces:

  • owner-computed decode;
  • a prompt chunk's history fetch for rows a part does not hold;
  • prompt borrowing (twins) at the first decode round;
  • carries under memory pressure;
  • each rank's own view in kept states and the disk tier.

Measured on two GB10s at TP=2, GLM:

  • the pool grows 1.51M → 3.13M tokens in the same memory (CONTEXT=1048576 PARALLEL=8, with a prompt cache and a spill tier);
  • replies identical to the unsharded server (39 of 39 cases; at 1M, 5 of 5 against the Python engine);
  • decode within a few percent.

Questions

#1313's design moves queries rather than rows, on kv_pool's pages with DeepSeek's page book and owner rule. Before writing it:

  1. Do you want it on kv_pool pages at any W (GLM-5.3-Flash CUDA at TP=2: --parallel 8 at a 1M window is refused (90.48 GiB of windows, 2.33 GiB left), though KV pages would serve it #1313's design), or on the DeepSeek kv/split.zig model?
  2. Would you take it in pieces, each off by default until the last, as GLM-5.3-Flash CUDA at TP=2: --parallel 8 at a 1M window is refused (90.48 GiB of windows, 2.33 GiB left), though KV pages would serve it #1313 question 4 asks?
    • the per-rank latent book and owner;
    • owner decode;
    • the history fetch;
    • borrowing and carries.
  3. How should it interact with the prompt cache's byte order and with GLM-5.3-Flash on CUDA keeps conversation states on disk, every rank's part (--learn-conversations) #1411's disk images? A sharded state's image holds each part's own rows.

By Robert Wojciechowski (wojo on GitHub, @rwojo on X)

Activity

  1. ashhart commented on Oct 27, 2026

    @ashhart
    Owner

    Thanks, @wojo, for picking up #1313's step 2 with a working reference behind it. (1) On kv_pool pages at any W, as #1313 designed: since #1322 they are GLM's default from two ranks, so take DeepSeek's page book and owner rule as the model rather than adding a second pool. (2) Yes, in those four pieces, each off by default and leaving today's bytes unchanged when off; once whole, the split gives replies equal to the unsplit server at TP=2 and TP=3, with the drafted, resumed and concurrent checks, and any new launch goes in the kernel contract. Turning it on by default is a later call, once it is measured faster. (3) Each rank keeps and writes only its own rows, with the world size and owner map recorded; a restore into a different layout is refused by name, and resumed equals fresh across a save and restore. Please stack it on #1411 so the image format is settled once. The per-rank latent book and owner is the first PR.

  2. wojo commented on Oct 28, 2026

    @wojo
    ContributorAuthor

    /atc claim

  3. wojo commented on Oct 28, 2026

    @wojo
    ContributorAuthor

    @ashhart Thanks for the answer above. Before I write the first PR, here is its shape and the calls that are yours.

    The per-rank latent book is designed against main (ea01ae189) plus #1411 (fbea020a2), so it stacks as you asked.
    PR 1's shape:

    • Book. kv_pool gains owner classes on DeepSeek's residue rule: page ids c, c + W, c + 2W, ... are class c,
      each with its own free stack. A lease takes all its ids from one class. One class is today's pool, id for id.
    • Owner. Rank 0 picks it at admission: the class with the most available pages, lowest rank on ties (GLM-5.3-Flash CUDA at TP=2: --parallel 8 at a 1M window is refused (90.48 GiB of windows, 2.33 GiB left), though KV pages would serve it #1313's
      rule). begin carries it in its unused depth word, and every other rank checks each id's residue
      (ResidueMismatch).
    • Layout. With TF_GLM_KV_SPLIT=1 a page's pooled keys and latent rows lie in two regions, each with its own
      stride (derived from the weights) and its own page table. No kernel or launch is new: the latent launches take the
      latent table and stride, the pooled-key launches the others. In PR 1 the latent table holds the same ids and every
      rank still keeps every row, so replies, kept states and images equal off's. PR 2 then changes only that table's
      contents and the region's size, together with owner decode.
    • Off is today's bytes: one class, one table, the same launches, wire headers, states and images.
    • Refused by name: at one rank, without pages, and when one class cannot hold a stream's window. At TP=2,
      --context 131072 --parallel 4 serves (514 pages a class, 257 a window); --context 1048576 --parallel 8 is
      refused until PR 2 (1,028 pages a class against 2,049, computed). A request longer than a class is dropped by name,
      never requeued.

    Questions (my recommendation first):

    1. Where the book lives. Owner classes inside core kv_pool (recommended): one arena and one id space, no second
      pool. The cost is a change to the pool the other families share, held by a pinned one-class run (same ids, same
      order). The alternative is W kv_pool pools inside GLM: core untouched, but two id spaces in GLM.
    2. What on means in PR 1. Servable, with every row still on every rank (recommended): the book, owner, wire and
      both tables run on GPUs before any row moves. The cost: in PR 1 on saves no memory and caps a conversation at
      one class, and its start-up line says so. The alternative refuses on at start until owner decode lands, so
      nothing of PR 1 runs on a GPU before PR 2.
    3. The owner rule. GLM-5.3-Flash CUDA at TP=2: --parallel 8 at a 1M window is refused (90.48 GiB of windows, 2.33 GiB left), though KV pages would serve it #1313's most available pages, lowest rank on ties (recommended), kept in one function. My
      reference used fewest live streams first, which evens the owners' decode work. I'd revisit it with PR 2's
      numbers.
    4. The MTP head's latent rows. Owner-only like the DSA layers (recommended): 6,912 bytes a token per rank at TP=2
      (1.89x the rows) and 4,864 at TP=3. Replicated: 7,424 (1.76x) and about 5,547, but the head's pass then needs no
      exchange. PR 1's layout follows the answer: the head's latent part goes in the latent region or the replicated one.
    5. GLM-5.3-Flash on CUDA keeps conversation states on disk, every rank's part (--learn-conversations) #1411's image header, before your GB10 run. Add world, layout and owner words to Words now
      (recommended): 48 to 72 bytes, the seal step 12 to 18 words, unsplit images written as layout 0, owner 0, and a
      restore whose words name another layout refused by name. PR 4 then only fills them, and the format is settled
      once. The cost is one more small commit on GLM-5.3-Flash on CUDA keeps conversation states on disk, every rank's part (--learn-conversations) #1411 (about 30 lines with tests) under the cells you have queued. The
      alternative leaves GLM-5.3-Flash on CUDA keeps conversation states on disk, every rank's part (--learn-conversations) #1411 as it is, and PR 4 gives split images a format 2, refused by name in both directions.
    6. Stacking. I'd open PR 1 on main once it is green, carrying GLM-5.3-Flash on CUDA keeps conversation states on disk, every rank's part (--learn-conversations) #1411's commits, with "review the last 6 commits"
      (recommended). Or I hold it until GLM-5.3-Flash on CUDA keeps conversation states on disk, every rank's part (--learn-conversations) #1411 lands. It is earlier review against a noisier diff.
    7. TP=3. Our two GB10s run TP=2 only. Could the three-GB10 ring behind glm-kv-pages-8e705835 run the TP=3
      cells (the paged check and served exactness with the switch on, about 45 minutes)? Until then W=3 is host tests.

    Unless you'd rather otherwise:

    • the switch is TF_GLM_KV_SPLIT (0/off, 1/on), after TF_DSV41_KV_SPLIT;
    • in PR 2 the owner also computes prompt chunks' heads, until the history fetch lands in PR 3;
    • from PR 2, kept states (the prompt cache, --learn-conversations) are refused by name with the switch on until
      PR 4, which takes borrowing, carries, kept states and images.

    What we can run at TP=2 on our two GB10s, drafts on (about two hours):

    • the kernel contract with the new two-region cases and their planted controls, and glm-index-check at the pooled
      stride;
    • glm-paged-check with the switch on, on both owners, pages out of order and NaN before use;
    • served exactness at --context 131072 --parallel 4 under TF_KV_POISON=1: concurrent = alone, drafted =
      "draft": false, resumed = fresh, and the replies against main's;
    • a prompt cancelled mid-fill, then the next request and each class's idle census;
    • a GLM-5.3-Flash on CUDA keeps conversation states on disk, every rank's part (--learn-conversations) #1411 state saved with the switch on and restored with it off;
    • each refusal on the real binary.

    TP=3 needs a third GPU we don't have.

    By Robert Wojciechowski (wojo on GitHub, @rwojo on X)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions