<?xml version='1.0' encoding='UTF-8'?>
<rfc version="3" docName="draft-li-rdma-kvcache-transfer-optimization-00" category="std" consensus="true" submissionType="IETF" ipr="trust200902" tocInclude="true" tocDepth="3" sortRefs="true" symRefs="true" xml:lang="en">

  <front>
    <title abbrev="KV Cache Block Transfer over RDMA">Key-Value (KV) Cache Block Transfer Protocol (KBTP) over RDMA: Block Load, Mapped Store, and Unmapped Store</title>
    <seriesInfo name="Internet-Draft" value="draft-li-rdma-kvcache-transfer-optimization-00"/>

    <author fullname="Zhiqiang Li" initials="Z." surname="Li">
      <organization>China Mobile</organization>
      <address>
        <postal><city>Beijing</city><code>100053</code>
        <country>China</country></postal>
        <email>lizhiqiangyjy@chinamobile.com</email>
      </address>
    </author>
    <author fullname="Zongpeng Du" initials="Z." surname="Du">
      <organization>China Mobile</organization>
      <address>
        <postal><city>Beijing</city><code>100053</code>
        <country>China</country></postal>
        <email>duzongpeng@chinamobile.com</email>
      </address>
    </author>
    <author fullname="Junjie Wang" initials="J." surname="Wang">
      <organization>Centec</organization>
      <address>
        <postal><city>Shanghai</city><code>201203</code>
        <country>China</country></postal>
        <email>wangjj@centec.com</email>
      </address>
    </author>
    <author fullname="Wei Cheng" initials="W." surname="Cheng">
      <organization>Centec</organization>
      <address>
        <postal><city>Shanghai</city><code>201203</code>
        <country>China</country></postal>
        <email>chengw@centec.com</email>
      </address>
    </author>
    <author fullname="Ying Zhao" initials="Y." surname="Zhao">
      <organization>Centec</organization>
      <address>
        <postal><city>Shanghai</city><code>200000</code>
        <country>China</country></postal>
        <email>zhaoying@centec.com</email>
      </address>
    </author>
    <author fullname="Chunhao Zhao" initials="C." surname="Zhao">
      <organization>SAIA</organization>
      <address>
        <postal><city>Shanghai</city><code>200125</code>
        <country>China</country></postal>
        <email>chunhao.zhao@sh-aia.com</email>
      </address>
    </author>
    <author fullname="Xun Sun" initials="X." surname="Sun">
      <organization>Inesa</organization>
      <address>
        <postal><city>Shanghai</city><code>200030</code>
        <country>China</country></postal>
        <email>sunxun@inesa.com</email>
      </address>
    </author>

    <date year="2026" month="November" day="2"/>
    <area>Web and Internet Transport</area>
    <keyword>RDMA</keyword>
    <keyword>RDMAP</keyword>
    <keyword>KV cache</keyword>
    <keyword>remote memory</keyword>
    <keyword>atomic fetch-and-add</keyword>
    <keyword>offload</keyword>

    <abstract>
      <t>Inference systems move key-value (KV) cache blocks between instances
      and cache tiers over RDMA.  Existing practice forces a choice between
      statically registered remote memory, which is fast but wastes memory,
      and per-transfer remote procedure calls, which use memory well but
      involve the target's CPU in every transfer.  This document specifies
      the KV Cache Block Transfer Protocol (KBTP), an upper-layer protocol
      over RDMA with three operations: Block Load, which places a stored block
      into the initiator's memory; Block Mapped Store, which writes a block
      into an already allocated block; and Block Unmapped Store, which
      allocates a free block at the target and writes it in one operation.
      KBTP defines the request and completion messages, a block table, and a
      free-block ring whose counters use masked atomic fetch-and-add.  These
      structures are designed so that a target can execute loads and stores
      within its RDMA network interface, and allocations as well where the
      interface supports conditional execution, without involving its CPU
      per operation, while remaining interoperable with targets that
      process requests in software.  KBTP is bound to RDMAP with the RDMA
      protocol extensions, and can also be used over InfiniBand and
      RoCEv2.</t>
    </abstract>
  </front>

  <middle>

    <section anchor="intro">
      <name>Introduction</name>

      <section anchor="background">
        <name>Background</name>
        <t>The KV cache that attention layers keep for processed tokens is
        reused across decoding steps and, increasingly, across requests.
        Longer context windows make it larger than accelerator memory can
        hold, so serving systems store it in several tiers and move it
        between prefill instances, decode instances, and cache servers
        <xref target="PAGEDATTENTION"/> <xref target="MOONCAKE"/>.  Two
        organizations are common: a central KV store that inference instances
        write to and read from, and peer-to-peer transfer in which, for
        example, a prefill instance writes directly to the decode instance
        that will use the cache.  In both, the data moves in fixed-size blocks
        (typically holding the cache of 16 to 512 tokens, from kilobytes to
        megabytes) over RDMA.</t>
      </section>

      <section anchor="problem">
        <name>Problem Statement</name>
        <t>Three RDMA usage patterns are common today:</t>
        <ol>
          <li>One-sided access to statically allocated memory: the target
          registers a large region in advance (often gigabytes) and the source
          writes or reads it directly.  Access is fast, but the coarse static
          allocation leaves much memory unused.</li>
          <li>One-sided access to dynamically allocated memory: the source asks
          the target, with a two-sided remote procedure call, to allocate a
          fine-grained region (for example 4 KiB to 1 MiB), then accesses it
          one-sidedly.  Memory is used well, but every allocation involves the
          target CPU, which limits throughput and adds latency.</li>
          <li>Two-sided transfer: the source sends the data in messages and the
          target CPU allocates memory and copies or registers it.  Memory is
          used well, but every transfer involves the target CPU.</li>
        </ol>
        <table anchor="tbl-compare">
          <name>Comparison of Approaches</name>
          <thead>
            <tr><th>Approach</th><th>Memory utilization</th><th>Target CPU per operation</th><th>Operation cost</th></tr>
          </thead>
          <tbody>
            <tr><td>One-sided, static</td><td>low</td><td>no</td><td>low</td></tr>
            <tr><td>One-sided, dynamic</td><td>medium</td><td>yes (allocation)</td><td>medium</td></tr>
            <tr><td>Two-sided</td><td>high</td><td>yes</td><td>high</td></tr>
            <tr><td>Target-executed block operations (this document)</td><td>high (fine-grained blocks)</td><td>not required per operation</td><td>one round trip per operation</td></tr>
          </tbody>
        </table>
        <t>No interoperable protocol provides fine-grained allocation and
        one-round-trip block access without per-operation involvement of the
        target CPU.  Since inference instances and cache servers are built by
        different implementers, the message formats and the structure of
        target memory must be specified, not only the RDMA operations.</t>
      </section>

      <section anchor="overview">
        <name>Overview</name>
        <t>A KBTP target manages a pool of equally sized blocks.  It keeps a
        block table that maps block handles to blocks, and a free-block ring
        that lists unallocated blocks.  An initiator sends one request message
        per operation; the target answers with a completion.  The operations
        are:</t>
        <dl newline="false">
          <dt>Block Load:</dt><dd>the target looks up the handle and writes the
          block into a buffer named by the initiator.</dd>
          <dt>Block Mapped Store:</dt><dd>the target looks up the handle and
          writes data supplied by the initiator into the block.</dd>
          <dt>Block Unmapped Store:</dt><dd>the target takes a free block from
          the ring, binds it to the handle, and writes the data into it.</dd>
          <dt>Block Free:</dt><dd>the target unbinds the handle and returns the
          block to the ring.</dd>
        </dl>
        <t>Each operation needs a fixed, short sequence of RDMA operations at
        the target whose addresses come from the request, the table, or the
        ring.  The formats in this document are designed so that the target
        can pre-post these sequences and let its RDMA interface execute them
        when a request arrives (<xref target="offload"/>).  A target that
        processes requests in software is equally conformant; the initiator
        cannot tell the difference.</t>
        <t>Optional direct modes let initiators that the target trusts read
        blocks and allocate them with one-sided operations only
        (<xref target="direct"/>).</t>
      </section>

      <section anchor="scope">
        <name>Scope and Relationship to Other Work</name>
        <t>KBTP defines an upper-layer protocol in the sense of RDMAP
        <xref target="RFC5040"/>, like the RDMA transport for RPC
        <xref target="RFC8166"/> and iSER <xref target="RFC7145"/>.  It is
        bound normatively to RDMAP over DDP <xref target="RFC5041"/> and MPA
        <xref target="RFC5044"/> <xref target="RFC6581"/>, with the atomic and
        immediate data operations of <xref target="RFC7306"/>.  A mapping to
        InfiniBand reliable connections and RoCEv2 <xref target="IBTA"/> is
        given in <xref target="ib"/>.  KBTP does not define new RDMA
        operations.  <xref target="I-D.talpey-rdma-commit"/> specifies RDMA
        extensions for remote persistence and visibility, which KBTP targets
        can use for persistent tiers.  Block keys and cache indexing, which
        tell an initiator which handle holds a given block, are provided by
        <xref target="I-D.li-kvcache-efficient-storage"/> or by local
        means.</t>
        <t>Typical uses are: storing the KV cache of external knowledge
        (retrieval-augmented generation documents) once and loading it into
        any instance; keeping the cache of very long contexts in a shared
        pool and reusing it across requests; and caches embedded in network
        devices of computing power networks.</t>
      </section>
    </section>

    <section anchor="conventions">
      <name>Conventions and Definitions</name>
      <t>The key words "<bcp14>MUST</bcp14>", "<bcp14>MUST NOT</bcp14>",
      "<bcp14>REQUIRED</bcp14>", "<bcp14>SHALL</bcp14>", "<bcp14>SHALL
      NOT</bcp14>", "<bcp14>SHOULD</bcp14>", "<bcp14>SHOULD NOT</bcp14>",
      "<bcp14>RECOMMENDED</bcp14>", "<bcp14>NOT RECOMMENDED</bcp14>",
      "<bcp14>MAY</bcp14>", and "<bcp14>OPTIONAL</bcp14>" in this document
      are to be interpreted as described in BCP 14
      <xref target="RFC2119"/> <xref target="RFC8174"/> when, and only
      when, they appear in all capitals, as shown here.</t>
      <t>This document uses the RDMAP terms Steering Tag (STag), Tagged
      Offset, Send, RDMA Write, RDMA Read, and Remote Peer from
      <xref target="RFC5040"/>, and FetchAdd from <xref target="RFC7306"/>.
      In addition:</t>
      <dl newline="false" spacing="normal">
        <dt>Initiator:</dt><dd>The endpoint that issues KBTP requests.</dd>
        <dt>Target:</dt><dd>The endpoint that owns the block pool.</dd>
        <dt>Block:</dt><dd>A fixed-size region of the target's block pool,
        identified by its Block Offset, the Tagged Offset of its first octet
        within the Pool STag.</dd>
        <dt>Block Handle:</dt><dd>A 64-bit name, chosen by an initiator within
        the handle range assigned to it, that is bound to a block by an
        Unmapped Store.</dd>
        <dt>Generation:</dt><dd>A 16-bit counter of a table entry that changes
        whenever the handle is bound or unbound, so that stale references
        are detected.</dd>
        <dt>Sealed block:</dt><dd>A block whose contents will not change until
        it is freed.</dd>
      </dl>
      <t>All fields in messages and in memory structures read by remote
      peers are in network byte order, except the address fields listed in
      <xref target="session"/> when the target selects little-endian
      address fields.</t>
    </section>

    <section anchor="memory">
      <name>Target Memory Structures</name>

      <section anchor="pool">
        <name>Block Pool</name>
        <t>The target allocates F blocks of Block Size octets each within a
        memory region named by the Pool STag.  Each block is identified by its
        Block Offset.  Tables and ring slots store Block Offsets rather than
        block numbers, so that a value read from them can be used directly as
        the address of an RDMA operation without arithmetic.</t>
      </section>

      <section anchor="table">
        <name>Block Table</name>
        <t>The block table has one 16-octet entry per handle that the target
        serves.  The entry for handle h is at Table Offset + 16 * (h - Table
        Base Handle) within the Table STag.</t>
        <figure anchor="fig-entry">
          <name>Block Table Entry</name>
          <artwork type="ascii-art">
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|             Flags             |          Generation           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          Data Length                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Block Offset (64 bits)                    |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
</artwork>
        </figure>
        <t>Flags: 0x0001 VALID (the handle is bound), 0x0002 SEALED.  Data
        Length is the number of valid octets in the block.  Generation never
        takes the value zero.  An entry is written only by the target; when
        it binds an entry, the target writes Data Length and Block Offset
        before Flags and Generation.</t>
      </section>

      <section anchor="ring">
        <name>Free-Block Ring</name>
        <t>The free-block ring has N slots, where N = 2^n is a power of two not
        smaller than F, and two 64-bit counters, Head (used to allocate) and
        Tail (used to free), each aligned to 8 octets.  Each slot is 16
        octets:</t>
        <figure anchor="fig-slot">
          <name>Ring Slot</name>
          <artwork type="ascii-art">
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Sequence (Ticket)                       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           Reserved                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Block Offset (64 bits)                    |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
</artwork>
        </figure>
        <t>A slot whose Block Offset is all ones is empty (consumed).  Each
        counter holds two fields that advance together: a 32-bit Ticket in
        bits 63-32 and the Slot Offset, the byte offset of the current slot
        (16 times the slot number), in the low n+4 bits.  Bits 31 to n+4 are
        zero.  Counters are modified only with FetchAdd
        <xref target="RFC7306" section="5.1.1"/> using:</t>
        <artwork>
   Add Data = 0x0000000100000010
   Add Mask = bit 63 | bit 31 | bit (n+3)
