river-client 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,80 @@
1
+ """River Python client for ML training API.
2
+
3
+ Example usage:
4
+
5
+ import river_client as river
6
+
7
+ client = river.Client(api_key="...", endpoint="api.river.ai")
8
+
9
+ # Stateless sampling from base model
10
+ samples = client.sample(
11
+ "What is 2+2?",
12
+ base_model="Qwen/Qwen3.6-35B-A3B-FP8",
13
+ max_tokens=50,
14
+ )
15
+ print(samples[0].text)
16
+
17
+ # Training with session
18
+ with client.session() as session:
19
+ model = session.create_model(
20
+ base_model="Qwen/Qwen3.6-35B-A3B-FP8",
21
+ lora=river.LoraConfig(rank=16)
22
+ )
23
+
24
+ # Training loop
25
+ result = model.forward_backward(data, loss_fn="cross_entropy")
26
+ model.optim_step(lr=1e-4)
27
+
28
+ # Sample from current weights
29
+ sample_groups = model.sample("Continue:", max_tokens=100)
30
+ """
31
+
32
+ from river_client.client import (
33
+ Client,
34
+ Model,
35
+ Session,
36
+ SessionContext,
37
+ )
38
+ from river_client.types import (
39
+ AuthenticationError,
40
+ CapacityError,
41
+ ChatCompleteResult,
42
+ Checkpoint,
43
+ ForwardResult,
44
+ LoraConfig,
45
+ ModelNotFoundError,
46
+ OptimStepResult,
47
+ PendingOp,
48
+ PendingSample,
49
+ PromotedStreamingReplica,
50
+ RiverConnectionError,
51
+ RiverError,
52
+ SessionHeartbeatError,
53
+ RiverTimeoutError,
54
+ Sample,
55
+ )
56
+
57
+ __version__ = "0.1.0"
58
+
59
+ __all__ = [
60
+ "AuthenticationError",
61
+ "CapacityError",
62
+ "ChatCompleteResult",
63
+ "Checkpoint",
64
+ "Client",
65
+ "ForwardResult",
66
+ "LoraConfig",
67
+ "Model",
68
+ "ModelNotFoundError",
69
+ "OptimStepResult",
70
+ "PendingOp",
71
+ "PendingSample",
72
+ "PromotedStreamingReplica",
73
+ "RiverConnectionError",
74
+ "RiverError",
75
+ "SessionHeartbeatError",
76
+ "RiverTimeoutError",
77
+ "Sample",
78
+ "Session",
79
+ "SessionContext",
80
+ ]
@@ -0,0 +1,263 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ import struct
5
+ from collections.abc import Mapping, Sequence
6
+ from typing import Any
7
+
8
+ import numpy as np
9
+ import xxhash
10
+
11
+ _MAGIC = b"NEST"
12
+ _VERSION = 1
13
+ _HEADER = _MAGIC + bytes([_VERSION, 0, 0, 0])
14
+ _CHECKSUM_SEED = 0x4E455354
15
+
16
+ _TAG_ARRAY = 0
17
+ _TAG_DICT = 1
18
+ _TAG_LIST = 2
19
+
20
+ _DTYPE_BOOL = 0
21
+ _DTYPE_INT8 = 1
22
+ _DTYPE_INT16 = 2
23
+ _DTYPE_INT32 = 3
24
+ _DTYPE_INT64 = 4
25
+ _DTYPE_UINT8 = 5
26
+ _DTYPE_UINT16 = 6
27
+ _DTYPE_UINT32 = 7
28
+ _DTYPE_UINT64 = 8
29
+ _DTYPE_FLOAT32 = 9
30
+ _DTYPE_FLOAT64 = 10
31
+ _DTYPE_STRING = 11
32
+
33
+ _ENCODABLE_DTYPES: dict[np.dtype[Any], tuple[int, np.dtype[Any]]] = {
34
+ np.dtype(np.bool_): (_DTYPE_BOOL, np.dtype(np.bool_)),
35
+ np.dtype(np.int8): (_DTYPE_INT8, np.dtype(np.int8)),
36
+ np.dtype(np.int16): (_DTYPE_INT16, np.dtype("<i2")),
37
+ np.dtype(np.int32): (_DTYPE_INT32, np.dtype("<i4")),
38
+ np.dtype(np.int64): (_DTYPE_INT64, np.dtype("<i8")),
39
+ np.dtype(np.uint8): (_DTYPE_UINT8, np.dtype(np.uint8)),
40
+ np.dtype(np.uint16): (_DTYPE_UINT16, np.dtype("<u2")),
41
+ np.dtype(np.uint32): (_DTYPE_UINT32, np.dtype("<u4")),
42
+ np.dtype(np.uint64): (_DTYPE_UINT64, np.dtype("<u8")),
43
+ np.dtype(np.float32): (_DTYPE_FLOAT32, np.dtype("<f4")),
44
+ np.dtype(np.float64): (_DTYPE_FLOAT64, np.dtype("<f8")),
45
+ }
46
+
47
+ _DECODABLE_DTYPES: dict[int, np.dtype[Any]] = {
48
+ _DTYPE_BOOL: np.dtype(np.bool_),
49
+ _DTYPE_INT8: np.dtype(np.int8),
50
+ _DTYPE_INT16: np.dtype("<i2"),
51
+ _DTYPE_INT32: np.dtype("<i4"),
52
+ _DTYPE_INT64: np.dtype("<i8"),
53
+ _DTYPE_UINT8: np.dtype(np.uint8),
54
+ _DTYPE_UINT16: np.dtype("<u2"),
55
+ _DTYPE_UINT32: np.dtype("<u4"),
56
+ _DTYPE_UINT64: np.dtype("<u8"),
57
+ _DTYPE_FLOAT32: np.dtype("<f4"),
58
+ _DTYPE_FLOAT64: np.dtype("<f8"),
59
+ }
60
+
61
+
62
+ class _Reader:
63
+ def __init__(self, data: bytes) -> None:
64
+ self._data = data
65
+ self._offset = 0
66
+
67
+ @property
68
+ def offset(self) -> int:
69
+ return self._offset
70
+
71
+ def align_to_eight(self) -> None:
72
+ self._offset = _align_to_eight(self._offset)
73
+ if self._offset > len(self._data):
74
+ raise ValueError("nested payload ended before alignment padding")
75
+
76
+ def read(self, size: int) -> bytes:
77
+ end = self._offset + size
78
+ if end > len(self._data):
79
+ raise ValueError("nested payload ended unexpectedly")
80
+ value = self._data[self._offset : end]
81
+ self._offset = end
82
+ return value
83
+
84
+ def read_u8(self) -> int:
85
+ return self.read(1)[0]
86
+
87
+ def read_u16(self) -> int:
88
+ return struct.unpack("<H", self.read(2))[0]
89
+
90
+ def read_u32(self) -> int:
91
+ return struct.unpack("<I", self.read(4))[0]
92
+
93
+ def read_u64(self) -> int:
94
+ return struct.unpack("<Q", self.read(8))[0]
95
+
96
+ def read_key(self) -> str:
97
+ length = self.read_u16()
98
+ return self.read(length).decode("utf-8")
99
+
100
+
101
+ def to_bytes(value: Any) -> bytes:
102
+ output = bytearray(_HEADER)
103
+ _write_value(output, value)
104
+ return bytes(output)
105
+
106
+
107
+ def from_bytes(data: bytes | bytearray | memoryview) -> Any:
108
+ payload = bytes(data)
109
+ reader = _Reader(payload)
110
+ header = reader.read(len(_HEADER))
111
+ if not header.startswith(_MAGIC):
112
+ raise ValueError("invalid nested payload magic")
113
+ version = header[len(_MAGIC)]
114
+ if version != _VERSION:
115
+ raise ValueError(f"unsupported nested payload version: {version}")
116
+
117
+ value = _read_value(reader)
118
+ if reader.offset != len(payload):
119
+ raise ValueError("nested payload has trailing bytes")
120
+ return value
121
+
122
+
123
+ def _write_value(output: bytearray, value: Any) -> None:
124
+ if isinstance(value, np.ndarray):
125
+ _write_array(output, value)
126
+ elif isinstance(value, str):
127
+ _write_string(output, value)
128
+ elif isinstance(value, Mapping):
129
+ _write_dict(output, value)
130
+ elif _is_sequence(value):
131
+ _write_list(output, value)
132
+ else:
133
+ _write_array(output, np.asarray(value))
134
+
135
+
136
+ def _write_array(output: bytearray, array: np.ndarray) -> None:
137
+ dtype_code, wire_dtype = _wire_dtype(array.dtype)
138
+ wire_array = np.ascontiguousarray(array, dtype=wire_dtype)
139
+ raw = wire_array.tobytes(order="C")
140
+ checksum = xxhash.xxh64_intdigest(raw, seed=_CHECKSUM_SEED)
141
+
142
+ if wire_array.ndim > 255:
143
+ raise ValueError(f"too many nested array dimensions: {wire_array.ndim}")
144
+
145
+ output.append(_TAG_ARRAY)
146
+ output.append(dtype_code)
147
+ output.append(wire_array.ndim)
148
+ for dim in wire_array.shape:
149
+ output.extend(struct.pack("<Q", int(dim)))
150
+ output.extend(struct.pack("<Q", checksum))
151
+ _pad_to_eight(output)
152
+ output.extend(raw)
153
+
154
+
155
+ def _write_string(output: bytearray, value: str) -> None:
156
+ raw = value.encode("utf-8")
157
+ checksum = xxhash.xxh64_intdigest(raw, seed=_CHECKSUM_SEED)
158
+
159
+ output.append(_TAG_ARRAY)
160
+ output.append(_DTYPE_STRING)
161
+ output.append(0)
162
+ output.extend(struct.pack("<I", len(raw)))
163
+ output.extend(struct.pack("<Q", checksum))
164
+ _pad_to_eight(output)
165
+ output.extend(raw)
166
+
167
+
168
+ def _write_dict(output: bytearray, value: Mapping[Any, Any]) -> None:
169
+ output.append(_TAG_DICT)
170
+ output.extend(struct.pack("<I", len(value)))
171
+ for key, item in value.items():
172
+ if not isinstance(key, str):
173
+ raise TypeError("nested dict keys must be strings")
174
+ encoded_key = key.encode("utf-8")
175
+ if len(encoded_key) > 65535:
176
+ raise ValueError("nested dict key is too long")
177
+ output.extend(struct.pack("<H", len(encoded_key)))
178
+ output.extend(encoded_key)
179
+ _write_value(output, item)
180
+
181
+
182
+ def _write_list(output: bytearray, value: Sequence[Any]) -> None:
183
+ output.append(_TAG_LIST)
184
+ output.extend(struct.pack("<I", len(value)))
185
+ for item in value:
186
+ _write_value(output, item)
187
+
188
+
189
+ def _read_value(reader: _Reader) -> Any:
190
+ tag = reader.read_u8()
191
+ if tag == _TAG_ARRAY:
192
+ return _read_array(reader)
193
+ if tag == _TAG_DICT:
194
+ return _read_dict(reader)
195
+ if tag == _TAG_LIST:
196
+ return _read_list(reader)
197
+ raise ValueError(f"unknown nested payload tag: {tag}")
198
+
199
+
200
+ def _read_array(reader: _Reader) -> np.ndarray[Any, Any] | str:
201
+ dtype_code = reader.read_u8()
202
+ ndim = reader.read_u8()
203
+ shape = tuple(reader.read_u64() for _ in range(ndim))
204
+
205
+ if dtype_code == _DTYPE_STRING:
206
+ if ndim != 0:
207
+ raise ValueError("nested string payload must be scalar")
208
+ length = reader.read_u32()
209
+ checksum = reader.read_u64()
210
+ reader.align_to_eight()
211
+ raw = reader.read(length)
212
+ _verify_checksum(raw, checksum)
213
+ return raw.decode("utf-8")
214
+
215
+ dtype = _DECODABLE_DTYPES.get(dtype_code)
216
+ if dtype is None:
217
+ raise ValueError(f"unknown nested dtype code: {dtype_code}")
218
+
219
+ checksum = reader.read_u64()
220
+ reader.align_to_eight()
221
+ item_count = math.prod(shape) if shape else 1
222
+ raw = reader.read(item_count * dtype.itemsize)
223
+ _verify_checksum(raw, checksum)
224
+ array = np.frombuffer(raw, dtype=dtype).copy()
225
+ return array.reshape(shape)
226
+
227
+
228
+ def _read_dict(reader: _Reader) -> dict[str, Any]:
229
+ count = reader.read_u32()
230
+ return {reader.read_key(): _read_value(reader) for _ in range(count)}
231
+
232
+
233
+ def _read_list(reader: _Reader) -> list[Any]:
234
+ count = reader.read_u32()
235
+ return [_read_value(reader) for _ in range(count)]
236
+
237
+
238
+ def _wire_dtype(dtype: np.dtype[Any]) -> tuple[int, np.dtype[Any]]:
239
+ normalized = np.dtype(dtype).newbyteorder("=")
240
+ try:
241
+ return _ENCODABLE_DTYPES[normalized]
242
+ except KeyError as exc:
243
+ raise TypeError(f"unsupported nested dtype: {dtype}") from exc
244
+
245
+
246
+ def _verify_checksum(data: bytes, checksum: int) -> None:
247
+ actual = xxhash.xxh64_intdigest(data, seed=_CHECKSUM_SEED)
248
+ if actual != checksum:
249
+ raise ValueError("nested payload checksum mismatch")
250
+
251
+
252
+ def _pad_to_eight(output: bytearray) -> None:
253
+ output.extend(b"\0" * (_align_to_eight(len(output)) - len(output)))
254
+
255
+
256
+ def _align_to_eight(offset: int) -> int:
257
+ return (offset + 7) & ~7
258
+
259
+
260
+ def _is_sequence(value: Any) -> bool:
261
+ return isinstance(value, Sequence) and not isinstance(
262
+ value, str | bytes | bytearray
263
+ )
@@ -0,0 +1,5 @@
1
+ # Generated protobuf modules
2
+ from river_client._proto import river_pb2 as pb2 # noqa: PLC0415
3
+ from river_client._proto import river_pb2_grpc as pb2_grpc # noqa: PLC0415
4
+
5
+ __all__ = ["pb2", "pb2_grpc"]