Compare commits
3 Commits
5feb5b96f8
...
08826f6ace
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08826f6ace | ||
|
|
599aa44d97 | ||
|
|
94046f1102 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,3 +26,4 @@ dist/
|
|||||||
logs/tracker/error.log
|
logs/tracker/error.log
|
||||||
logs/tracker/info.log
|
logs/tracker/info.log
|
||||||
logs/tracker/warning.log
|
logs/tracker/warning.log
|
||||||
|
.venv*
|
||||||
|
|||||||
@@ -529,4 +529,4 @@
|
|||||||
"metadata": {
|
"metadata": {
|
||||||
"updatedAt": "2026-07-08T23:30:00.000Z"
|
"updatedAt": "2026-07-08T23:30:00.000Z"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
120
QUICKSTART.md
120
QUICKSTART.md
@@ -3,7 +3,9 @@
|
|||||||
Get from zero to a live inference request in **three terminals**: install once, start
|
Get from zero to a live inference request in **three terminals**: install once, start
|
||||||
the tracker, start a node, send a request.
|
the tracker, start a node, send a request.
|
||||||
|
|
||||||
Tested on: AMD Ryzen AI Max (Strix Halo APU), 124 GB RAM, Linux, CPU inference.
|
Tested on: AMD Ryzen AI Max (Strix Halo APU), 124 GB RAM, Linux CPU inference.
|
||||||
|
ROCm GPU setup is covered below, but must be verified on the host because ROCm
|
||||||
|
support depends on the exact AMD GPU/APU, kernel, driver, and ROCm runtime.
|
||||||
|
|
||||||
**Active development models** (what we run day-to-day):
|
**Active development models** (what we run day-to-day):
|
||||||
|
|
||||||
@@ -130,11 +132,116 @@ Install **one** torch line into the same env as `meshnet-node`:
|
|||||||
|----------|---------|
|
|----------|---------|
|
||||||
| NVIDIA CUDA | `pip install torch` (default index) |
|
| NVIDIA CUDA | `pip install torch` (default index) |
|
||||||
| CPU only | `pip install torch --index-url https://download.pytorch.org/whl/cpu` |
|
| CPU only | `pip install torch --index-url https://download.pytorch.org/whl/cpu` |
|
||||||
| AMD ROCm | `pip install torch --index-url https://download.pytorch.org/whl/rocm6.2` |
|
| AMD ROCm | `pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.3` |
|
||||||
|
|
||||||
On Windows `.venv`, prefix with `.\.venv\Scripts\pip.exe`. Conda users with CUDA
|
On Windows `.venv`, prefix with `.\.venv\Scripts\pip.exe`. Conda users with CUDA
|
||||||
torch already installed can skip this step.
|
torch already installed can skip this step.
|
||||||
|
|
||||||
|
### Linux AMD ROCm GPU install
|
||||||
|
|
||||||
|
Use this when the node machine has an AMD GPU/APU and you want PyTorch to run on
|
||||||
|
ROCm instead of CPU. The Python wheel is not enough by itself: the host must have
|
||||||
|
working AMD GPU device access and a compatible ROCm runtime.
|
||||||
|
|
||||||
|
**Host prerequisites:**
|
||||||
|
|
||||||
|
1. Confirm the AMD GPU is visible:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lspci | grep -Ei 'vga|3d|display|amd|ati'
|
||||||
|
ls -l /dev/kfd /dev/dri/renderD* 2>/dev/null
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Make sure the node user can access GPU devices. AMD ROCm documents the normal
|
||||||
|
Linux permission path as membership in both `video` and `render`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
groups
|
||||||
|
sudo usermod -a -G video,render "$LOGNAME"
|
||||||
|
# log out and back in before continuing
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Confirm the ROCm runtime tools work if they are installed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
rocminfo | head
|
||||||
|
```
|
||||||
|
|
||||||
|
If `rocminfo` is missing or cannot see the GPU, fix the host ROCm install first.
|
||||||
|
Do not debug `meshnet-node` until this works.
|
||||||
|
|
||||||
|
**Install ROCm PyTorch into the node env:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/neuron-tai
|
||||||
|
python3.12 -m venv .venv-rocm
|
||||||
|
source .venv-rocm/bin/activate
|
||||||
|
python -m pip install --upgrade pip setuptools wheel
|
||||||
|
python -m pip install -e packages/tracker -e packages/node -e packages/p2p -e packages/gateway -e packages/relay
|
||||||
|
python -m pip install "transformers>=5.12" accelerate safetensors
|
||||||
|
python -m pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/rocm6.3
|
||||||
|
```
|
||||||
|
|
||||||
|
Keep this separate from a known-good CPU `.venv` until ROCm is verified on that
|
||||||
|
machine. ROCm wheels are large and host-runtime-sensitive; a failed ROCm install
|
||||||
|
should not break the CPU fallback environment.
|
||||||
|
|
||||||
|
Use Python 3.12 for this env. Python 3.14 is currently a bad fit for the
|
||||||
|
Qwen3.6/FLA path because `torch.compile` is not supported there.
|
||||||
|
|
||||||
|
**Verify PyTorch sees ROCm:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python - <<'PY'
|
||||||
|
import torch
|
||||||
|
print("torch", torch.__version__)
|
||||||
|
print("hip", torch.version.hip)
|
||||||
|
print("cuda api available", torch.cuda.is_available())
|
||||||
|
if torch.cuda.is_available():
|
||||||
|
print("device", torch.cuda.get_device_name(0))
|
||||||
|
x = torch.ones((1,), device="cuda")
|
||||||
|
torch.cuda.synchronize()
|
||||||
|
print("tensor", x)
|
||||||
|
PY
|
||||||
|
```
|
||||||
|
|
||||||
|
Expected: `torch.version.hip` is not `None`, `torch.cuda.is_available()` is
|
||||||
|
`True`, and the tensor allocation succeeds. PyTorch intentionally exposes ROCm
|
||||||
|
through the `torch.cuda` API.
|
||||||
|
|
||||||
|
**Start an AMD ROCm node:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HF_HOME=/path/to/models .venv-rocm/bin/meshnet-node start \
|
||||||
|
--tracker <tracker-url> \
|
||||||
|
--model Qwen/Qwen2.5-0.5B-Instruct \
|
||||||
|
--quantization bfloat16
|
||||||
|
```
|
||||||
|
|
||||||
|
For the Qwen3.6 alpha model on Linux ROCm, install the optional FLA ROCm fast
|
||||||
|
path in the same env:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.venv-rocm/bin/pip install 'flash-linear-attention[rocm]'
|
||||||
|
HF_HOME=/path/to/models .venv-rocm/bin/meshnet-node start \
|
||||||
|
--tracker <tracker-url> \
|
||||||
|
--model qwen3.6-35b-a3b \
|
||||||
|
--quantization bfloat16
|
||||||
|
```
|
||||||
|
|
||||||
|
**Troubleshooting notes:**
|
||||||
|
|
||||||
|
- `torch.version.hip is None` means you installed a CPU/CUDA torch build, not ROCm.
|
||||||
|
- `torch.cuda.is_available() == False` with a ROCm build usually means host driver,
|
||||||
|
permissions, unsupported hardware, or missing runtime libraries.
|
||||||
|
- `which meshnet-node` should not point at `~/.local/bin/meshnet-node` for ROCm
|
||||||
|
testing. Run `.venv-rocm/bin/meshnet-node ...` so the node uses the same ROCm
|
||||||
|
PyTorch, `transformers`, and FLA packages you verified.
|
||||||
|
- Missing libraries such as `libamdhip64.so`, `libMIOpen.so`, `librocsolver.so`,
|
||||||
|
or `libroctx64.so` are host ROCm runtime problems, not meshnet-node problems.
|
||||||
|
- Some AMD APUs and consumer GPUs require newer ROCm/Radeon support than server
|
||||||
|
Instinct cards. Check AMD's ROCm Radeon/Ryzen support matrix for the exact model.
|
||||||
|
|
||||||
### Qwen3.5/3.6-MoE notes
|
### Qwen3.5/3.6-MoE notes
|
||||||
|
|
||||||
Applies to **`qwen3.6-35b-a3b`** and other hybrid linear-attention models. **`Qwen2.5-0.5B`**
|
Applies to **`qwen3.6-35b-a3b`** and other hybrid linear-attention models. **`Qwen2.5-0.5B`**
|
||||||
@@ -356,13 +463,20 @@ meshnet-node start --tracker http://192.168.0.179:8080 --model qwen3.6-35b-a3b -
|
|||||||
|
|
||||||
Do not add `causal-conv1d` or `flash-linear-attention[cuda]` on Windows (see Qwen3.5/3.6 notes).
|
Do not add `causal-conv1d` or `flash-linear-attention[cuda]` on Windows (see Qwen3.5/3.6 notes).
|
||||||
|
|
||||||
**Alpha model (Qwen3.6, Linux GPU — with fast path):**
|
**Alpha model (Qwen3.6, Linux NVIDIA GPU — with fast path):**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
HF_HOME=/path/to/models .venv/bin/meshnet-node start --tracker <tracker-url> --model qwen3.6-35b-a3b --quantization bfloat16
|
HF_HOME=/path/to/models .venv/bin/meshnet-node start --tracker <tracker-url> --model qwen3.6-35b-a3b --quantization bfloat16
|
||||||
# Install once on that machine: pip install flash-linear-attention[cuda]
|
# Install once on that machine: pip install flash-linear-attention[cuda]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Alpha model (Qwen3.6, Linux AMD ROCm GPU — with fast path):**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
HF_HOME=/path/to/models .venv-rocm/bin/meshnet-node start --tracker <tracker-url> --model qwen3.6-35b-a3b --quantization bfloat16
|
||||||
|
# Install once on that machine: .venv-rocm/bin/pip install 'flash-linear-attention[rocm]'
|
||||||
|
```
|
||||||
|
|
||||||
After the first node registers a model, later nodes can join with only the tracker
|
After the first node registers a model, later nodes can join with only the tracker
|
||||||
URL (shard auto-assigned):
|
URL (shard auto-assigned):
|
||||||
|
|
||||||
|
|||||||
63
docs/adr/0022-sharded-per-node-generation-cache.md
Normal file
63
docs/adr/0022-sharded-per-node-generation-cache.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# ADR-0022: Sharded per-node generation cache for distributed PyTorch routes
|
||||||
|
|
||||||
|
## Status: Accepted
|
||||||
|
|
||||||
|
## Context
|
||||||
|
|
||||||
|
The distributed PyTorch chat path previously recomputed the full prompt-so-far for
|
||||||
|
every generated token. The head shard embedded the entire sequence each step, forwarded
|
||||||
|
full-sequence activations through every downstream shard, and every shard called its
|
||||||
|
decoder layers with `use_cache=False`. On a two-node Qwen2.5-0.5B route this produced
|
||||||
|
the expected quadratic slowdown as output length grew.
|
||||||
|
|
||||||
|
ADR-0020 and ADR-0021 fixed route construction and `start_layer` semantics. They did not
|
||||||
|
define the per-request cache lifecycle needed for efficient decode.
|
||||||
|
|
||||||
|
## Decision
|
||||||
|
|
||||||
|
Distributed PyTorch generation now uses one stable route session id for an entire chat
|
||||||
|
request. The wire protocol marks each activation hop with:
|
||||||
|
|
||||||
|
- `X-Meshnet-Session`: stable per generation.
|
||||||
|
- `X-Meshnet-Cache-Mode`: `prefill`, `decode`, or `stateless`.
|
||||||
|
- `X-Meshnet-Seq-Len`: the total sequence length represented by the step.
|
||||||
|
|
||||||
|
Step 0 is prefill: the head sends the full prompt activation through the planned route.
|
||||||
|
Each shard stores only the opaque cache state returned by its own executed layer range.
|
||||||
|
No shard receives or stores another shard's cache.
|
||||||
|
|
||||||
|
Later cached decode steps send only the newest token activation (`[1, 1, hidden]`) with
|
||||||
|
the full sequence length and newest position id. The backend deliberately treats layer
|
||||||
|
cache state as opaque. Standard K/V tuples, HuggingFace cache objects, and hybrid
|
||||||
|
linear-attention recurrent state are stored without shape assumptions.
|
||||||
|
|
||||||
|
## Cache lifecycle
|
||||||
|
|
||||||
|
Each `TorchModelShard` owns an in-memory LRU map keyed by
|
||||||
|
`(session_id, effective_start_layer, shard_end)`. Entries expire by TTL and by a maximum
|
||||||
|
session count (`MESHNET_SHARD_CACHE_TTL_SECONDS`, default 600;
|
||||||
|
`MESHNET_SHARD_CACHE_MAX_SESSIONS`, default 16).
|
||||||
|
|
||||||
|
If a decode step reaches a node after restart, eviction, TTL expiry, or route mismatch,
|
||||||
|
the node returns an explicit `cache_miss` response. The head falls back to full prefill
|
||||||
|
for the current prompt-so-far using the same session id, rebuilding the shard-local
|
||||||
|
caches before continuing. Alpha route repair still does not migrate cache state across
|
||||||
|
nodes; a true route change is treated as cache loss and recovered by re-prefill.
|
||||||
|
|
||||||
|
## Consequences
|
||||||
|
|
||||||
|
- Healthy decode sends O(1) activation payloads per token between nodes instead of
|
||||||
|
O(sequence length).
|
||||||
|
- Cache internals stay behind the model backend boundary, which keeps Qwen3.6-style
|
||||||
|
hybrid recurrent cache state compatible with the same route protocol.
|
||||||
|
- Restart and eviction degrade to slower stateless/full-prefill work rather than silent
|
||||||
|
output corruption.
|
||||||
|
- Cross-node cache migration, batching cache state across sessions, and speculative
|
||||||
|
decoding remain future work.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Unit coverage in `tests/test_real_model_backend.py` verifies opaque per-layer cache
|
||||||
|
storage, cached one-token decode, explicit cache-miss errors, and LRU eviction. Live
|
||||||
|
two-node Qwen2.5-0.5B TPS measurement still requires the physical two-machine topology
|
||||||
|
used to observe the regression.
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
"""US-012 tests for the real PyTorch node backend."""
|
"""US-012 tests for the real PyTorch node backend."""
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -14,6 +15,7 @@ import pytest
|
|||||||
from meshnet_node.model_backend import (
|
from meshnet_node.model_backend import (
|
||||||
InsufficientVRAMError,
|
InsufficientVRAMError,
|
||||||
PartialModelLoadUnsupported,
|
PartialModelLoadUnsupported,
|
||||||
|
ShardCacheMiss,
|
||||||
TensorPayload,
|
TensorPayload,
|
||||||
TorchModelShard,
|
TorchModelShard,
|
||||||
_call_layer,
|
_call_layer,
|
||||||
@@ -43,7 +45,15 @@ class _FakeBackend:
|
|||||||
position_ids_header=None,
|
position_ids_header=None,
|
||||||
)
|
)
|
||||||
|
|
||||||
def forward_bytes(self, body, shape, attention_mask_header, position_ids_header, start_layer=None):
|
def forward_bytes(
|
||||||
|
self,
|
||||||
|
body,
|
||||||
|
shape,
|
||||||
|
attention_mask_header,
|
||||||
|
position_ids_header,
|
||||||
|
start_layer=None,
|
||||||
|
**kwargs, # noqa: ARG002
|
||||||
|
):
|
||||||
assert shape == [1, 6, 8]
|
assert shape == [1, 6, 8]
|
||||||
return TensorPayload(
|
return TensorPayload(
|
||||||
body=body,
|
body=body,
|
||||||
@@ -57,7 +67,15 @@ class _FakeTailBackend(_FakeBackend):
|
|||||||
is_head = False
|
is_head = False
|
||||||
is_tail = True
|
is_tail = True
|
||||||
|
|
||||||
def forward_bytes(self, body, shape, attention_mask_header, position_ids_header, start_layer=None):
|
def forward_bytes(
|
||||||
|
self,
|
||||||
|
body,
|
||||||
|
shape,
|
||||||
|
attention_mask_header,
|
||||||
|
position_ids_header,
|
||||||
|
start_layer=None,
|
||||||
|
**kwargs, # noqa: ARG002
|
||||||
|
):
|
||||||
assert len(body) == 1 * 6 * 8 * 2
|
assert len(body) == 1 * 6 * 8 * 2
|
||||||
return " Paris"
|
return " Paris"
|
||||||
|
|
||||||
@@ -114,7 +132,15 @@ class _FakePipelineTailBackend(_FakeTailBackend):
|
|||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.start_layers: list[int | None] = []
|
self.start_layers: list[int | None] = []
|
||||||
|
|
||||||
def forward_bytes(self, body, shape, attention_mask_header, position_ids_header, start_layer=None):
|
def forward_bytes(
|
||||||
|
self,
|
||||||
|
body,
|
||||||
|
shape,
|
||||||
|
attention_mask_header,
|
||||||
|
position_ids_header,
|
||||||
|
start_layer=None,
|
||||||
|
**kwargs, # noqa: ARG002
|
||||||
|
):
|
||||||
self.start_layers.append(start_layer)
|
self.start_layers.append(start_layer)
|
||||||
assert len(body) == 1 * 6 * 8 * 2
|
assert len(body) == 1 * 6 * 8 * 2
|
||||||
return " token"
|
return " token"
|
||||||
@@ -125,7 +151,15 @@ class _BlockingStreamingTailBackend(_FakeTailBackend):
|
|||||||
self._release = second_token_release
|
self._release = second_token_release
|
||||||
self.calls = 0
|
self.calls = 0
|
||||||
|
|
||||||
def forward_bytes(self, body, shape, attention_mask_header, position_ids_header, start_layer=None):
|
def forward_bytes(
|
||||||
|
self,
|
||||||
|
body,
|
||||||
|
shape,
|
||||||
|
attention_mask_header,
|
||||||
|
position_ids_header,
|
||||||
|
start_layer=None,
|
||||||
|
**kwargs, # noqa: ARG002
|
||||||
|
):
|
||||||
self.calls += 1
|
self.calls += 1
|
||||||
if self.calls == 1:
|
if self.calls == 1:
|
||||||
return " first"
|
return " first"
|
||||||
@@ -488,13 +522,118 @@ def test_call_layer_passes_rotary_position_embeddings():
|
|||||||
assert kwargs["position_embeddings"] == "rotary"
|
assert kwargs["position_embeddings"] == "rotary"
|
||||||
return hidden_states
|
return hidden_states
|
||||||
|
|
||||||
assert _call_layer(
|
hidden, cache_state = _call_layer(
|
||||||
NeedsPositionEmbeddings(),
|
NeedsPositionEmbeddings(),
|
||||||
"hidden",
|
"hidden",
|
||||||
attention_mask=None,
|
attention_mask=None,
|
||||||
position_ids="positions",
|
position_ids="positions",
|
||||||
position_embeddings="rotary",
|
position_embeddings="rotary",
|
||||||
) == "hidden"
|
)
|
||||||
|
|
||||||
|
assert hidden == "hidden"
|
||||||
|
assert cache_state is None
|
||||||
|
|
||||||
|
|
||||||
|
def _fake_cache_shard(torch, *, max_sessions=16, ttl=600.0):
|
||||||
|
class RecordingLayer:
|
||||||
|
def __init__(self, index):
|
||||||
|
self.index = index
|
||||||
|
self.calls = []
|
||||||
|
|
||||||
|
def __call__(self, hidden_states, **kwargs):
|
||||||
|
self.calls.append({
|
||||||
|
"shape": tuple(hidden_states.shape),
|
||||||
|
"use_cache": kwargs.get("use_cache"),
|
||||||
|
"past_key_value": kwargs.get("past_key_value"),
|
||||||
|
})
|
||||||
|
present = {
|
||||||
|
"layer": self.index,
|
||||||
|
"shape": tuple(hidden_states.shape),
|
||||||
|
"opaque": object(),
|
||||||
|
}
|
||||||
|
return hidden_states + (self.index + 1), present
|
||||||
|
|
||||||
|
shard = object.__new__(TorchModelShard)
|
||||||
|
shard.shard_start = 0
|
||||||
|
shard.shard_end = 1
|
||||||
|
shard.torch = torch
|
||||||
|
shard.model = types.SimpleNamespace(model=types.SimpleNamespace(layers=[]))
|
||||||
|
shard.layers = [RecordingLayer(0), RecordingLayer(1)]
|
||||||
|
shard._session_cache = OrderedDict()
|
||||||
|
shard._cache_max_sessions = max_sessions
|
||||||
|
shard._cache_ttl_seconds = ttl
|
||||||
|
return shard
|
||||||
|
|
||||||
|
|
||||||
|
def test_shard_cache_prefill_then_decode_reuses_opaque_layer_state():
|
||||||
|
torch = pytest.importorskip("torch")
|
||||||
|
shard = _fake_cache_shard(torch)
|
||||||
|
|
||||||
|
prefill_hidden = torch.zeros((1, 4, 2), dtype=torch.bfloat16)
|
||||||
|
prefill_mask = torch.ones((1, 4), dtype=torch.long)
|
||||||
|
prefill_positions = torch.arange(4, dtype=torch.long).reshape(1, 4)
|
||||||
|
shard._run_layers(
|
||||||
|
prefill_hidden,
|
||||||
|
prefill_mask,
|
||||||
|
prefill_positions,
|
||||||
|
session_id="session-1",
|
||||||
|
cache_mode="prefill",
|
||||||
|
seq_len=4,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert len(shard._session_cache) == 1
|
||||||
|
cached_states = next(iter(shard._session_cache.values())).layer_states
|
||||||
|
assert len(cached_states) == 2
|
||||||
|
assert cached_states[0]["shape"] == (1, 4, 2)
|
||||||
|
|
||||||
|
decode_hidden = torch.zeros((1, 1, 2), dtype=torch.bfloat16)
|
||||||
|
decode_mask = torch.ones((1, 5), dtype=torch.long)
|
||||||
|
decode_positions = torch.tensor([[4]], dtype=torch.long)
|
||||||
|
shard._run_layers(
|
||||||
|
decode_hidden,
|
||||||
|
decode_mask,
|
||||||
|
decode_positions,
|
||||||
|
session_id="session-1",
|
||||||
|
cache_mode="decode",
|
||||||
|
seq_len=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert shard.layers[0].calls[-1]["shape"] == (1, 1, 2)
|
||||||
|
assert shard.layers[0].calls[-1]["past_key_value"] is cached_states[0]
|
||||||
|
assert shard.layers[1].calls[-1]["past_key_value"] is cached_states[1]
|
||||||
|
assert next(iter(shard._session_cache.values())).seq_len == 5
|
||||||
|
|
||||||
|
|
||||||
|
def test_shard_cache_decode_miss_is_explicit():
|
||||||
|
torch = pytest.importorskip("torch")
|
||||||
|
shard = _fake_cache_shard(torch)
|
||||||
|
|
||||||
|
with pytest.raises(ShardCacheMiss):
|
||||||
|
shard._run_layers(
|
||||||
|
torch.zeros((1, 1, 2), dtype=torch.bfloat16),
|
||||||
|
torch.ones((1, 5), dtype=torch.long),
|
||||||
|
torch.tensor([[4]], dtype=torch.long),
|
||||||
|
session_id="missing",
|
||||||
|
cache_mode="decode",
|
||||||
|
seq_len=5,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_shard_cache_lru_bounds_sessions():
|
||||||
|
torch = pytest.importorskip("torch")
|
||||||
|
shard = _fake_cache_shard(torch, max_sessions=1)
|
||||||
|
|
||||||
|
for session in ("old", "new"):
|
||||||
|
shard._run_layers(
|
||||||
|
torch.zeros((1, 2, 2), dtype=torch.bfloat16),
|
||||||
|
torch.ones((1, 2), dtype=torch.long),
|
||||||
|
torch.arange(2, dtype=torch.long).reshape(1, 2),
|
||||||
|
session_id=session,
|
||||||
|
cache_mode="prefill",
|
||||||
|
seq_len=2,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert list(shard._session_cache.keys()) == [("new", 0, 1)]
|
||||||
|
|
||||||
|
|
||||||
def test_partial_materialize_guard_requires_local_non_full_non_quantized_snapshot(tmp_path):
|
def test_partial_materialize_guard_requires_local_non_full_non_quantized_snapshot(tmp_path):
|
||||||
|
|||||||
Reference in New Issue
Block a user