</artwork>
        <t>The Add Mask makes each field add independently and discard its
        carry, so one FetchAdd returns the current Ticket and Slot Offset and
        advances the Ticket by one and the Slot Offset by one slot, wrapping
        at the end of the ring.  The returned Slot Offset can be used as an
        address offset without further arithmetic, which allows targets to
        execute the allocation in their RDMA interface.  Counters are in the
        target's memory byte order, as RFC 7306 specifies for atomic
        operations; they are never read with RDMA Read.</t>
        <t>The rules are:</t>
        <ul>
          <li>Initialization: for s &lt; F the target writes slot s = {Sequence
          s, Block Offset of the s-th free block}; for F &lt;= s &lt; N it writes
          {Sequence s - N (modulo 2^32), all ones}.  It sets Head to Ticket 0
          and Slot Offset 0, and Tail to Ticket F and Slot Offset 16 * (F mod
          N).</li>
          <li>Allocation: FetchAdd on Head returns ticket t and offset o.  The
          slot at o holds the block for this allocation once its Sequence
          equals t and its Block Offset is not all ones; until then the
          allocator re-reads the slot.  Having taken the Block Offset, the
          slot is marked consumed by setting its Block Offset to all ones:
          by the target itself for target-executed allocations, and by the
          target when it processes BIND for direct allocations
          (<xref target="direct"/>).</li>
          <li>Free: the target first updates the table entry
          (<xref target="free"/>), then performs FetchAdd on Tail, obtaining
          ticket u and offset o.  If the slot at o is consumed, the target
          writes the Block Offset and then Sequence u.  If it is not yet
          consumed, because the allocation that owns it has taken its ticket
          but not yet read it, the target <bcp14>MUST</bcp14> defer the write until the slot
          is consumed; it <bcp14>MUST NOT</bcp14> overwrite the slot.</li>
        </ul>
        <t>With these rules no block is ever lost or handed out twice: each
        slot is written only after its previous content has been consumed,
        and each allocation reads exactly the slot written for its ticket.
        Writing the Block Offset before the Sequence ensures that a reader
        that observes the new Sequence also observes the new Block
        Offset.</t>
      </section>

      <section anchor="quota">
        <name>Allocation Quotas</name>
        <t>The target assigns each initiator an Allocation Quota at session
        setup and keeps the sum of quotas not larger than F.  An initiator's
        usage is the number of handles it has bound plus the number of
        tickets it has taken and not yet bound.  An initiator <bcp14>MUST NOT</bcp14> exceed
        its quota; the target releases one unit of usage when a FREE
        completes or when an abandoned ticket is reclaimed.  When all
        initiators respect their quotas, the ring always holds a block for
        every ticket taken, so an allocation waits at most for an in-progress
        free to finish writing its slot.</t>
        <t>Target-executed allocations take blocks from a ring shared by all
        initiators that the target serves this way.  For DIRECT_ALLOC
        (<xref target="direct"/>), the target instead gives each initiator its
        own ring and Head counter over a partition of the pool whose size
        equals the initiator's quota, so that every ticket on that ring
        belongs to that initiator and the quota is enforced by the ring
        itself.  A ticket that is not bound by BIND within the Ticket Timeout
        advertised in HELLO_ACK is abandoned.  Before reclaiming its block,
        the target <bcp14>MUST</bcp14> ensure that no RDMA Write of the initiator can still
        reach the block, by invalidating the initiator's Partition STag
        (<xref target="session"/>), which disables DIRECT_ALLOC for the rest
        of the session, or by terminating the connection.  It then consumes
        the slot, returns the block to the ring with a new ticket, and
        rejects a later BIND for that ticket with status TICKET_ABANDONED.
        If a target observes an allocation by an initiator that exceeds its
        quota, it completes the request with NO_SPACE and <bcp14>MAY</bcp14> terminate the
        session.</t>
      </section>
    </section>

    <section anchor="session">
      <name>Session Establishment</name>
      <t>After the RDMA connection is established, the initiator sends HELLO
      and the target answers with HELLO_ACK, both as Send messages and both
      starting with the common header (<xref target="common"/>) in which
      Request ID, Block Handle, Generation, and Status are zero (Status
      reports an error in HELLO_ACK).</t>
      <figure anchor="fig-hello">
        <name>HELLO Body</name>
        <artwork type="ascii-art">
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|      Requested Features       |      Completion Credits       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                   Initiator ID (128 bits)                     |
|                                                               |
|                                                               |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
</artwork>
      </figure>
      <t>Completion Credits is the number of receive buffers the initiator
      has posted for COMPLETION messages.  The Initiator ID is a stable
      random identifier of the initiator; a target uses it to restore the
      initiator's handle range and bindings after a reconnection.  Because it
      gives access to those bindings, the Initiator ID <bcp14>MUST</bcp14> be generated with
      at least 128 bits of entropy and kept secret, <bcp14>MUST</bcp14> be sent only on
      connections authenticated as described in <xref target="security"/>,
      and a target <bcp14>MUST</bcp14> reject a HELLO whose Initiator ID belongs to a
      session that is still active.</t>
      <figure anchor="fig-hello-ack">
        <name>HELLO_ACK Body (Target Descriptor)</name>
        <artwork type="ascii-art">
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Features            |        Request Credits        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          Block Size                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                          Max Inline                           |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Allocation Quota                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Handle Base (64 bits)                     |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                         Handle Count                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Ticket Timeout (ms)                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Direct-mode descriptors (present per Features, 16 each) ...  |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
</artwork>
      </figure>
      <t>Features: 0x0001 DIRECT_LOAD, 0x0002 DIRECT_ALLOC, 0x0004
      RECORD_COMPLETION, 0x0008 ADDR_LE.  Each direct-mode descriptor is 16
      octets: STag (32 bits), Tagged Offset (64 bits), and Count (32 bits).
      If DIRECT_LOAD is set, a Table descriptor (Count = number of entries)
      follows, then a Table Base Handle (64 bits) and 8 reserved octets, and
      then a Pool descriptor (Count = F) that grants read access only.  If
      DIRECT_ALLOC is set, a Partition descriptor follows, naming the part of
      the pool reserved for this initiator (Count = its quota) through an
      STag that grants write access, is specific to this connection (for
      example a memory window bound to it), and covers nothing else; then a
      Ring descriptor (Count = N) and a Head descriptor (Count = 1; the
      Tagged Offset <bcp14>MUST</bcp14> be a multiple of 8, as required for FetchAdd) of the
      ring dedicated to this initiator (<xref target="quota"/>).</t>
      <t>Handles of different initiators never overlap, so each initiator can
      choose handles for Unmapped Store without coordination.  Any initiator
      <bcp14>MAY</bcp14> load any handle the target authorizes it to read; handles are
      exchanged among initiators by means outside this protocol.</t>
      <t>If ADDR_LE is set, the initiator <bcp14>MUST</bcp14> encode the Block Handle and the
      STag, Tagged Offset, and Length fields of the Data and Completion
      Descriptors in little-endian byte order, and the Block Offset fields of
      table entries and ring slots are in little-endian byte order.  A
      target sets ADDR_LE when its RDMA interface uses little-endian work
      request fields and it wants to place these fields into pre-posted work
      requests unchanged (<xref target="offload"/>).</t>
      <t>GOODBYE ends the session.  With flag 0x0001 RELEASE, the target frees
      all handles bound by the initiator.  Otherwise, and when a connection
      fails, the target keeps the initiator's bindings for a retention time
      of its choosing; an initiator that reconnects with the same Initiator
      ID within that time receives the same handle range and finds its
      bindings intact.</t>
    </section>

    <section anchor="messages">
      <name>Messages</name>

      <section anchor="common">
        <name>Common Header</name>
        <figure anchor="fig-common">
          <name>KBTP Common Header (24 Octets)</name>
          <artwork type="ascii-art">
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|    Version    |    Opcode     |             Flags             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     Request ID (64 bits)                      |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Block Handle (64 bits)                     |
|                                                               |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          Generation           |            Status             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
</artwork>
        </figure>
        <t>Version is 1.  Opcodes are listed in <xref target="tbl-ops"/>.
        Request ID is chosen by the initiator and echoed in the completion.
        Generation in a request is the generation the initiator expects (zero
        means "any"); in a completion it is the entry's current generation.
        Status is zero in requests.  Flags are interpreted per opcode; for
        LOAD, STORE, ALLOC_STORE, and FREE they are:</t>
        <ul>
          <li>0x0001 INLINE: data follows the request.</li>
          <li>0x0002 SEAL: seal the block after the store.</li>
          <li>0x0004 RECORD: complete with a completion record
          (<xref target="completion"/>); a Completion Descriptor is
          present.</li>
          <li>0x0008 INVALIDATE: the target completes with Send with
          Invalidate <xref target="RFC5040"/>, invalidating the STag of the
          initiator's Data Descriptor.</li>
          <li>0x0010 ALLOW_UNSEALED: a load may return an unsealed block.</li>
        </ul>
        <table anchor="tbl-ops">
          <name>KBTP Opcodes</name>
          <thead><tr><th>Opcode</th><th>Name</th><th>Body after the header</th></tr></thead>
          <tbody>
            <tr><td>0x01</td><td>HELLO</td><td><xref target="fig-hello"/></td></tr>
            <tr><td>0x02</td><td>HELLO_ACK</td><td><xref target="fig-hello-ack"/></td></tr>
            <tr><td>0x10</td><td>LOAD</td><td>Data Descriptor [, Completion Descriptor]</td></tr>
            <tr><td>0x11</td><td>STORE (mapped)</td><td>Data Descriptor [, Completion Descriptor] [, inline data]</td></tr>
            <tr><td>0x12</td><td>ALLOC_STORE (unmapped)</td><td>Data Descriptor [, Completion Descriptor] [, inline data]</td></tr>
            <tr><td>0x13</td><td>FREE</td><td>[Completion Descriptor]</td></tr>
            <tr><td>0x14</td><td>BIND</td><td>Ticket (32), Data Length (32), Block Offset (64)</td></tr>
            <tr><td>0x20</td><td>COMPLETION</td><td>Data Length (32), Reserved (32), Block Offset (64)</td></tr>
            <tr><td>0x30</td><td>GOODBYE</td><td>none</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="descriptors">
        <name>Descriptors</name>
        <t>A Data Descriptor is 16 octets: STag (32 bits), Tagged Offset (64
        bits), and Length (32 bits), naming a buffer of the initiator.  For
        LOAD it is the destination; for STORE and ALLOC_STORE it is the source
        that the target reads with RDMA Read, unless INLINE is set, in which
        case STag and Tagged Offset are zero, Length is the inline data
        length, and the data follows the message.  A Completion Descriptor is
        16 octets: STag (32 bits), Tagged Offset (64 bits), and 32 reserved
        bits, naming a 24-octet completion record slot of the initiator.</t>
      </section>
    </section>

    <section anchor="operations">
      <name>Operations</name>
      <t>A target processes the requests of one connection in order.  It
      validates every request before touching memory: the handle <bcp14>MUST</bcp14> be
      within a range the initiator is authorized for (its own range for
      stores and frees), lengths <bcp14>MUST NOT</bcp14> exceed Block Size or the
      initiator's buffer, and, if the request Generation is non-zero, it <bcp14>MUST</bcp14>
      equal the entry's.  A failed check completes the request with the
      corresponding status and has no other effect.</t>

      <section anchor="load">
        <name>Block Load</name>
        <ol>
          <li>The target reads the table entry of the handle.  The entry <bcp14>MUST</bcp14>
          be VALID, and SEALED unless ALLOW_UNSEALED is set.</li>
          <li>It RDMA-writes Data Length octets of the block to the Data
          Descriptor.  Data Length <bcp14>MUST NOT</bcp14> exceed the descriptor
          Length.</li>
          <li>It completes the request (<xref target="completion"/>) with the
          Block Offset, Data Length, and Generation of the entry.</li>
        </ol>
      </section>

      <section anchor="mapped">
        <name>Block Mapped Store</name>
        <ol>
          <li>The target reads the table entry, which <bcp14>MUST</bcp14> be VALID and not
          SEALED and <bcp14>MUST</bcp14> belong to the initiator's handle range.</li>
          <li>It obtains the data, from the inline payload or by RDMA Read from
          the Data Descriptor, and writes it into the block.</li>
          <li>It updates Data Length and, if SEAL is set, sets SEALED.</li>
          <li>It completes the request.</li>
        </ol>
      </section>

      <section anchor="unmapped">
        <name>Block Unmapped Store</name>
        <ol>
          <li>The handle <bcp14>MUST</bcp14> be in the initiator's range and its entry <bcp14>MUST NOT</bcp14>
          be VALID.</li>
          <li>The target allocates a block from the ring and marks the slot
          consumed (<xref target="ring"/>).</li>
          <li>It obtains the data and writes it into the block.</li>
          <li>It writes the table entry with the Block Offset and Data Length,
          then with VALID, SEALED if requested, and the next Generation
          (incremented by one, skipping zero).</li>
          <li>It completes the request with the Block Offset and
          Generation.</li>
        </ol>
        <t>The entry is written after the data, so any initiator that observes
        VALID in the entry also observes the data.</t>
      </section>

      <section anchor="free">
        <name>Block Free</name>
        <ol>
          <li>The handle <bcp14>MUST</bcp14> be in the initiator's range and VALID.</li>
          <li>The target clears VALID and SEALED and increments the Generation
          (skipping zero) in the table entry.</li>
          <li>It returns the block to the ring from which it was allocated
          (<xref target="ring"/>), after the entry update is visible.</li>
          <li>It completes the request.</li>
        </ol>
        <t>Initiators that may still be loading the block detect the free by
        the changed Generation.  If the block may next be allocated to an
        initiator of a different trust domain, the target clears its
        contents before returning it to the ring.</t>
      </section>

      <section anchor="completion">
        <name>Completion</name>
        <t>By default the target completes a request with a COMPLETION message
        sent as a Send (or Send with Invalidate when INVALIDATE is set).  In
        the RDMAP binding, the contents of a buffer written by RDMA Write are
        determinate once a subsequent Send has been delivered (rule 5 of
        <xref target="RFC5040" section="5.5"/>), so receipt of the completion
        implies that the loaded data is in the initiator's buffer.</t>
        <t>If the target advertised RECORD_COMPLETION and the request has the
        RECORD flag, the target instead writes a 24-octet completion record
        to the Completion Descriptor: Request ID (64 bits), Status (16 bits),
        Generation (16 bits), Data Length (32 bits), and Block Offset (64
        bits).  A target <bcp14>MUST</bcp14> use completion records only on bindings in
        which an RDMA Write cannot become visible before the data of earlier
        RDMA Writes on the same connection.  RDMAP does not guarantee this
        (rules 3 and 5 of <xref target="RFC5040" section="5.5"/>), so a
        target <bcp14>MUST NOT</bcp14> advertise RECORD_COMPLETION in the RDMAP
        binding.</t>
      </section>

      <section anchor="direct">
        <name>Direct Modes</name>
        <t>Direct modes expose target structures to the initiator and are
        <bcp14>OPTIONAL</bcp14>.  A target <bcp14>MUST</bcp14> offer them only to initiators it trusts for
        all blocks reachable through the exposed STags.</t>
        <t>Direct Load (DIRECT_LOAD): through the read-only Table and Pool
        STags, the initiator RDMA-reads the table entry,
        then the block, then the entry again, issuing each read only after the
        previous one has completed.  The data is valid if both entry reads
        show the same Generation with VALID and SEALED set, because a sealed
        block does not change until it is freed and a free changes the
        Generation.  This costs
        two round trips but no target processing.</t>
        <t>Direct Allocation (DIRECT_ALLOC): the initiator performs FetchAdd on
        its Head with the Add Data and Add Mask of <xref target="ring"/>,
        RDMA-reads the slot at the returned offset (re-reading until the
        Sequence equals its ticket and the Block Offset is not all ones, and
        then once more to confirm both values, since a 16-octet read is not
        guaranteed to be atomic), RDMA-writes the data into the block, and
        then sends BIND with the
        ticket, Data Length, and Block Offset.  The target verifies that the
        ticket has not been abandoned, that the slot of the ticket holds that
        Block Offset, and that the initiator is within its quota; it then marks
        the slot consumed, writes the table entry, and completes.  The
        initiator never writes ring slots.  Frees always use FREE.</t>
      </section>
    </section>

    <section anchor="status">
      <name>Status Codes</name>
      <table anchor="tbl-status">
        <name>KBTP Status Codes</name>
        <thead><tr><th>Value</th><th>Name</th><th>Meaning</th></tr></thead>
        <tbody>
          <tr><td>0</td><td>OK</td><td>Success</td></tr>
          <tr><td>1</td><td>INVALID_HANDLE</td><td>Handle outside authorized range</td></tr>
          <tr><td>2</td><td>STALE_GENERATION</td><td>Generation mismatch</td></tr>
          <tr><td>3</td><td>NOT_VALID</td><td>Entry not bound</td></tr>
          <tr><td>4</td><td>ALREADY_BOUND</td><td>Unmapped Store to a bound handle</td></tr>
          <tr><td>5</td><td>NO_SPACE</td><td>Quota exceeded or ring empty</td></tr>
          <tr><td>6</td><td>SEALED</td><td>Store to a sealed block</td></tr>
          <tr><td>7</td><td>NOT_SEALED</td><td>Load of an unsealed block</td></tr>
          <tr><td>8</td><td>TOO_LARGE</td><td>Length exceeds block or buffer</td></tr>
          <tr><td>9</td><td>MALFORMED</td><td>Request cannot be parsed</td></tr>
          <tr><td>10</td><td>INTERNAL</td><td>Target failure</td></tr>
          <tr><td>11</td><td>TICKET_ABANDONED</td><td>BIND for a ticket that was reclaimed</td></tr>
        </tbody>
      </table>
    </section>

    <section anchor="offload">
      <name>Target Execution Model</name>
      <t>This section is informative.  It shows how the formats allow a target
      to execute operations without CPU involvement.  A target pre-posts, for
      each request credit, a receive buffer followed by a chain of work
      requests that its RDMA interface starts when the receive completes.  The
      receive scatter list places the request's Data Descriptor and handle
      directly into the address fields of the following work requests
      (hence the ADDR_LE option), and table and ring entries read by the
      chain are placed into later work requests in the same way.</t>
      <table anchor="tbl-chains">
        <name>Example Work Request Chains</name>
        <thead><tr><th>Operation</th><th>Chain at the target</th></tr></thead>
        <tbody>
          <tr><td>Block Load</td><td>receive request; read table entry into the next request; write block to initiator; send completion</td></tr>
          <tr><td>Block Mapped Store</td><td>receive request with inline data; read table entry; write data to block; write entry; send completion</td></tr>
          <tr><td>Block Unmapped Store</td><td>receive request with inline data; masked fetch-and-add on Head; read slot; check Sequence (conditional execution); mark slot consumed; write data to block; write table entry; send completion</td></tr>
        </tbody>
      </table>
      <t>The masked FetchAdd of <xref target="ring"/> returns a slot offset
      that is directly usable as an address, and table entries and slots hold
      Block Offsets in fixed positions, which is what makes these chains
      possible.  Loads and mapped stores need only data movement.  An
      allocation also needs the Sequence check of <xref target="ring"/> and
      the consumption of the slot; a chain can perform them only if the
      interface supports conditional execution of work requests, and
      otherwise the target handles allocations in software.  Frees, which
      update the Generation and may be deferred, are normally handled in
      software and are not latency-critical.  The target CPU otherwise only
      refills the pre-posted chains, in batches.  Implementations that cannot
      chain work requests process the same messages in software.</t>
    </section>

    <section anchor="bindings">
      <name>Transport Bindings</name>

      <section anchor="rdmap">
        <name>RDMAP Binding</name>
        <ul>
          <li>KBTP runs over an RDMAP Stream <xref target="RFC5040"/> on DDP
          <xref target="RFC5041"/> and MPA           <xref target="RFC5044"/>.  Peers
          <bcp14>SHOULD</bcp14> use the enhanced connection establishment of
          <xref target="RFC6581"/>.  Targets that advertise DIRECT_ALLOC, and
          targets that execute allocations in their RDMA interface, <bcp14>MUST</bcp14>
          support the atomic operations of <xref target="RFC7306"/>.</li>
          <li>Requests, HELLO, HELLO_ACK, and COMPLETION are carried in Send or
          Send with Solicited Event messages; INVALIDATE uses Send with
          Invalidate.</li>
          <li>Loads use RDMA Write; stores without INLINE use RDMA Read by the
          target.</li>
          <li>Ring counters use FetchAdd with the Add Mask of
          <xref target="ring"/>.</li>
          <li>Completions are always COMPLETION messages; RECORD_COMPLETION is
          not offered (<xref target="completion"/>).</li>
          <li>The security of the binding is that of
          <xref target="RFC5042"/>; when the network is not trusted, IPsec
          <bcp14>MUST</bcp14> be used as specified there.</li>
        </ul>
      </section>

      <section anchor="ib">
        <name>InfiniBand and RoCEv2</name>
        <t>Over an InfiniBand reliable connection or RoCEv2 <xref target="IBTA"/>,
        the same messages are carried with Send, RDMA Write, and RDMA Read.
        The InfiniBand specification defines an unmasked Fetch and Add; masked
        variants exist as vendor extensions.  With unmasked Fetch and Add, the
        Head and Tail counters hold the Ticket in their high 32 bits and zero
        in their low 32 bits (Add Data 0x0000000100000000), and the slot is
        computed as 16 * (ticket mod N) by the initiator in DIRECT_ALLOC or
        by the target in software.  Completion records on these transports
        <bcp14>MUST</bcp14> be written with RDMA Write with Immediate, so that the
        initiator learns of completion from its completion queue rather than
        by polling memory.</t>
      </section>
    </section>

    <section anchor="ops">
      <name>Operational Considerations</name>
      <ul>
        <li>Block size follows the KV cache block of the serving system
        (for example 16 tokens per block) or a multiple of it.</li>
        <li>Quotas and handle ranges are set at session setup and changed by
        reconnecting; targets reserve free blocks for the sum of quotas.</li>
        <li>Implementations <bcp14>SHOULD</bcp14> expose counters per operation and status,
        ring occupancy, and quota use per initiator.</li>
      </ul>
    </section>

    <section anchor="security">
      <name>Security Considerations</name>
      <t>The security considerations of <xref target="RFC5042"/> apply.  In
      the default mode, the target exposes no STag of its own memory to
      initiators: all access to the block pool, the table, and the ring is
      performed by the target after validation.  The initiator exposes only
      the buffers named in its descriptors; it <bcp14>SHOULD</bcp14> use STags that are
      valid for a single request and <bcp14>SHOULD</bcp14> request INVALIDATE so that they
      are revoked by the completion.</t>
      <t>Handle ranges prevent an initiator from overwriting or freeing
      another initiator's blocks.  Generations prevent use of stale handles.
      Quotas bound memory consumption per initiator.</t>
      <t>Direct modes expose the table and pool for reading, and the ring
      counters for atomic modification, to the trusted initiator.  A
      misbehaving initiator with DIRECT_ALLOC can take tickets without
      binding them; the Ticket Timeout bounds how long such blocks are
      withheld, and quotas bound how many.  Targets offer direct modes only
      to initiators within the same trust domain and verify every BIND
      against the ring.</t>
      <t>KV cache blocks contain information derived from user prompts.
      Connections <bcp14>SHOULD</bcp14> be confined to a trusted network or protected with
      IPsec, and targets <bcp14>SHOULD</bcp14> clear blocks when they are freed if the
      blocks may be allocated to initiators of a different trust domain.</t>
    </section>

    <section anchor="iana">
      <name>IANA Considerations</name>
      <t>IANA is requested to create a registry group named "KV Cache Block
      Transfer Protocol (KBTP)" with the registries "KBTP Opcodes" (8 bits,
      initial contents from <xref target="tbl-ops"/>), "KBTP Status Codes"
      (16 bits, initial contents from <xref target="tbl-status"/>), "KBTP
      Flags" (one entry per opcode and bit of the 16-bit Flags field; initial
      contents: for LOAD, STORE, ALLOC_STORE, and FREE, the flags of
      <xref target="common"/>; for GOODBYE, 0x0001 RELEASE), and "KBTP
      Features" (one entry per bit of the 16-bit
      Features field, initial contents from <xref target="session"/>).  New
      values are assigned by Specification Required <xref target="RFC8126"/>;
      the upper quarter of each value space is reserved for Private Use.
      The designated experts should check that new opcodes define their
      validation rules, memory ordering, and completion.  This document requests no port
      number; KBTP is identified by the RDMA connection over which it is
      established.</t>
    </section>
  </middle>

  <back>
    <references>
      <name>References</name>
      <references>
        <name>Normative References</name>
        <reference anchor="RFC2119" target="https://www.rfc-editor.org/info/rfc2119">
  <front>
    <title>Key words for use in RFCs to Indicate Requirement Levels</title>
    <author fullname="S. Bradner" initials="S." surname="Bradner"/>
    <date month="March" year="1997"/>
    </front>
  <seriesInfo name="BCP" value="14"/>
  <seriesInfo name="RFC" value="2119"/>
  <seriesInfo name="DOI" value="10.17487/RFC2119"/>
</reference>
        <reference anchor="RFC5040" target="https://www.rfc-editor.org/info/rfc5040">
  <front>
    <title>A Remote Direct Memory Access Protocol Specification</title>
    <author fullname="R. Recio" initials="R." surname="Recio"/>
    <author fullname="B. Metzler" initials="B." surname="Metzler"/>
    <author fullname="P. Culley" initials="P." surname="Culley"/>
    <author fullname="J. Hilland" initials="J." surname="Hilland"/>
    <author fullname="D. Garcia" initials="D." surname="Garcia"/>
    <date month="October" year="2007"/>
    </front>
  <seriesInfo name="RFC" value="5040"/>
  <seriesInfo name="DOI" value="10.17487/RFC5040"/>
</reference>
        <reference anchor="RFC5041" target="https://www.rfc-editor.org/info/rfc5041">
  <front>
    <title>Direct Data Placement over Reliable Transports</title>
    <author fullname="H. Shah" initials="H." surname="Shah"/>
    <author fullname="J. Pinkerton" initials="J." surname="Pinkerton"/>
    <author fullname="R. Recio" initials="R." surname="Recio"/>
    <author fullname="P. Culley" initials="P." surname="Culley"/>
    <date month="October" year="2007"/>
    </front>
  <seriesInfo name="RFC" value="5041"/>
  <seriesInfo name="DOI" value="10.17487/RFC5041"/>
</reference>
        <reference anchor="RFC5042" target="https://www.rfc-editor.org/info/rfc5042">
  <front>
    <title>Direct Data Placement Protocol (DDP) / Remote Direct Memory Access Protocol (RDMAP) Security</title>
    <author fullname="J. Pinkerton" initials="J." surname="Pinkerton"/>
    <author fullname="E. Deleganes" initials="E." surname="Deleganes"/>
    <date month="October" year="2007"/>
    </front>
  <seriesInfo name="RFC" value="5042"/>
  <seriesInfo name="DOI" value="10.17487/RFC5042"/>
</reference>
        <reference anchor="RFC5044" target="https://www.rfc-editor.org/info/rfc5044">
  <front>
    <title>Marker PDU Aligned Framing for TCP Specification</title>
    <author fullname="P. Culley" initials="P." surname="Culley"/>
    <author fullname="U. Elzur" initials="U." surname="Elzur"/>
    <author fullname="R. Recio" initials="R." surname="Recio"/>
    <author fullname="S. Bailey" initials="S." surname="Bailey"/>
    <author fullname="J. Carrier" initials="J." surname="Carrier"/>
    <date month="October" year="2007"/>
    </front>
  <seriesInfo name="RFC" value="5044"/>
  <seriesInfo name="DOI" value="10.17487/RFC5044"/>
</reference>
        <reference anchor="RFC6581" target="https://www.rfc-editor.org/info/rfc6581">
  <front>
    <title>Enhanced Remote Direct Memory Access (RDMA) Connection Establishment</title>
    <author fullname="A. Kanevsky" initials="A." role="editor" surname="Kanevsky"/>
    <author fullname="C. Bestler" initials="C." role="editor" surname="Bestler"/>
    <author fullname="R. Sharp" initials="R." surname="Sharp"/>
    <author fullname="S. Wise" initials="S." surname="Wise"/>
    <date month="April" year="2012"/>
    </front>
  <seriesInfo name="RFC" value="6581"/>
  <seriesInfo name="DOI" value="10.17487/RFC6581"/>
</reference>
        <reference anchor="RFC7306" target="https://www.rfc-editor.org/info/rfc7306">
  <front>
    <title>Remote Direct Memory Access (RDMA) Protocol Extensions</title>
    <author fullname="H. Shah" initials="H." surname="Shah"/>
    <author fullname="F. Marti" initials="F." surname="Marti"/>
    <author fullname="W. Noureddine" initials="W." surname="Noureddine"/>
    <author fullname="A. Eiriksson" initials="A." surname="Eiriksson"/>
    <author fullname="R. Sharp" initials="R." surname="Sharp"/>
    <date month="June" year="2014"/>
    </front>
  <seriesInfo name="RFC" value="7306"/>
  <seriesInfo name="DOI" value="10.17487/RFC7306"/>
</reference>
        <reference anchor="RFC8126" target="https://www.rfc-editor.org/info/rfc8126">
  <front>
    <title>Guidelines for Writing an IANA Considerations Section in RFCs</title>
    <author fullname="M. Cotton" initials="M." surname="Cotton"/>
    <author fullname="B. Leiba" initials="B." surname="Leiba"/>
    <author fullname="T. Narten" initials="T." surname="Narten"/>
    <date month="June" year="2017"/>
    </front>
  <seriesInfo name="BCP" value="26"/>
  <seriesInfo name="RFC" value="8126"/>
  <seriesInfo name="DOI" value="10.17487/RFC8126"/>
</reference>
        <reference anchor="RFC8174" target="https://www.rfc-editor.org/info/rfc8174">
  <front>
    <title>Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words</title>
    <author fullname="B. Leiba" initials="B." surname="Leiba"/>
    <date month="May" year="2017"/>
    </front>
  <seriesInfo name="BCP" value="14"/>
  <seriesInfo name="RFC" value="8174"/>
  <seriesInfo name="DOI" value="10.17487/RFC8174"/>
</reference>
      </references>
      <references>
        <name>Informative References</name>
        <reference anchor="RFC7145" target="https://www.rfc-editor.org/info/rfc7145">
  <front>
    <title>Internet Small Computer System Interface (iSCSI) Extensions for the Remote Direct Memory Access (RDMA) Specification</title>
    <author fullname="M. Ko" initials="M." surname="Ko"/>
    <author fullname="A. Nezhinsky" initials="A." surname="Nezhinsky"/>
    <date month="April" year="2014"/>
    </front>
  <seriesInfo name="RFC" value="7145"/>
  <seriesInfo name="DOI" value="10.17487/RFC7145"/>
</reference>
        <reference anchor="RFC8166" target="https://www.rfc-editor.org/info/rfc8166">
  <front>
    <title>Remote Direct Memory Access Transport for Remote Procedure Call Version 1</title>
    <author fullname="C. Lever" initials="C." role="editor" surname="Lever"/>
    <author fullname="W. Simpson" initials="W." surname="Simpson"/>
    <author fullname="T. Talpey" initials="T." surname="Talpey"/>
    <date month="June" year="2017"/>
    </front>
  <seriesInfo name="RFC" value="8166"/>
  <seriesInfo name="DOI" value="10.17487/RFC8166"/>
</reference>
        <reference anchor="I-D.talpey-rdma-commit" target="https://datatracker.ietf.org/doc/html/draft-talpey-rdma-commit-02">
  <front>
    <title>RDMA Extensions for Enhanced Memory Placement</title>
    <author fullname="Tom Talpey" initials="T." surname="Talpey">
      <organization>Unaffiliated</organization>
    </author>
    <date day="25" month="January" year="2023"/>
    </front>
  <seriesInfo name="Internet-Draft" value="draft-talpey-rdma-commit-02"/>
</reference>
        <reference anchor="I-D.li-kvcache-efficient-storage" target="https://datatracker.ietf.org/doc/html/draft-li-kvcache-efficient-storage-00">
          <front>
            <title>Key-Value (KV) Cache Storage Protocol (KVSP): Indexing, Placement, Prefetch, and Migration for Distributed Inference</title>
            <author initials="Z." surname="Li" fullname="Zhiqiang Li"/>
            <author initials="Z." surname="Du" fullname="Zongpeng Du"/>
            <author initials="J." surname="Wang" fullname="Junjie Wang"/>
            <author initials="W." surname="Cheng" fullname="Wei Cheng"/>
            <author initials="Y." surname="Zhao" fullname="Ying Zhao"/>
            <author initials="C." surname="Zhao" fullname="Chunhao Zhao"/>
            <author initials="X." surname="Sun" fullname="Xun Sun"/>
            <date year="2026" month="November" day="2"/>
          </front>
          <seriesInfo name="Internet-Draft" value="draft-li-kvcache-efficient-storage-00"/>
        </reference>
        <reference anchor="IBTA" target="https://www.infinibandta.org/">
          <front>
            <title>InfiniBand Architecture Specification Volume 1, Release 1.7, and Annex A17: RoCEv2</title>
            <author><organization>InfiniBand Trade Association</organization></author>
            <date year="2023"/>
          </front>
        </reference>
        <reference anchor="PAGEDATTENTION" target="https://doi.org/10.1145/3600006.3613165">
          <front>
            <title>Efficient Memory Management for Large Language Model Serving with PagedAttention</title>
            <author initials="W." surname="Kwon" fullname="Woosuk Kwon"/>
            <author initials="Z." surname="Li" fullname="Zhuohan Li"/>
            <author initials="S." surname="Zhuang" fullname="Siyuan Zhuang"/>
            <author initials="Y." surname="Sheng" fullname="Ying Sheng"/>
            <author initials="L." surname="Zheng" fullname="Lianmin Zheng"/>
            <author initials="C. H." surname="Yu" fullname="Cody Hao Yu"/>
            <author initials="J." surname="Gonzalez" fullname="Joseph Gonzalez"/>
            <author initials="H." surname="Zhang" fullname="Hao Zhang"/>
            <author initials="I." surname="Stoica" fullname="Ion Stoica"/>
            <date year="2023" month="October"/>
          </front>
          <refcontent>Proceedings of the 29th ACM Symposium on Operating Systems Principles (SOSP)</refcontent>
        </reference>
        <reference anchor="MOONCAKE" target="https://www.usenix.org/conference/fast25/presentation/qin">
          <front>
            <title>Mooncake: Trading More Storage for Less Computation - A KVCache-centric Architecture for Serving LLM Chatbot</title>
            <author initials="R." surname="Qin" fullname="Ruoyu Qin"/>
            <author initials="Z." surname="Li" fullname="Zheming Li"/>
            <author initials="W." surname="He" fullname="Weiran He"/>
            <author initials="J." surname="Cui" fullname="Jialei Cui"/>
            <author initials="F." surname="Ren" fullname="Feng Ren"/>
            <author initials="M." surname="Zhang" fullname="Mingxing Zhang"/>
            <author initials="Y." surname="Wu" fullname="Yongwei Wu"/>
            <author initials="W." surname="Zheng" fullname="Weimin Zheng"/>
            <author initials="X." surname="Xu" fullname="Xinran Xu"/>
            <date year="2025" month="February"/>
          </front>
          <refcontent>23rd USENIX Conference on File and Storage Technologies (FAST 25)</refcontent>
        </reference>
      </references>
    </references>

    <section anchor="examples" numbered="true">
      <name>Examples</name>
      <t>ALLOC_STORE of 8 octets of inline data ("KVBLOCK!") to handle 0x4001,
      Request ID 7, flags INLINE:</t>
      <artwork>
01 12 00 01 00 00 00 00 00 00 00 07 00 00 00 00
00 00 40 01 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 08 4b 56 42 4c 4f 43 4b 21
</artwork>
      <t>Its COMPLETION (Generation 5, Status OK, Data Length 8, Block Offset
      0x150000, which is block 42 of a pool of 32 KiB blocks):</t>
      <artwork>
01 20 00 00 00 00 00 00 00 00 00 07 00 00 00 00
00 00 40 01 00 05 00 00 00 00 00 08 00 00 00 00
00 00 00 00 00 15 00 00
</artwork>
      <t>Ring counter for N = 4 (n = 2) with 16-octet slots: Add Data
      0x0000000100000010, Add Mask 0x8000000080000020.  Successive FetchAdd
      results on Head, starting from zero, show the ticket advancing while
      the slot offset wraps after four slots:</t>
      <artwork>
returned (ticket, offset)   new counter value
(0, 0)                      0x0000000100000010
(1, 16)                     0x0000000200000020
(2, 32)                     0x0000000300000030
(3, 48)                     0x0000000400000000
(4, 0)                      0x0000000500000010
</artwork>
      <t>A shared ring of four free blocks 100 to 103 (N = F = 4) serves
      target-executed allocations.  The target takes ticket 0 for an
      ALLOC_STORE of initiator X, and that work request chain is delayed
      before reading slot 0.  Meanwhile ALLOC_STORE requests of initiator Y
      take tickets 1, 2, and 3 (blocks 101, 102, 103), and Y frees block 101;
      the free obtains ticket 4, whose slot is slot 0, which has not been
      consumed yet, so the free is deferred.  X's allocation then reads slot
      0, obtains block 100, and consumes the slot; the deferred free now
      writes slot 0 with Sequence 4 and block 101, which the next allocation
      (ticket 4) obtains.  No block is lost or handed out twice.</t>
    </section>

    <section anchor="acknowledgments" numbered="false">
      <name>Acknowledgments</name>
      <t>The authors thank colleagues working on RDMA offload and KV cache
      storage for their comments.</t>
    </section>
  </back>
</rfc>