diff --git a/lib/python3.12/site-packages/nvidia/cufft/include/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/nvidia/cufft/include/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1635bc96f51bf93ee4f910b674d48286e89182da
Binary files /dev/null and b/lib/python3.12/site-packages/nvidia/cufft/include/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/nvidia/cufft/include/cufftXt.h b/lib/python3.12/site-packages/nvidia/cufft/include/cufftXt.h
new file mode 100644
index 0000000000000000000000000000000000000000..6a22724261d27ae0e5e894e4e2f60ef484b52bf2
--- /dev/null
+++ b/lib/python3.12/site-packages/nvidia/cufft/include/cufftXt.h
@@ -0,0 +1,259 @@
+
+ /* Copyright 2005-2021 NVIDIA Corporation. All rights reserved.
+ *
+ * NOTICE TO LICENSEE:
+ *
+ * The source code and/or documentation ("Licensed Deliverables") are
+ * subject to NVIDIA intellectual property rights under U.S. and
+ * international Copyright laws.
+ *
+ * The Licensed Deliverables contained herein are PROPRIETARY and
+ * CONFIDENTIAL to NVIDIA and are being provided under the terms and
+ * conditions of a form of NVIDIA software license agreement by and
+ * between NVIDIA and Licensee ("License Agreement") or electronically
+ * accepted by Licensee. Notwithstanding any terms or conditions to
+ * the contrary in the License Agreement, reproduction or disclosure
+ * of the Licensed Deliverables to any third party without the express
+ * written consent of NVIDIA is prohibited.
+ *
+ * NOTWITHSTANDING ANY TERMS OR CONDITIONS TO THE CONTRARY IN THE
+ * LICENSE AGREEMENT, NVIDIA MAKES NO REPRESENTATION ABOUT THE
+ * SUITABILITY OF THESE LICENSED DELIVERABLES FOR ANY PURPOSE. THEY ARE
+ * PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF ANY KIND.
+ * NVIDIA DISCLAIMS ALL WARRANTIES WITH REGARD TO THESE LICENSED
+ * DELIVERABLES, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY,
+ * NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE.
+ * NOTWITHSTANDING ANY TERMS OR CONDITIONS TO THE CONTRARY IN THE
+ * LICENSE AGREEMENT, IN NO EVENT SHALL NVIDIA BE LIABLE FOR ANY
+ * SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, OR ANY
+ * DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+ * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+ * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+ * OF THESE LICENSED DELIVERABLES.
+ *
+ * U.S. Government End Users. These Licensed Deliverables are a
+ * "commercial item" as that term is defined at 48 C.F.R. 2.101 (OCT
+ * 1995), consisting of "commercial computer software" and "commercial
+ * computer software documentation" as such terms are used in 48
+ * C.F.R. 12.212 (SEPT 1995) and are provided to the U.S. Government
+ * only as a commercial end item. Consistent with 48 C.F.R.12.212 and
+ * 48 C.F.R. 227.7202-1 through 227.7202-4 (JUNE 1995), all
+ * U.S. Government End Users acquire the Licensed Deliverables with
+ * only those rights set forth herein.
+ *
+ * Any use of the Licensed Deliverables in individual and commercial
+ * software must include, in the user documentation and internal
+ * comments to the code, the above Disclaimer and U.S. Government End
+ * Users Notice.
+ */
+
+/*!
+* \file cufftXt.h
+* \brief Public header file for the NVIDIA CUDA FFT library (CUFFT)
+*/
+
+#ifndef _CUFFTXT_H_
+#define _CUFFTXT_H_
+#include "cudalibxt.h"
+#include "cufft.h"
+
+
+#ifndef CUFFTAPI
+#ifdef _WIN32
+#define CUFFTAPI __stdcall
+#else
+#define CUFFTAPI
+#endif
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+//
+// cufftXtSubFormat identifies the data layout of
+// a memory descriptor owned by cufft.
+// note that multi GPU cufft does not yet support out-of-place transforms
+//
+
+typedef enum cufftXtSubFormat_t {
+ CUFFT_XT_FORMAT_INPUT = 0x00, //by default input is in linear order across GPUs
+ CUFFT_XT_FORMAT_OUTPUT = 0x01, //by default output is in scrambled order depending on transform
+ CUFFT_XT_FORMAT_INPLACE = 0x02, //by default inplace is input order, which is linear across GPUs
+ CUFFT_XT_FORMAT_INPLACE_SHUFFLED = 0x03, //shuffled output order after execution of the transform
+ CUFFT_XT_FORMAT_1D_INPUT_SHUFFLED = 0x04, //shuffled input order prior to execution of 1D transforms
+ CUFFT_XT_FORMAT_DISTRIBUTED_INPUT = 0x05,
+ CUFFT_XT_FORMAT_DISTRIBUTED_OUTPUT = 0x06,
+ CUFFT_FORMAT_UNDEFINED = 0x07
+} cufftXtSubFormat;
+
+//
+// cufftXtCopyType specifies the type of copy for cufftXtMemcpy
+//
+typedef enum cufftXtCopyType_t {
+ CUFFT_COPY_HOST_TO_DEVICE = 0x00,
+ CUFFT_COPY_DEVICE_TO_HOST = 0x01,
+ CUFFT_COPY_DEVICE_TO_DEVICE = 0x02,
+ CUFFT_COPY_UNDEFINED = 0x03
+} cufftXtCopyType;
+
+//
+// cufftXtQueryType specifies the type of query for cufftXtQueryPlan
+//
+typedef enum cufftXtQueryType_t {
+ CUFFT_QUERY_1D_FACTORS = 0x00,
+ CUFFT_QUERY_UNDEFINED = 0x01
+} cufftXtQueryType;
+
+typedef struct cufftXt1dFactors_t {
+ long long int size;
+ long long int stringCount;
+ long long int stringLength;
+ long long int substringLength;
+ long long int factor1;
+ long long int factor2;
+ long long int stringMask;
+ long long int substringMask;
+ long long int factor1Mask;
+ long long int factor2Mask;
+ int stringShift;
+ int substringShift;
+ int factor1Shift;
+ int factor2Shift;
+} cufftXt1dFactors;
+
+//
+// cufftXtWorkAreaPolicy specifies policy for cufftXtSetWorkAreaPolicy
+//
+typedef enum cufftXtWorkAreaPolicy_t {
+ CUFFT_WORKAREA_MINIMAL = 0, /* maximum reduction */
+ CUFFT_WORKAREA_USER = 1, /* use workSize parameter as limit */
+ CUFFT_WORKAREA_PERFORMANCE = 2, /* default - 1x overhead or more, maximum performance */
+} cufftXtWorkAreaPolicy;
+
+// multi-GPU routines
+cufftResult CUFFTAPI cufftXtSetGPUs(cufftHandle handle, int nGPUs, int *whichGPUs);
+
+cufftResult CUFFTAPI cufftXtMalloc(cufftHandle plan,
+ cudaLibXtDesc ** descriptor,
+ cufftXtSubFormat format);
+
+cufftResult CUFFTAPI cufftXtMemcpy(cufftHandle plan,
+ void *dstPointer,
+ void *srcPointer,
+ cufftXtCopyType type);
+
+cufftResult CUFFTAPI cufftXtFree(cudaLibXtDesc *descriptor);
+
+cufftResult CUFFTAPI cufftXtSetWorkArea(cufftHandle plan, void **workArea);
+
+cufftResult CUFFTAPI cufftXtExecDescriptorC2C(cufftHandle plan,
+ cudaLibXtDesc *input,
+ cudaLibXtDesc *output,
+ int direction);
+
+cufftResult CUFFTAPI cufftXtExecDescriptorR2C(cufftHandle plan,
+ cudaLibXtDesc *input,
+ cudaLibXtDesc *output);
+
+cufftResult CUFFTAPI cufftXtExecDescriptorC2R(cufftHandle plan,
+ cudaLibXtDesc *input,
+ cudaLibXtDesc *output);
+
+cufftResult CUFFTAPI cufftXtExecDescriptorZ2Z(cufftHandle plan,
+ cudaLibXtDesc *input,
+ cudaLibXtDesc *output,
+ int direction);
+
+cufftResult CUFFTAPI cufftXtExecDescriptorD2Z(cufftHandle plan,
+ cudaLibXtDesc *input,
+ cudaLibXtDesc *output);
+
+cufftResult CUFFTAPI cufftXtExecDescriptorZ2D(cufftHandle plan,
+ cudaLibXtDesc *input,
+ cudaLibXtDesc *output);
+
+// Utility functions
+
+cufftResult CUFFTAPI cufftXtQueryPlan(cufftHandle plan, void *queryStruct, cufftXtQueryType queryType);
+
+
+// callbacks
+
+
+typedef enum cufftXtCallbackType_t {
+ CUFFT_CB_LD_COMPLEX = 0x0,
+ CUFFT_CB_LD_COMPLEX_DOUBLE = 0x1,
+ CUFFT_CB_LD_REAL = 0x2,
+ CUFFT_CB_LD_REAL_DOUBLE = 0x3,
+ CUFFT_CB_ST_COMPLEX = 0x4,
+ CUFFT_CB_ST_COMPLEX_DOUBLE = 0x5,
+ CUFFT_CB_ST_REAL = 0x6,
+ CUFFT_CB_ST_REAL_DOUBLE = 0x7,
+ CUFFT_CB_UNDEFINED = 0x8
+
+} cufftXtCallbackType;
+
+typedef cufftComplex (*cufftCallbackLoadC)(void *dataIn, size_t offset, void *callerInfo, void *sharedPointer);
+typedef cufftDoubleComplex (*cufftCallbackLoadZ)(void *dataIn, size_t offset, void *callerInfo, void *sharedPointer);
+typedef cufftReal (*cufftCallbackLoadR)(void *dataIn, size_t offset, void *callerInfo, void *sharedPointer);
+typedef cufftDoubleReal(*cufftCallbackLoadD)(void *dataIn, size_t offset, void *callerInfo, void *sharedPointer);
+
+typedef void (*cufftCallbackStoreC)(void *dataOut, size_t offset, cufftComplex element, void *callerInfo, void *sharedPointer);
+typedef void (*cufftCallbackStoreZ)(void *dataOut, size_t offset, cufftDoubleComplex element, void *callerInfo, void *sharedPointer);
+typedef void (*cufftCallbackStoreR)(void *dataOut, size_t offset, cufftReal element, void *callerInfo, void *sharedPointer);
+typedef void (*cufftCallbackStoreD)(void *dataOut, size_t offset, cufftDoubleReal element, void *callerInfo, void *sharedPointer);
+
+
+cufftResult CUFFTAPI cufftXtSetCallback(cufftHandle plan, void **callback_routine, cufftXtCallbackType cbType, void **caller_info);
+cufftResult CUFFTAPI cufftXtClearCallback(cufftHandle plan, cufftXtCallbackType cbType);
+cufftResult CUFFTAPI cufftXtSetCallbackSharedSize(cufftHandle plan, cufftXtCallbackType cbType, size_t sharedSize);
+
+cufftResult CUFFTAPI cufftXtMakePlanMany(cufftHandle plan,
+ int rank,
+ long long int *n,
+ long long int *inembed,
+ long long int istride,
+ long long int idist,
+ cudaDataType inputtype,
+ long long int *onembed,
+ long long int ostride,
+ long long int odist,
+ cudaDataType outputtype,
+ long long int batch,
+ size_t *workSize,
+ cudaDataType executiontype);
+
+cufftResult CUFFTAPI cufftXtGetSizeMany(cufftHandle plan,
+ int rank,
+ long long int *n,
+ long long int *inembed,
+ long long int istride,
+ long long int idist,
+ cudaDataType inputtype,
+ long long int *onembed,
+ long long int ostride,
+ long long int odist,
+ cudaDataType outputtype,
+ long long int batch,
+ size_t *workSize,
+ cudaDataType executiontype);
+
+
+cufftResult CUFFTAPI cufftXtExec(cufftHandle plan,
+ void *input,
+ void *output,
+ int direction);
+
+cufftResult CUFFTAPI cufftXtExecDescriptor(cufftHandle plan,
+ cudaLibXtDesc *input,
+ cudaLibXtDesc *output,
+ int direction);
+
+cufftResult CUFFTAPI cufftXtSetWorkAreaPolicy(cufftHandle plan, cufftXtWorkAreaPolicy policy, size_t *workSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/python3.12/site-packages/nvidia/cufft/lib/__init__.py b/lib/python3.12/site-packages/nvidia/cufft/lib/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/nvidia/cufft/lib/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/nvidia/cufft/lib/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c983b85c38a8923a7caff6543d7c788a3f35d207
Binary files /dev/null and b/lib/python3.12/site-packages/nvidia/cufft/lib/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__init__.py b/lib/python3.12/site-packages/ray/_common/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..21910318d6c3aef7faf246dfdec9c95be1b0ca7d
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/constants.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/constants.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e9a5c354fedeb40853f615478d3f287a6ddc0769
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/constants.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/deprecation.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/deprecation.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..93e8cb98b07292fe2ce4899f4b2f1377a9917684
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/deprecation.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/filters.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/filters.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..49fdc2d32eae9a88f32a808c1566496ce235e562
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/filters.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/formatters.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/formatters.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8265a5beffe2b8e5500f5aeb44d99b6190d04f1b
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/formatters.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/network_utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/network_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..62187743da4d062484e122b77bf8d8c4a12d7256
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/network_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/pydantic_compat.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/pydantic_compat.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bcb67f992fd04adb5ab6fcc5c577f77a20494ac3
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/pydantic_compat.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/ray_constants.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/ray_constants.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b8401d86a1c111d8c05d36aa4d099687328e774d
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/ray_constants.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/ray_option_utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/ray_option_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b7ef363120993118a6ef38abaead3b180993cafa
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/ray_option_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/retry.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/retry.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..dc46c7f193c6ae0930e5eb09c7c20bdae11844c4
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/retry.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/serialization.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/serialization.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..aeb1b9dc58e19635c2b85f27d92320200922e541
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/serialization.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/signature.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/signature.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..aa02eaa29a0b5106d64c4c2b4cf9ab5639cbb168
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/signature.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/test_utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/test_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..bbd8587f32893fbb5e46ccc3f0b212245940f445
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/test_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/__pycache__/utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/__pycache__/utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f8ebee22824ed7d3f7c0a9c41769a635aab79d46
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/__pycache__/utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/constants.py b/lib/python3.12/site-packages/ray/_common/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..c9ca78de99f5493adcf42653908413d5f8e26a1d
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/constants.py
@@ -0,0 +1,7 @@
+# Prefix for the node id resource that is automatically added to each node.
+# For example, a node may have id `node:172.23.42.1`.
+NODE_ID_PREFIX = "node:"
+# The system resource that head node has.
+HEAD_NODE_RESOURCE_NAME = NODE_ID_PREFIX + "__internal_head__"
+
+RAY_WARN_BLOCKING_GET_INSIDE_ASYNC_ENV_VAR = "RAY_WARN_BLOCKING_GET_INSIDE_ASYNC"
diff --git a/lib/python3.12/site-packages/ray/_common/deprecation.py b/lib/python3.12/site-packages/ray/_common/deprecation.py
new file mode 100644
index 0000000000000000000000000000000000000000..01c0bd62467c5745c3c9890061abdeaef2c9099f
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/deprecation.py
@@ -0,0 +1,141 @@
+import inspect
+import logging
+from typing import Optional, Union
+
+from ray.util import log_once
+from ray.util.annotations import _mark_annotated
+
+logger = logging.getLogger(__name__)
+
+# A constant to use for any configuration that should be deprecated
+# (to check, whether this config has actually been assigned a proper value or
+# not).
+DEPRECATED_VALUE = -1
+
+
+def deprecation_warning(
+ old: str,
+ new: Optional[str] = None,
+ *,
+ help: Optional[str] = None,
+ error: Optional[Union[bool, Exception]] = None,
+ stacklevel: int = 2,
+) -> None:
+ """Warns (via the `logger` object) or throws a deprecation warning/error.
+
+ Args:
+ old: A description of the "thing" that is to be deprecated.
+ new: A description of the new "thing" that replaces it.
+ help: An optional help text to tell the user, what to
+ do instead of using `old`.
+ error: Whether or which exception to raise. If True, raise ValueError.
+ If False, just warn. If `error` is-a subclass of Exception,
+ raise that Exception.
+ stacklevel: The stacklevel to use for the warning message.
+ Use 2 to point to where this function is called, 3+ to point
+ further up the stack.
+
+ Raises:
+ ValueError: If `error=True`.
+ Exception: Of type `error`, iff `error` is a sub-class of `Exception`.
+ """
+ msg = "`{}` has been deprecated.{}".format(
+ old, (" Use `{}` instead.".format(new) if new else f" {help}" if help else "")
+ )
+
+ if error:
+ if not isinstance(error, bool) and issubclass(error, Exception):
+ # error is an Exception
+ raise error(msg)
+ else:
+ # error is a boolean, construct ValueError ourselves
+ raise ValueError(msg)
+ else:
+ logger.warning(
+ "DeprecationWarning: " + msg + " This will raise an error in the future!",
+ stacklevel=stacklevel,
+ )
+
+
+def Deprecated(old=None, *, new=None, help=None, error):
+ """Decorator for documenting a deprecated class, method, or function.
+
+ Automatically adds a `deprecation.deprecation_warning(old=...,
+ error=False)` to not break existing code at this point to the decorated
+ class' constructor, method, or function.
+
+ In a next major release, this warning should then be made an error
+ (by setting error=True), which means at this point that the
+ class/method/function is no longer supported, but will still inform
+ the user about the deprecation event.
+
+ In a further major release, the class, method, function should be erased
+ entirely from the codebase.
+
+
+ .. testcode::
+ :skipif: True
+
+ from ray._common.deprecation import Deprecated
+ # Deprecated class: Patches the constructor to warn if the class is
+ # used.
+ @Deprecated(new="NewAndMuchCoolerClass", error=False)
+ class OldAndUncoolClass:
+ ...
+
+ # Deprecated class method: Patches the method to warn if called.
+ class StillCoolClass:
+ ...
+ @Deprecated(new="StillCoolClass.new_and_much_cooler_method()",
+ error=False)
+ def old_and_uncool_method(self, uncool_arg):
+ ...
+
+ # Deprecated function: Patches the function to warn if called.
+ @Deprecated(new="new_and_much_cooler_function", error=False)
+ def old_and_uncool_function(*uncool_args):
+ ...
+ """
+
+ def _inner(obj):
+ # A deprecated class.
+ if inspect.isclass(obj):
+ # Patch the class' init method to raise the warning/error.
+ obj_init = obj.__init__
+
+ def patched_init(*args, **kwargs):
+ if log_once(old or obj.__name__):
+ deprecation_warning(
+ old=old or obj.__name__,
+ new=new,
+ help=help,
+ error=error,
+ stacklevel=3,
+ )
+ return obj_init(*args, **kwargs)
+
+ obj.__init__ = patched_init
+ _mark_annotated(obj)
+ # Return the patched class (with the warning/error when
+ # instantiated).
+ return obj
+
+ # A deprecated class method or function.
+ # Patch with the warning/error at the beginning.
+ def _ctor(*args, **kwargs):
+ if log_once(old or obj.__name__):
+ deprecation_warning(
+ old=old or obj.__name__,
+ new=new,
+ help=help,
+ error=error,
+ stacklevel=3,
+ )
+ # Call the deprecated method/function.
+ return obj(*args, **kwargs)
+
+ # Return the patched class method/function.
+ return _ctor
+
+ # Return the prepared decorator.
+ return _inner
diff --git a/lib/python3.12/site-packages/ray/_common/filters.py b/lib/python3.12/site-packages/ray/_common/filters.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3c8219b540997239e4c925dda7189a3f214e9ca
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/filters.py
@@ -0,0 +1,55 @@
+import logging
+from typing import Any, Dict
+
+import ray
+from ray._private.ray_logging.constants import LogKey
+
+
+class CoreContextFilter(logging.Filter):
+ TASK_LEVEL_LOG_KEYS = [
+ LogKey.TASK_ID.value,
+ LogKey.TASK_NAME.value,
+ LogKey.TASK_FUNCTION_NAME.value,
+ ]
+
+ @classmethod
+ def get_ray_core_logging_context(cls) -> Dict[str, Any]:
+ """
+ Get the ray core logging context as a dict.
+ Only use this function if you need include the attributes to the log record
+ yourself by bypassing the filter.
+ """
+ if not ray.is_initialized():
+ # There is no additional context if ray is not initialized
+ return {}
+
+ runtime_context = ray.get_runtime_context()
+ ray_core_logging_context = {
+ LogKey.JOB_ID.value: runtime_context.get_job_id(),
+ LogKey.WORKER_ID.value: runtime_context.get_worker_id(),
+ LogKey.NODE_ID.value: runtime_context.get_node_id(),
+ }
+ if runtime_context.worker.mode == ray.WORKER_MODE:
+ ray_core_logging_context[
+ LogKey.ACTOR_ID.value
+ ] = runtime_context.get_actor_id()
+ ray_core_logging_context[
+ LogKey.TASK_ID.value
+ ] = runtime_context.get_task_id()
+ ray_core_logging_context[
+ LogKey.TASK_NAME.value
+ ] = runtime_context.get_task_name()
+ ray_core_logging_context[
+ LogKey.TASK_FUNCTION_NAME.value
+ ] = runtime_context.get_task_function_name()
+ ray_core_logging_context[
+ LogKey.ACTOR_NAME.value
+ ] = runtime_context.get_actor_name()
+ return ray_core_logging_context
+
+ def filter(self, record):
+ context = self.get_ray_core_logging_context()
+ for key, value in context.items():
+ if value is not None:
+ setattr(record, key, value)
+ return True
diff --git a/lib/python3.12/site-packages/ray/_common/formatters.py b/lib/python3.12/site-packages/ray/_common/formatters.py
new file mode 100644
index 0000000000000000000000000000000000000000..bf67a309bb5c5d33bb7836f36a8a1ac15120bd85
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/formatters.py
@@ -0,0 +1,120 @@
+import json
+import logging
+from abc import ABC, abstractmethod
+from typing import Any, Dict, List
+
+from ray._private.log import INTERNAL_TIMESTAMP_LOG_KEY
+from ray._private.ray_constants import LOGGER_FORMAT
+from ray._private.ray_logging.constants import (
+ LOGGER_FLATTEN_KEYS,
+ LOGRECORD_STANDARD_ATTRS,
+ LogKey,
+)
+
+
+def _append_flatten_attributes(formatted_attrs: Dict[str, Any], key: str, value: Any):
+ """Flatten the dictionary values for special keys and append the values in place.
+
+ If the key is in `LOGGER_FLATTEN_KEYS`, the value will be flattened and appended
+ to the `formatted_attrs` dictionary. Otherwise, the key-value pair will be appended
+ directly.
+ """
+ if key in LOGGER_FLATTEN_KEYS:
+ if not isinstance(value, dict):
+ raise ValueError(
+ f"Expected a dictionary passing into {key}, but got {type(value)}"
+ )
+ for k, v in value.items():
+ if k in formatted_attrs:
+ raise KeyError(f"Found duplicated key in the log record: {k}")
+ formatted_attrs[k] = v
+ else:
+ formatted_attrs[key] = value
+
+
+class AbstractFormatter(logging.Formatter, ABC):
+ def __init__(self, fmt=None, datefmt=None, style="%", validate=True) -> None:
+ super().__init__(fmt, datefmt, style, validate)
+ self._additional_log_standard_attrs = []
+
+ def set_additional_log_standard_attrs(
+ self, additional_log_standard_attrs: List[str]
+ ) -> None:
+ self._additional_log_standard_attrs = additional_log_standard_attrs
+
+ @property
+ def additional_log_standard_attrs(self) -> List[str]:
+ return self._additional_log_standard_attrs
+
+ def generate_record_format_attrs(
+ self,
+ record: logging.LogRecord,
+ exclude_default_standard_attrs,
+ ) -> dict:
+ record_format_attrs = {}
+
+ # If `exclude_default_standard_attrs` is False, include the standard attributes.
+ # Otherwise, include only Ray and user-provided context.
+ if not exclude_default_standard_attrs:
+ record_format_attrs.update(
+ {
+ LogKey.ASCTIME.value: self.formatTime(record),
+ LogKey.LEVELNAME.value: record.levelname,
+ LogKey.MESSAGE.value: record.getMessage(),
+ LogKey.FILENAME.value: record.filename,
+ LogKey.LINENO.value: record.lineno,
+ LogKey.PROCESS.value: record.process,
+ }
+ )
+ if record.exc_info:
+ if not record.exc_text:
+ record.exc_text = self.formatException(record.exc_info)
+ record_format_attrs[LogKey.EXC_TEXT.value] = record.exc_text
+
+ # Add the user specified additional standard attributes.
+ for key in self._additional_log_standard_attrs:
+ _append_flatten_attributes(
+ record_format_attrs, key, getattr(record, key, None)
+ )
+
+ for key, value in record.__dict__.items():
+ # Both Ray and user-provided context are stored in `record_format`.
+ if key not in LOGRECORD_STANDARD_ATTRS:
+ _append_flatten_attributes(record_format_attrs, key, value)
+
+ # Format the internal timestamp to the standardized `timestamp_ns` key.
+ if INTERNAL_TIMESTAMP_LOG_KEY in record_format_attrs:
+ record_format_attrs[LogKey.TIMESTAMP_NS.value] = record_format_attrs.pop(
+ INTERNAL_TIMESTAMP_LOG_KEY
+ )
+
+ return record_format_attrs
+
+ @abstractmethod
+ def format(self, record: logging.LogRecord) -> str:
+ pass
+
+
+class JSONFormatter(AbstractFormatter):
+ def format(self, record: logging.LogRecord) -> str:
+ record_format_attrs = self.generate_record_format_attrs(
+ record, exclude_default_standard_attrs=False
+ )
+ return json.dumps(record_format_attrs)
+
+
+class TextFormatter(AbstractFormatter):
+ def __init__(self, fmt=None, datefmt=None, style="%", validate=True) -> None:
+ super().__init__(fmt, datefmt, style, validate)
+ self._inner_formatter = logging.Formatter(LOGGER_FORMAT)
+
+ def format(self, record: logging.LogRecord) -> str:
+ s = self._inner_formatter.format(record)
+ record_format_attrs = self.generate_record_format_attrs(
+ record, exclude_default_standard_attrs=True
+ )
+
+ additional_attrs = " ".join(
+ [f"{key}={value}" for key, value in record_format_attrs.items()]
+ )
+ return f"{s} {additional_attrs}"
diff --git a/lib/python3.12/site-packages/ray/_common/network_utils.py b/lib/python3.12/site-packages/ray/_common/network_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..593407b5c3775ef06d55b2a812f9c7ad63fb211d
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/network_utils.py
@@ -0,0 +1,111 @@
+import socket
+from contextlib import closing
+from functools import lru_cache
+from typing import Optional, Tuple, Union
+
+from ray._raylet import (
+ build_address as _build_address,
+ is_ipv6 as _is_ipv6,
+ node_ip_address_from_perspective as _node_ip_address_from_perspective,
+ parse_address as _parse_address,
+)
+
+
+def parse_address(address: str) -> Optional[Tuple[str, str]]:
+ """Parse a network address string into host and port.
+
+ Args:
+ address: The address string to parse (e.g., "localhost:8000", "[::1]:8000").
+
+ Returns:
+ Tuple with (host, port) if port found, None if no colon separator.
+ """
+ return _parse_address(address)
+
+
+def build_address(host: str, port: Union[int, str]) -> str:
+ """Build a network address string from host and port.
+
+ Args:
+ host: The hostname or IP address.
+ port: The port number (int or string).
+
+ Returns:
+ Formatted address string (e.g., "localhost:8000" or "[::1]:8000").
+ """
+ return _build_address(host, port)
+
+
+def node_ip_address_from_perspective(address: Optional[str] = None) -> str:
+ """IP address by which the local node can be reached *from* the `address`.
+
+ If no address is given, defaults to public DNS servers for detection.
+
+ Args:
+ address: The IP address and port of any known live service on the
+ network you care about.
+
+ Returns:
+ The IP address by which the local node can be reached from the address.
+ """
+ return _node_ip_address_from_perspective(address)
+
+
+def is_ipv6(host: str) -> bool:
+ """Check if a host is resolved to IPv6.
+
+ Args:
+ host: The IP or domain name to check (must be without port).
+
+ Returns:
+ True if the host is resolved to IPv6, False if IPv4.
+ """
+ return _is_ipv6(host)
+
+
+@lru_cache(maxsize=1)
+def get_localhost_ip() -> str:
+ """Get localhost loopback ip with IPv4/IPv6 support.
+
+ Returns:
+ The localhost loopback IP.
+ """
+ # Try IPv4 first, then IPv6 localhost resolution
+ for family in [socket.AF_INET, socket.AF_INET6]:
+ try:
+ dns_result = socket.getaddrinfo(
+ "localhost", None, family, socket.SOCK_STREAM
+ )
+ return dns_result[0][4][0]
+ except Exception:
+ continue
+
+ # Final fallback to IPv4 loopback
+ return "127.0.0.1"
+
+
+def is_localhost(host: str) -> bool:
+ """Check if the given host string represents a localhost address.
+
+ Args:
+ host: The hostname or IP address to check.
+
+ Returns:
+ True if the host is a localhost address, False otherwise.
+ """
+ return host in ("localhost", "127.0.0.1", "::1")
+
+
+def find_free_port(family: socket.AddressFamily = socket.AF_INET) -> int:
+ """Find a free port on the local machine.
+
+ Args:
+ family: The socket address family (AF_INET for IPv4, AF_INET6 for IPv6).
+ Defaults to AF_INET.
+
+ Returns:
+ An available port number.
+ """
+ with closing(socket.socket(family, socket.SOCK_STREAM)) as s:
+ s.bind(("", 0))
+ return s.getsockname()[1]
diff --git a/lib/python3.12/site-packages/ray/_common/pydantic_compat.py b/lib/python3.12/site-packages/ray/_common/pydantic_compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..b405e64ffa8ff77aa3eeb694961bbe141e3a6dd3
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/pydantic_compat.py
@@ -0,0 +1,108 @@
+# ruff: noqa
+import packaging.version
+
+# Pydantic is a dependency of `ray["default"]` but not the minimal installation,
+# so handle the case where it isn't installed.
+try:
+ import pydantic
+
+ PYDANTIC_INSTALLED = True
+except ImportError:
+ pydantic = None
+ PYDANTIC_INSTALLED = False
+
+
+if not PYDANTIC_INSTALLED:
+ IS_PYDANTIC_2 = False
+ BaseModel = None
+ Extra = None
+ Field = None
+ NonNegativeFloat = None
+ NonNegativeInt = None
+ PositiveFloat = None
+ PositiveInt = None
+ PrivateAttr = None
+ StrictInt = None
+ ValidationError = None
+ root_validator = None
+ validator = None
+ is_subclass_of_base_model = lambda obj: False
+# In pydantic <1.9.0, __version__ attribute is missing, issue ref:
+# https://github.com/pydantic/pydantic/issues/2572, so we need to check
+# the existence prior to comparison.
+elif not hasattr(pydantic, "__version__") or packaging.version.parse(
+ pydantic.__version__
+) < packaging.version.parse("2.0"):
+ IS_PYDANTIC_2 = False
+ from pydantic import (
+ BaseModel,
+ Extra,
+ Field,
+ NonNegativeFloat,
+ NonNegativeInt,
+ PositiveFloat,
+ PositiveInt,
+ PrivateAttr,
+ StrictInt,
+ ValidationError,
+ root_validator,
+ validator,
+ )
+
+ def is_subclass_of_base_model(obj):
+ return issubclass(obj, BaseModel)
+
+else:
+ IS_PYDANTIC_2 = True
+ from pydantic.v1 import (
+ BaseModel,
+ Extra,
+ Field,
+ NonNegativeFloat,
+ NonNegativeInt,
+ PositiveFloat,
+ PositiveInt,
+ PrivateAttr,
+ StrictInt,
+ ValidationError,
+ root_validator,
+ validator,
+ )
+
+ def is_subclass_of_base_model(obj):
+ from pydantic import BaseModel as BaseModelV2
+ from pydantic.v1 import BaseModel as BaseModelV1
+
+ return issubclass(obj, BaseModelV1) or issubclass(obj, BaseModelV2)
+
+
+def register_pydantic_serializers(serialization_context):
+ if not PYDANTIC_INSTALLED:
+ return
+
+ if IS_PYDANTIC_2:
+ # TODO(edoakes): compare against the version that has the fixes.
+ from pydantic.v1.fields import ModelField
+ else:
+ from pydantic.fields import ModelField
+
+ # Pydantic's Cython validators are not serializable.
+ # https://github.com/cloudpipe/cloudpickle/issues/408
+ serialization_context._register_cloudpickle_serializer(
+ ModelField,
+ custom_serializer=lambda o: {
+ "name": o.name,
+ # outer_type_ is the original type for ModelFields,
+ # while type_ can be updated later with the nested type
+ # like int for List[int].
+ "type_": o.outer_type_,
+ "class_validators": o.class_validators,
+ "model_config": o.model_config,
+ "default": o.default,
+ "default_factory": o.default_factory,
+ "required": o.required,
+ "alias": o.alias,
+ "field_info": o.field_info,
+ },
+ custom_deserializer=lambda kwargs: ModelField(**kwargs),
+ )
diff --git a/lib/python3.12/site-packages/ray/_common/ray_constants.py b/lib/python3.12/site-packages/ray/_common/ray_constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..0c8c65269bb41d99ce17f6cf093fca27e47c197e
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/ray_constants.py
@@ -0,0 +1,5 @@
+# Default max_concurrency option in @ray.remote for async actors.
+DEFAULT_MAX_CONCURRENCY_ASYNC = 1000
+
+LOGGING_ROTATE_BYTES = 512 * 1024 * 1024 # 512MB.
+LOGGING_ROTATE_BACKUP_COUNT = 5 # 5 Backup files at max.
diff --git a/lib/python3.12/site-packages/ray/_common/ray_option_utils.py b/lib/python3.12/site-packages/ray/_common/ray_option_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..85a972881d639f07c01f247cf0a65bc55d63b494
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/ray_option_utils.py
@@ -0,0 +1,384 @@
+"""Manage, parse and validate options for Ray tasks, actors and actor methods."""
+import warnings
+from dataclasses import dataclass
+from typing import Any, Callable, Dict, Optional, Tuple, Union
+
+import ray
+from ray._private import ray_constants
+from ray._private.label_utils import (
+ validate_fallback_strategy,
+ validate_label_selector,
+)
+from ray._private.utils import get_ray_doc_version
+from ray.util.placement_group import PlacementGroup
+from ray.util.scheduling_strategies import (
+ NodeAffinitySchedulingStrategy,
+ NodeLabelSchedulingStrategy,
+ PlacementGroupSchedulingStrategy,
+)
+
+
+@dataclass
+class Option:
+ # Type constraint of an option.
+ type_constraint: Optional[Union[type, Tuple[type]]] = None
+ # Value constraint of an option.
+ # The callable should return None if there is no error.
+ # Otherwise, return the error message.
+ value_constraint: Optional[Callable[[Any], Optional[str]]] = None
+ # Default value.
+ default_value: Any = None
+
+ def validate(self, keyword: str, value: Any):
+ """Validate the option."""
+ if self.type_constraint is not None:
+ if not isinstance(value, self.type_constraint):
+ raise TypeError(
+ f"The type of keyword '{keyword}' must be {self.type_constraint}, "
+ f"but received type {type(value)}"
+ )
+ if self.value_constraint is not None:
+ possible_error_message = self.value_constraint(value)
+ if possible_error_message:
+ raise ValueError(possible_error_message)
+
+
+def _counting_option(name: str, infinite: bool = True, default_value: Any = None):
+ """This is used for positive and discrete options.
+
+ Args:
+ name: The name of the option keyword.
+ infinite: If True, user could use -1 to represent infinity.
+ default_value: The default value for this option.
+
+ Returns:
+ An Option object.
+ """
+ if infinite:
+ return Option(
+ (int, type(None)),
+ lambda x: None
+ if (x is None or x >= -1)
+ else f"The keyword '{name}' only accepts None, 0, -1"
+ " or a positive integer, where -1 represents infinity.",
+ default_value=default_value,
+ )
+ return Option(
+ (int, type(None)),
+ lambda x: None
+ if (x is None or x >= 0)
+ else f"The keyword '{name}' only accepts None, 0 or a positive integer.",
+ default_value=default_value,
+ )
+
+
+def _validate_resource_quantity(name, quantity):
+ if quantity < 0:
+ return f"The quantity of resource {name} cannot be negative"
+ if (
+ isinstance(quantity, float)
+ and quantity != 0.0
+ and int(quantity * ray._raylet.RESOURCE_UNIT_SCALING) == 0
+ ):
+ return (
+ f"The precision of the fractional quantity of resource {name}"
+ " cannot go beyond 0.0001"
+ )
+ resource_name = "GPU" if name == "num_gpus" else name
+ if resource_name in ray._private.accelerators.get_all_accelerator_resource_names():
+ (
+ valid,
+ error_message,
+ ) = ray._private.accelerators.get_accelerator_manager_for_resource(
+ resource_name
+ ).validate_resource_request_quantity(
+ quantity
+ )
+ if not valid:
+ return error_message
+ return None
+
+
+def _resource_option(name: str, default_value: Any = None):
+ """This is used for resource related options."""
+ return Option(
+ (float, int, type(None)),
+ lambda x: None if (x is None) else _validate_resource_quantity(name, x),
+ default_value=default_value,
+ )
+
+
+def _validate_resources(resources: Optional[Dict[str, float]]) -> Optional[str]:
+ if resources is None:
+ return None
+
+ if "CPU" in resources or "GPU" in resources:
+ return (
+ "Use the 'num_cpus' and 'num_gpus' keyword instead of 'CPU' and 'GPU' "
+ "in 'resources' keyword"
+ )
+
+ for name, quantity in resources.items():
+ possible_error_message = _validate_resource_quantity(name, quantity)
+ if possible_error_message:
+ return possible_error_message
+
+ return None
+
+
+_common_options = {
+ "label_selector": Option((dict, type(None)), lambda x: validate_label_selector(x)),
+ "fallback_strategy": Option(
+ (list, type(None)), lambda x: validate_fallback_strategy(x)
+ ),
+ "accelerator_type": Option((str, type(None))),
+ "memory": _resource_option("memory"),
+ "name": Option((str, type(None))),
+ "num_cpus": _resource_option("num_cpus"),
+ "num_gpus": _resource_option("num_gpus"),
+ "object_store_memory": _counting_option("object_store_memory", False),
+ # TODO(suquark): "placement_group", "placement_group_bundle_index"
+ # and "placement_group_capture_child_tasks" are deprecated,
+ # use "scheduling_strategy" instead.
+ "placement_group": Option(
+ (type(None), str, PlacementGroup), default_value="default"
+ ),
+ "placement_group_bundle_index": Option(int, default_value=-1),
+ "placement_group_capture_child_tasks": Option((bool, type(None))),
+ "resources": Option((dict, type(None)), lambda x: _validate_resources(x)),
+ "runtime_env": Option((dict, type(None))),
+ "scheduling_strategy": Option(
+ (
+ type(None),
+ str,
+ PlacementGroupSchedulingStrategy,
+ NodeAffinitySchedulingStrategy,
+ NodeLabelSchedulingStrategy,
+ )
+ ),
+ "enable_task_events": Option(bool, default_value=True),
+ "_labels": Option((dict, type(None))),
+}
+
+
+def issubclass_safe(obj: Any, cls_: type) -> bool:
+ try:
+ return issubclass(obj, cls_)
+ except TypeError:
+ return False
+
+
+_task_only_options = {
+ "max_calls": _counting_option("max_calls", False, default_value=0),
+ # Normal tasks may be retried on failure this many times.
+ # TODO(swang): Allow this to be set globally for an application.
+ "max_retries": _counting_option(
+ "max_retries", default_value=ray_constants.DEFAULT_TASK_MAX_RETRIES
+ ),
+ # override "_common_options"
+ "num_cpus": _resource_option("num_cpus", default_value=1),
+ "num_returns": Option(
+ (int, str, type(None)),
+ lambda x: None
+ if (x is None or x == "dynamic" or x == "streaming" or x >= 0)
+ else "Default None. When None is passed, "
+ "The default value is 1 for a task and actor task, and "
+ "'streaming' for generator tasks and generator actor tasks. "
+ "The keyword 'num_returns' only accepts None, "
+ "a non-negative integer, "
+ "'streaming' (for generators), or 'dynamic'. 'dynamic' flag "
+ "will be deprecated in the future, and it is recommended to use "
+ "'streaming' instead.",
+ default_value=None,
+ ),
+ "object_store_memory": Option( # override "_common_options"
+ (int, type(None)),
+ lambda x: None
+ if (x is None)
+ else "Setting 'object_store_memory' is not implemented for tasks",
+ ),
+ "retry_exceptions": Option(
+ (bool, list, tuple),
+ lambda x: None
+ if (
+ isinstance(x, bool)
+ or (
+ isinstance(x, (list, tuple))
+ and all(issubclass_safe(x_, Exception) for x_ in x)
+ )
+ )
+ else "retry_exceptions must be either a boolean or a list of exceptions",
+ default_value=False,
+ ),
+ "_generator_backpressure_num_objects": Option(
+ (int, type(None)),
+ lambda x: None
+ if x != 0
+ else (
+ "_generator_backpressure_num_objects=0 is not allowed. "
+ "Use a value > 0. If the value is equal to 1, the behavior "
+ "is identical to Python generator (generator 1 object "
+ "whenever `next` is called). Use -1 to disable this feature. "
+ ),
+ ),
+}
+
+_actor_only_options = {
+ "concurrency_groups": Option((list, dict, type(None))),
+ "enable_tensor_transport": Option((bool, type(None)), default_value=None),
+ "lifetime": Option(
+ (str, type(None)),
+ lambda x: None
+ if x in (None, "detached", "non_detached")
+ else "actor `lifetime` argument must be one of 'detached', "
+ "'non_detached' and 'None'.",
+ ),
+ "max_concurrency": _counting_option("max_concurrency", False),
+ "max_restarts": _counting_option("max_restarts", default_value=0),
+ "max_task_retries": _counting_option("max_task_retries", default_value=0),
+ "max_pending_calls": _counting_option("max_pending_calls", default_value=-1),
+ "namespace": Option((str, type(None))),
+ "get_if_exists": Option(bool, default_value=False),
+ "allow_out_of_order_execution": Option((bool, type(None))),
+}
+
+# Priority is important here because during dictionary update, same key with higher
+# priority overrides the same key with lower priority. We make use of priority
+# to set the correct default value for tasks / actors.
+
+# priority: _common_options > _actor_only_options > _task_only_options
+valid_options: Dict[str, Option] = {
+ **_task_only_options,
+ **_actor_only_options,
+ **_common_options,
+}
+# priority: _task_only_options > _common_options
+task_options: Dict[str, Option] = {**_common_options, **_task_only_options}
+# priority: _actor_only_options > _common_options
+actor_options: Dict[str, Option] = {**_common_options, **_actor_only_options}
+
+remote_args_error_string = (
+ "The @ray.remote decorator must be applied either with no arguments and no "
+ "parentheses, for example '@ray.remote', or it must be applied using some of "
+ f"the arguments in the list {list(valid_options.keys())}, for example "
+ "'@ray.remote(num_returns=2, resources={\"CustomResource\": 1})'."
+)
+
+
+def _check_deprecate_placement_group(options: Dict[str, Any]):
+ """Check if deprecated placement group option exists."""
+ placement_group = options.get("placement_group", "default")
+ scheduling_strategy = options.get("scheduling_strategy")
+ # TODO(suquark): @ray.remote(placement_group=None) is used in
+ # "python/ray.data._internal/remote_fn.py" and many other places,
+ # while "ray.data.read_api.read_datasource" set "scheduling_strategy=SPREAD".
+ # This might be a bug, but it is also ok to allow them co-exist.
+ if (placement_group not in ("default", None)) and (scheduling_strategy is not None):
+ raise ValueError(
+ "Placement groups should be specified via the "
+ "scheduling_strategy option. "
+ "The placement_group option is deprecated."
+ )
+
+
+def _warn_if_using_deprecated_placement_group(
+ options: Dict[str, Any], caller_stacklevel: int
+):
+ placement_group = options["placement_group"]
+ placement_group_bundle_index = options["placement_group_bundle_index"]
+ placement_group_capture_child_tasks = options["placement_group_capture_child_tasks"]
+ if placement_group != "default":
+ warnings.warn(
+ "placement_group parameter is deprecated. Use "
+ "scheduling_strategy=PlacementGroupSchedulingStrategy(...) "
+ "instead, see the usage at "
+ f"https://docs.ray.io/en/{get_ray_doc_version()}/ray-core/package-ref.html#ray-remote.", # noqa: E501
+ DeprecationWarning,
+ stacklevel=caller_stacklevel + 1,
+ )
+ if placement_group_bundle_index != -1:
+ warnings.warn(
+ "placement_group_bundle_index parameter is deprecated. Use "
+ "scheduling_strategy=PlacementGroupSchedulingStrategy(...) "
+ "instead, see the usage at "
+ f"https://docs.ray.io/en/{get_ray_doc_version()}/ray-core/package-ref.html#ray-remote.", # noqa: E501
+ DeprecationWarning,
+ stacklevel=caller_stacklevel + 1,
+ )
+ if placement_group_capture_child_tasks:
+ warnings.warn(
+ "placement_group_capture_child_tasks parameter is deprecated. Use "
+ "scheduling_strategy=PlacementGroupSchedulingStrategy(...) "
+ "instead, see the usage at "
+ f"https://docs.ray.io/en/{get_ray_doc_version()}/ray-core/package-ref.html#ray-remote.", # noqa: E501
+ DeprecationWarning,
+ stacklevel=caller_stacklevel + 1,
+ )
+
+
+def validate_task_options(options: Dict[str, Any], in_options: bool):
+ """Options check for Ray tasks.
+
+ Args:
+ options: Options for Ray tasks.
+ in_options: If True, we are checking the options under the context of
+ ".options()".
+ """
+ for k, v in options.items():
+ if k not in task_options:
+ raise ValueError(
+ f"Invalid option keyword {k} for remote functions. "
+ f"Valid ones are {list(task_options)}."
+ )
+ task_options[k].validate(k, v)
+ if in_options and "max_calls" in options:
+ raise ValueError("Setting 'max_calls' is not supported in '.options()'.")
+ _check_deprecate_placement_group(options)
+
+
+def validate_actor_options(options: Dict[str, Any], in_options: bool):
+ """Options check for Ray actors.
+
+ Args:
+ options: Options for Ray actors.
+ in_options: If True, we are checking the options under the context of
+ ".options()".
+ """
+ for k, v in options.items():
+ if k not in actor_options:
+ raise ValueError(
+ f"Invalid option keyword {k} for actors. "
+ f"Valid ones are {list(actor_options)}."
+ )
+ actor_options[k].validate(k, v)
+
+ if in_options and "concurrency_groups" in options:
+ raise ValueError(
+ "Setting 'concurrency_groups' is not supported in '.options()'."
+ )
+
+ if options.get("get_if_exists") and not options.get("name"):
+ raise ValueError("The actor name must be specified to use `get_if_exists`.")
+
+ if "object_store_memory" in options:
+ warnings.warn(
+ "Setting 'object_store_memory'"
+ " for actors is deprecated since it doesn't actually"
+ " reserve the required object store memory."
+ f" Use object spilling that's enabled by default (https://docs.ray.io/en/{get_ray_doc_version()}/ray-core/objects/object-spilling.html) " # noqa: E501
+ "instead to bypass the object store memory size limitation.",
+ DeprecationWarning,
+ stacklevel=1,
+ )
+
+ _check_deprecate_placement_group(options)
+
+
+def update_options(
+ original_options: Dict[str, Any], new_options: Dict[str, Any]
+) -> Dict[str, Any]:
+ """Update original options with new options and return.
+ The returned updated options contain shallow copy of original options.
+ """
+
+ return {**original_options, **new_options}
diff --git a/lib/python3.12/site-packages/ray/_common/retry.py b/lib/python3.12/site-packages/ray/_common/retry.py
new file mode 100644
index 0000000000000000000000000000000000000000..947a6360f53fa22e7953d144f00f3fbcee398027
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/retry.py
@@ -0,0 +1,82 @@
+import functools
+import logging
+import random
+import time
+from typing import Any, Callable, List, Optional
+
+logger = logging.getLogger(__name__)
+
+
+def call_with_retry(
+ f: Callable,
+ description: str,
+ match: Optional[List[str]] = None,
+ max_attempts: int = 10,
+ max_backoff_s: int = 32,
+ *args,
+ **kwargs,
+) -> Any:
+ """Retry a function with exponential backoff.
+
+ Args:
+ f: The function to retry.
+ description: An imperative description of the function being retried. For
+ example, "open the file".
+ match: A list of strings to match in the exception message. If ``None``, any
+ error is retried.
+ max_attempts: The maximum number of attempts to retry.
+ max_backoff_s: The maximum number of seconds to backoff.
+ *args: Arguments to pass to the function.
+ **kwargs: Keyword arguments to pass to the function.
+
+ Returns:
+ The result of the function.
+ """
+ # TODO: consider inverse match and matching exception type
+ assert max_attempts >= 1, f"`max_attempts` must be positive. Got {max_attempts}."
+
+ for i in range(max_attempts):
+ try:
+ return f(*args, **kwargs)
+ except Exception as e:
+ exception_str = str(e)
+ is_retryable = match is None or any(
+ pattern in exception_str for pattern in match
+ )
+ if is_retryable and i + 1 < max_attempts:
+ # Retry with binary exponential backoff with 20% random jitter.
+ backoff = min(2**i, max_backoff_s) * (random.uniform(0.8, 1.2))
+ logger.debug(
+ f"Retrying {i+1} attempts to {description} after {backoff} seconds."
+ )
+ time.sleep(backoff)
+ else:
+ if is_retryable:
+ logger.debug(
+ f"Failed to {description} after {max_attempts} attempts. Raising."
+ )
+ else:
+ logger.debug(
+ f"Did not find a match for {exception_str}. Raising after {i+1} attempts."
+ )
+ raise e from None
+
+
+def retry(
+ description: str,
+ match: Optional[List[str]] = None,
+ max_attempts: int = 10,
+ max_backoff_s: int = 32,
+) -> Callable:
+ """Decorator-based version of call_with_retry."""
+
+ def decorator(func: Callable) -> Callable:
+ @functools.wraps(func)
+ def inner(*args, **kwargs):
+ return call_with_retry(
+ func, description, match, max_attempts, max_backoff_s, *args, **kwargs
+ )
+
+ return inner
+
+ return decorator
diff --git a/lib/python3.12/site-packages/ray/_common/serialization.py b/lib/python3.12/site-packages/ray/_common/serialization.py
new file mode 100644
index 0000000000000000000000000000000000000000..051f155d38747c1888d4fde849cb4dbdf6e5101d
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/serialization.py
@@ -0,0 +1,34 @@
+import io
+import logging
+from typing import TYPE_CHECKING, Any
+
+if TYPE_CHECKING:
+ pass
+
+
+import ray._private.utils
+import ray.cloudpickle as pickle
+import ray.exceptions
+from ray._private import ray_constants
+from ray.util import inspect_serializability
+
+logger = logging.getLogger(__name__)
+ALLOW_OUT_OF_BAND_OBJECT_REF_SERIALIZATION = ray_constants.env_bool(
+ "RAY_allow_out_of_band_object_ref_serialization", True
+)
+
+
+def pickle_dumps(obj: Any, error_msg: str):
+ """Wrap cloudpickle.dumps to provide better error message
+ when the object is not serializable.
+ """
+ try:
+ return pickle.dumps(obj)
+ except (TypeError, ray.exceptions.OufOfBandObjectRefSerializationException) as e:
+ sio = io.StringIO()
+ inspect_serializability(obj, print_file=sio)
+ msg = f"{error_msg}:\n{sio.getvalue()}"
+ if isinstance(e, TypeError):
+ raise TypeError(msg) from e
+ else:
+ raise ray.exceptions.OufOfBandObjectRefSerializationException(msg)
diff --git a/lib/python3.12/site-packages/ray/_common/signature.py b/lib/python3.12/site-packages/ray/_common/signature.py
new file mode 100644
index 0000000000000000000000000000000000000000..190d18c139062e3ae014bf9b5c61e484a3a101d7
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/signature.py
@@ -0,0 +1,163 @@
+import inspect
+import logging
+from inspect import Parameter
+from typing import Any, Dict, List, Tuple
+
+from ray._private.inspect_util import is_cython
+
+# Logger for this module. It should be configured at the entry point
+# into the program using Ray. Ray provides a default configuration at
+# entry/init points.
+logger = logging.getLogger(__name__)
+
+# This dummy type is also defined in ArgumentsBuilder.java. Please keep it
+# synced.
+DUMMY_TYPE = b"__RAY_DUMMY__"
+
+
+def get_signature(func: Any) -> inspect.Signature:
+ """Get signature parameters.
+
+ Support Cython functions by grabbing relevant attributes from the Cython
+ function and attaching to a no-op function. This is somewhat brittle, since
+ inspect may change, but given that inspect is written to a PEP, we hope
+ it is relatively stable. Future versions of Python may allow overloading
+ the inspect 'isfunction' and 'ismethod' functions / create ABC for Python
+ functions. Until then, it appears that Cython won't do anything about
+ compatability with the inspect module.
+
+ Args:
+ func: The function whose signature should be checked.
+
+ Returns:
+ A function signature object, which includes the names of the keyword
+ arguments as well as their default values.
+
+ Raises:
+ TypeError: A type error if the signature is not supported
+ """
+ # The first condition for Cython functions, the latter for Cython instance
+ # methods
+ if is_cython(func):
+ attrs = ["__code__", "__annotations__", "__defaults__", "__kwdefaults__"]
+
+ if all(hasattr(func, attr) for attr in attrs):
+ original_func = func
+
+ def func():
+ return
+
+ for attr in attrs:
+ setattr(func, attr, getattr(original_func, attr))
+ else:
+ raise TypeError(f"{func!r} is not a Python function we can process")
+
+ return inspect.signature(func)
+
+
+def extract_signature(func: Any, ignore_first: bool = False) -> List[Parameter]:
+ """Extract the function signature from the function.
+
+ Args:
+ func: The function whose signature should be extracted.
+ ignore_first: True if the first argument should be ignored. This should
+ be used when func is a method of a class.
+
+ Returns:
+ List of Parameter objects representing the function signature.
+ """
+ signature_parameters = list(get_signature(func).parameters.values())
+
+ if ignore_first:
+ if len(signature_parameters) == 0:
+ raise ValueError(
+ "Methods must take a 'self' argument, but the "
+ f"method '{func.__name__}' does not have one."
+ )
+ signature_parameters = signature_parameters[1:]
+
+ return signature_parameters
+
+
+def validate_args(
+ signature_parameters: List[Parameter], args: Tuple[Any, ...], kwargs: Dict[str, Any]
+) -> None:
+ """Validates the arguments against the signature.
+
+ Args:
+ signature_parameters: The list of Parameter objects
+ representing the function signature, obtained from
+ `extract_signature`.
+ args: The positional arguments passed into the function.
+ kwargs: The keyword arguments passed into the function.
+
+ Raises:
+ TypeError: Raised if arguments do not fit in the function signature.
+ """
+ reconstructed_signature = inspect.Signature(parameters=signature_parameters)
+ try:
+ reconstructed_signature.bind(*args, **kwargs)
+ except TypeError as exc: # capture a friendlier stacktrace
+ raise TypeError(str(exc)) from None
+
+
+def flatten_args(
+ signature_parameters: List[Parameter], args: Tuple[Any, ...], kwargs: Dict[str, Any]
+) -> List[Any]:
+ """Validates the arguments against the signature and flattens them.
+
+ The flat list representation is a serializable format for arguments.
+ Since the flatbuffer representation of function arguments is a list, we
+ combine both keyword arguments and positional arguments. We represent
+ this with two entries per argument value - [DUMMY_TYPE, x] for positional
+ arguments and [KEY, VALUE] for keyword arguments. See the below example.
+ See `recover_args` for logic restoring the flat list back to args/kwargs.
+
+ Args:
+ signature_parameters: The list of Parameter objects
+ representing the function signature, obtained from
+ `extract_signature`.
+ args: The positional arguments passed into the function.
+ kwargs: The keyword arguments passed into the function.
+
+ Returns:
+ List of args and kwargs. Non-keyword arguments are prefixed
+ by internal enum DUMMY_TYPE.
+
+ Raises:
+ TypeError: Raised if arguments do not fit in the function signature.
+ """
+ validate_args(signature_parameters, args, kwargs)
+ list_args = []
+ for arg in args:
+ list_args += [DUMMY_TYPE, arg]
+
+ for keyword, arg in kwargs.items():
+ list_args += [keyword, arg]
+ return list_args
+
+
+def recover_args(flattened_args: List[Any]) -> Tuple[List[Any], Dict[str, Any]]:
+ """Recreates `args` and `kwargs` from the flattened arg list.
+
+ Args:
+ flattened_args: List of args and kwargs. This should be the output of
+ `flatten_args`.
+
+ Returns:
+ args: The non-keyword arguments passed into the function.
+ kwargs: The keyword arguments passed into the function.
+ """
+ assert (
+ len(flattened_args) % 2 == 0
+ ), "Flattened arguments need to be even-numbered. See `flatten_args`."
+ args = []
+ kwargs = {}
+ for name_index in range(0, len(flattened_args), 2):
+ name, arg = flattened_args[name_index], flattened_args[name_index + 1]
+ if name == DUMMY_TYPE:
+ args.append(arg)
+ else:
+ kwargs[name] = arg
+
+ return args, kwargs
diff --git a/lib/python3.12/site-packages/ray/_common/test_utils.py b/lib/python3.12/site-packages/ray/_common/test_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..c5e6020b1c98f548056e5f35abb22e210d1720eb
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/test_utils.py
@@ -0,0 +1,249 @@
+"""Test utilities for Ray.
+
+This module contains test utility classes that are distributed with the Ray package
+and can be used by external libraries and tests. These utilities must remain in
+_common/ (not in tests/) to be accessible in the Ray package distribution.
+"""
+
+import asyncio
+import inspect
+import os
+import time
+import traceback
+import uuid
+from collections.abc import Awaitable
+from contextlib import contextmanager
+from enum import Enum
+from typing import Any, Callable, Dict, Iterator, List, Optional, Set
+
+import ray
+import ray._common.usage.usage_lib as ray_usage_lib
+import ray._private.utils
+from ray._common.network_utils import build_address
+
+
+@ray.remote(num_cpus=0)
+class SignalActor:
+ """A Ray actor for coordinating test execution through signals.
+
+ Useful for testing async coordination, waiting for specific states,
+ and synchronizing multiple actors or tasks in tests.
+ """
+
+ def __init__(self):
+ self.ready_event = asyncio.Event()
+ self.num_waiters = 0
+
+ def send(self, clear: bool = False):
+ self.ready_event.set()
+ if clear:
+ self.ready_event.clear()
+
+ async def wait(self, should_wait: bool = True):
+ if should_wait:
+ self.num_waiters += 1
+ await self.ready_event.wait()
+ self.num_waiters -= 1
+
+ async def cur_num_waiters(self) -> int:
+ return self.num_waiters
+
+
+@ray.remote(num_cpus=0)
+class Semaphore:
+ """A Ray actor implementing a semaphore for test coordination.
+
+ Useful for testing resource limiting, concurrency control,
+ and coordination between multiple actors or tasks.
+ """
+
+ def __init__(self, value: int = 1):
+ self._sema = asyncio.Semaphore(value=value)
+
+ async def acquire(self):
+ await self._sema.acquire()
+
+ async def release(self):
+ self._sema.release()
+
+ async def locked(self) -> bool:
+ return self._sema.locked()
+
+
+__all__ = ["SignalActor", "Semaphore"]
+
+
+def wait_for_condition(
+ condition_predictor: Callable[..., bool],
+ timeout: float = 10,
+ retry_interval_ms: float = 100,
+ raise_exceptions: bool = False,
+ **kwargs: Any,
+):
+ """Wait until a condition is met or time out with an exception.
+
+ Args:
+ condition_predictor: A function that predicts the condition.
+ timeout: Maximum timeout in seconds.
+ retry_interval_ms: Retry interval in milliseconds.
+ raise_exceptions: If true, exceptions that occur while executing
+ condition_predictor won't be caught and instead will be raised.
+ **kwargs: Arguments to pass to the condition_predictor.
+
+ Returns:
+ None: Returns when the condition is met.
+
+ Raises:
+ RuntimeError: If the condition is not met before the timeout expires.
+ """
+ start = time.time()
+ last_ex = None
+ while time.time() - start <= timeout:
+ try:
+ if condition_predictor(**kwargs):
+ return
+ except Exception:
+ if raise_exceptions:
+ raise
+ last_ex = ray._private.utils.format_error_message(traceback.format_exc())
+ time.sleep(retry_interval_ms / 1000.0)
+ message = "The condition wasn't met before the timeout expired."
+ if last_ex is not None:
+ message += f" Last exception: {last_ex}"
+ raise RuntimeError(message)
+
+
+async def async_wait_for_condition(
+ condition_predictor: Callable[..., Awaitable[bool]],
+ timeout: float = 10,
+ retry_interval_ms: float = 100,
+ **kwargs: Any,
+):
+ """Wait until a condition is met or time out with an exception.
+
+ Args:
+ condition_predictor: A function that predicts the condition.
+ timeout: Maximum timeout in seconds.
+ retry_interval_ms: Retry interval in milliseconds.
+ **kwargs: Arguments to pass to the condition_predictor.
+
+ Returns:
+ None: Returns when the condition is met.
+
+ Raises:
+ RuntimeError: If the condition is not met before the timeout expires.
+ """
+ start = time.time()
+ last_ex = None
+ while time.time() - start <= timeout:
+ try:
+ if inspect.iscoroutinefunction(condition_predictor):
+ if await condition_predictor(**kwargs):
+ return
+ else:
+ if condition_predictor(**kwargs):
+ return
+ except Exception as ex:
+ last_ex = ex
+ await asyncio.sleep(retry_interval_ms / 1000.0)
+ message = "The condition wasn't met before the timeout expired."
+ if last_ex is not None:
+ message += f" Last exception: {last_ex}"
+ raise RuntimeError(message)
+
+
+@contextmanager
+def simulate_s3_bucket(
+ port: int = 5002,
+ region: str = "us-west-2",
+) -> Iterator[str]:
+ """Context manager that simulates an S3 bucket and yields the URI.
+
+ Args:
+ port: The port of the localhost endpoint where S3 is being served.
+ region: The S3 region.
+
+ Yields:
+ str: URI for the simulated S3 bucket.
+ """
+ from moto.server import ThreadedMotoServer
+
+ old_env = os.environ
+ os.environ["AWS_ACCESS_KEY_ID"] = "testing"
+ os.environ["AWS_SECRET_ACCESS_KEY"] = "testing"
+ os.environ["AWS_SECURITY_TOKEN"] = "testing"
+ os.environ["AWS_SESSION_TOKEN"] = "testing"
+
+ s3_server = f"http://{build_address('localhost', port)}"
+ server = ThreadedMotoServer(port=port)
+ server.start()
+ url = f"s3://{uuid.uuid4().hex}?region={region}&endpoint_override={s3_server}"
+ yield url
+ server.stop()
+ os.environ = old_env
+
+
+class TelemetryCallsite(Enum):
+ DRIVER = "driver"
+ ACTOR = "actor"
+ TASK = "task"
+
+
+def _get_library_usages() -> Set[str]:
+ return set(
+ ray_usage_lib.get_library_usages_to_report(
+ ray.experimental.internal_kv.internal_kv_get_gcs_client()
+ )
+ )
+
+
+def _get_extra_usage_tags() -> Dict[str, str]:
+ return ray_usage_lib.get_extra_usage_tags_to_report(
+ ray.experimental.internal_kv.internal_kv_get_gcs_client()
+ )
+
+
+def check_library_usage_telemetry(
+ use_lib_fn: Callable[[], None],
+ *,
+ callsite: TelemetryCallsite,
+ expected_library_usages: List[Set[str]],
+ expected_extra_usage_tags: Optional[Dict[str, str]] = None,
+):
+ """Helper for writing tests to validate library usage telemetry.
+
+ `use_lib_fn` is a callable that will be called from the provided callsite.
+ After calling it, the telemetry data to export will be validated against
+ expected_library_usages and expected_extra_usage_tags.
+ """
+ assert len(_get_library_usages()) == 0, _get_library_usages()
+
+ if callsite == TelemetryCallsite.DRIVER:
+ use_lib_fn()
+ elif callsite == TelemetryCallsite.ACTOR:
+
+ @ray.remote
+ class A:
+ def __init__(self):
+ use_lib_fn()
+
+ a = A.remote()
+ ray.get(a.__ray_ready__.remote())
+ elif callsite == TelemetryCallsite.TASK:
+
+ @ray.remote
+ def f():
+ use_lib_fn()
+
+ ray.get(f.remote())
+ else:
+ assert False, f"Unrecognized callsite: {callsite}"
+
+ library_usages = _get_library_usages()
+ extra_usage_tags = _get_extra_usage_tags()
+
+ assert library_usages in expected_library_usages, library_usages
+ if expected_extra_usage_tags:
+ assert all(
+ [extra_usage_tags[k] == v for k, v in expected_extra_usage_tags.items()]
+ ), extra_usage_tags
diff --git a/lib/python3.12/site-packages/ray/_common/usage/__init__.py b/lib/python3.12/site-packages/ray/_common/usage/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/_common/usage/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/usage/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8deeb24da58c631936d9ae15019cc1292b79e82b
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/usage/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/usage/__pycache__/usage_constants.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/usage/__pycache__/usage_constants.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8fac5dadcb1b7f044fa8e4fcb549892a564fdfaf
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/usage/__pycache__/usage_constants.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/usage/__pycache__/usage_lib.cpython-312.pyc b/lib/python3.12/site-packages/ray/_common/usage/__pycache__/usage_lib.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..475f0cc0192a1b577a0c3a66fb65511b1033385d
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_common/usage/__pycache__/usage_lib.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_common/usage/usage_constants.py b/lib/python3.12/site-packages/ray/_common/usage/usage_constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b5b97ad175e334aa963e2f02629329830c57ff8
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/usage/usage_constants.py
@@ -0,0 +1,63 @@
+SCHEMA_VERSION = "0.1"
+
+# The key to store / obtain cluster metadata.
+CLUSTER_METADATA_KEY = b"CLUSTER_METADATA"
+
+# The name of a json file where usage stats will be written.
+USAGE_STATS_FILE = "usage_stats.json"
+
+USAGE_STATS_ENABLED_ENV_VAR = "RAY_USAGE_STATS_ENABLED"
+
+USAGE_STATS_SOURCE_ENV_VAR = "RAY_USAGE_STATS_SOURCE"
+
+USAGE_STATS_SOURCE_OSS = "OSS"
+
+USAGE_STATS_ENABLED_FOR_CLI_MESSAGE = (
+ "Usage stats collection is enabled. To disable this, add `--disable-usage-stats` "
+ "to the command that starts the cluster, or run the following command:"
+ " `ray disable-usage-stats` before starting the cluster. "
+ "See https://docs.ray.io/en/master/cluster/usage-stats.html for more details."
+)
+
+USAGE_STATS_ENABLED_FOR_RAY_INIT_MESSAGE = (
+ "Usage stats collection is enabled. To disable this, run the following command:"
+ " `ray disable-usage-stats` before starting Ray. "
+ "See https://docs.ray.io/en/master/cluster/usage-stats.html for more details."
+)
+
+USAGE_STATS_DISABLED_MESSAGE = "Usage stats collection is disabled."
+
+USAGE_STATS_ENABLED_BY_DEFAULT_FOR_CLI_MESSAGE = (
+ "Usage stats collection is enabled by default without user confirmation "
+ "because this terminal is detected to be non-interactive. "
+ "To disable this, add `--disable-usage-stats` to the command that starts "
+ "the cluster, or run the following command:"
+ " `ray disable-usage-stats` before starting the cluster. "
+ "See https://docs.ray.io/en/master/cluster/usage-stats.html for more details."
+)
+
+USAGE_STATS_ENABLED_BY_DEFAULT_FOR_RAY_INIT_MESSAGE = (
+ "Usage stats collection is enabled by default for nightly wheels. "
+ "To disable this, run the following command:"
+ " `ray disable-usage-stats` before starting Ray. "
+ "See https://docs.ray.io/en/master/cluster/usage-stats.html for more details."
+)
+
+USAGE_STATS_CONFIRMATION_MESSAGE = (
+ "Enable usage stats collection? "
+ "This prompt will auto-proceed in 10 seconds to avoid blocking cluster startup."
+)
+
+LIBRARY_USAGE_SET_NAME = "library_usage_"
+
+HARDWARE_USAGE_SET_NAME = "hardware_usage_"
+
+# Keep in-sync with the same constants defined in usage_stats_client.h
+EXTRA_USAGE_TAG_PREFIX = "extra_usage_tag_"
+USAGE_STATS_NAMESPACE = "usage_stats"
+
+KUBERNETES_SERVICE_HOST_ENV = "KUBERNETES_SERVICE_HOST"
+KUBERAY_ENV = "RAY_USAGE_STATS_KUBERAY_IN_USE"
+
+PROVIDER_KUBERNETES_GENERIC = "kubernetes"
+PROVIDER_KUBERAY = "kuberay"
diff --git a/lib/python3.12/site-packages/ray/_common/usage/usage_lib.py b/lib/python3.12/site-packages/ray/_common/usage/usage_lib.py
new file mode 100644
index 0000000000000000000000000000000000000000..085c0c4f9fa9070718e2e3af6bdefa8370725492
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/usage/usage_lib.py
@@ -0,0 +1,1016 @@
+"""This is the module that is in charge of Ray usage report (telemetry) APIs.
+
+NOTE: Ray's usage report is currently "on by default".
+ One could opt-out, see details at https://docs.ray.io/en/master/cluster/usage-stats.html. # noqa
+
+Ray usage report follows the specification from
+https://docs.ray.io/en/master/cluster/usage-stats.html#usage-stats-collection # noqa
+
+# Module
+
+The module consists of 2 parts.
+
+## Public API
+It contains public APIs to obtain usage report information.
+APIs will be added before the usage report becomes opt-in by default.
+
+## Internal APIs for usage processing/report
+The telemetry report consists of 5 components. This module is in charge of the top 2 layers.
+
+Report -> usage_lib
+---------------------
+Usage data processing -> usage_lib
+---------------------
+Data storage -> Ray API server
+---------------------
+Aggregation -> Ray API server (currently a dashboard server)
+---------------------
+Usage data collection -> Various components (Ray agent, GCS, etc.) + usage_lib (cluster metadata).
+
+Usage report is currently "off by default". You can enable the report by setting an environment variable
+RAY_USAGE_STATS_ENABLED=1. For example, `RAY_USAGE_STATS_ENABLED=1 ray start --head`.
+Or `RAY_USAGE_STATS_ENABLED=1 python [drivers with ray.init()]`.
+
+"Ray API server (currently a dashboard server)" reports the usage data to https://usage-stats.ray.io/.
+
+Data is reported every hour by default.
+
+Note that it is also possible to configure the interval using the environment variable,
+`RAY_USAGE_STATS_REPORT_INTERVAL_S`.
+
+To see collected/reported data, see `usage_stats.json` inside a temp
+folder (e.g., /tmp/ray/session_[id]/*).
+"""
+import json
+import logging
+import os
+import platform
+import sys
+import threading
+import time
+from dataclasses import asdict, dataclass
+from enum import Enum, auto
+from pathlib import Path
+from typing import Dict, List, Optional, Set
+
+import requests
+import yaml
+
+import ray
+import ray._common.usage.usage_constants as usage_constant
+import ray._private.ray_constants as ray_constants
+from ray._raylet import GcsClient
+from ray.core.generated import gcs_pb2, usage_pb2
+from ray.experimental.internal_kv import (
+ _internal_kv_initialized,
+ _internal_kv_put,
+)
+
+logger = logging.getLogger(__name__)
+TagKey = usage_pb2.TagKey
+
+#################
+# Internal APIs #
+#################
+
+
+@dataclass(init=True)
+class ClusterConfigToReport:
+ cloud_provider: Optional[str] = None
+ min_workers: Optional[int] = None
+ max_workers: Optional[int] = None
+ head_node_instance_type: Optional[str] = None
+ worker_node_instance_types: Optional[List[str]] = None
+
+
+@dataclass(init=True)
+class ClusterStatusToReport:
+ total_num_cpus: Optional[int] = None
+ total_num_gpus: Optional[int] = None
+ total_memory_gb: Optional[float] = None
+ total_object_store_memory_gb: Optional[float] = None
+
+
+@dataclass(init=True)
+class UsageStatsToReport:
+ """Usage stats to report"""
+
+ #: The schema version of the report.
+ schema_version: str
+ #: The source of the data (i.e. OSS).
+ source: str
+ #: When the data is collected and reported.
+ collect_timestamp_ms: int
+ #: The total number of successful reports for the lifetime of the cluster.
+ total_success: Optional[int] = None
+ #: The total number of failed reports for the lifetime of the cluster.
+ total_failed: Optional[int] = None
+ #: The sequence number of the report.
+ seq_number: Optional[int] = None
+ #: The Ray version in use.
+ ray_version: Optional[str] = None
+ #: The Python version in use.
+ python_version: Optional[str] = None
+ #: A random id of the cluster session.
+ session_id: Optional[str] = None
+ #: The git commit hash of Ray (i.e. ray.__commit__).
+ git_commit: Optional[str] = None
+ #: The operating system in use.
+ os: Optional[str] = None
+ #: When the cluster is started.
+ session_start_timestamp_ms: Optional[int] = None
+ #: The cloud provider found in the cluster.yaml file (e.g., aws).
+ cloud_provider: Optional[str] = None
+ #: The min_workers found in the cluster.yaml file.
+ min_workers: Optional[int] = None
+ #: The max_workers found in the cluster.yaml file.
+ max_workers: Optional[int] = None
+ #: The head node instance type found in the cluster.yaml file (e.g., i3.8xlarge).
+ head_node_instance_type: Optional[str] = None
+ #: The worker node instance types found in the cluster.yaml file (e.g., i3.8xlarge).
+ worker_node_instance_types: Optional[List[str]] = None
+ #: The total num of cpus in the cluster.
+ total_num_cpus: Optional[int] = None
+ #: The total num of gpus in the cluster.
+ total_num_gpus: Optional[int] = None
+ #: The total size of memory in the cluster.
+ total_memory_gb: Optional[float] = None
+ #: The total size of object store memory in the cluster.
+ total_object_store_memory_gb: Optional[float] = None
+ #: The Ray libraries that are used (e.g., rllib).
+ library_usages: Optional[List[str]] = None
+ #: The extra tags to report when specified by an
+ # environment variable RAY_USAGE_STATS_EXTRA_TAGS
+ extra_usage_tags: Optional[Dict[str, str]] = None
+ #: The number of alive nodes when the report is generated.
+ total_num_nodes: Optional[int] = None
+ #: The total number of running jobs excluding internal ones
+ # when the report is generated.
+ total_num_running_jobs: Optional[int] = None
+ #: The libc version in the OS.
+ libc_version: Optional[str] = None
+ #: The hardwares that are used (e.g. Intel Xeon).
+ hardware_usages: Optional[List[str]] = None
+
+
+@dataclass(init=True)
+class UsageStatsToWrite:
+ """Usage stats to write to `USAGE_STATS_FILE`
+
+ We are writing extra metadata such as the status of report
+ to this file.
+ """
+
+ usage_stats: UsageStatsToReport
+ # Whether or not the last report succeeded.
+ success: bool
+ # The error message of the last report if it happens.
+ error: str
+
+
+class UsageStatsEnabledness(Enum):
+ ENABLED_EXPLICITLY = auto()
+ DISABLED_EXPLICITLY = auto()
+ ENABLED_BY_DEFAULT = auto()
+
+
+_recorded_library_usages = set()
+_recorded_library_usages_lock = threading.Lock()
+_recorded_extra_usage_tags = dict()
+_recorded_extra_usage_tags_lock = threading.Lock()
+
+
+def _add_to_usage_set(set_name: str, value: str):
+ assert _internal_kv_initialized()
+ try:
+ _internal_kv_put(
+ f"{set_name}{value}".encode(),
+ b"",
+ namespace=usage_constant.USAGE_STATS_NAMESPACE.encode(),
+ )
+ except Exception as e:
+ logger.debug(f"Failed to add {value} to usage set {set_name}, {e}")
+
+
+def _get_usage_set(gcs_client, set_name: str) -> Set[str]:
+ try:
+ result = set()
+ usages = gcs_client.internal_kv_keys(
+ set_name.encode(),
+ namespace=usage_constant.USAGE_STATS_NAMESPACE.encode(),
+ )
+ for usage in usages:
+ usage = usage.decode("utf-8")
+ result.add(usage[len(set_name) :])
+
+ return result
+ except Exception as e:
+ logger.debug(f"Failed to get usage set {set_name}, {e}")
+ return set()
+
+
+def _put_library_usage(library_usage: str):
+ _add_to_usage_set(usage_constant.LIBRARY_USAGE_SET_NAME, library_usage)
+
+
+def _put_hardware_usage(hardware_usage: str):
+ _add_to_usage_set(usage_constant.HARDWARE_USAGE_SET_NAME, hardware_usage)
+
+
+def record_extra_usage_tag(
+ key: TagKey, value: str, gcs_client: Optional[GcsClient] = None
+):
+ """Record extra kv usage tag.
+
+ If the key already exists, the value will be overwritten.
+
+ To record an extra tag, first add the key to the TagKey enum and
+ then call this function.
+ It will make a synchronous call to the internal kv store if the tag is updated.
+
+ Args:
+ key: The key of the tag.
+ value: The value of the tag.
+ gcs_client: The GCS client to perform KV operation PUT. Defaults to None.
+ When None, it will try to get the global client from the internal_kv.
+
+ Returns:
+ None
+ """
+ key = TagKey.Name(key).lower()
+ with _recorded_extra_usage_tags_lock:
+ if _recorded_extra_usage_tags.get(key) == value:
+ return
+ _recorded_extra_usage_tags[key] = value
+
+ if not _internal_kv_initialized() and gcs_client is None:
+ # This happens if the record is before ray.init and
+ # no GCS client is used for recording explicitly.
+ return
+
+ _put_extra_usage_tag(key, value, gcs_client)
+
+
+def _put_extra_usage_tag(key: str, value: str, gcs_client: Optional[GcsClient] = None):
+ try:
+ key = f"{usage_constant.EXTRA_USAGE_TAG_PREFIX}{key}".encode()
+ val = value.encode()
+ namespace = usage_constant.USAGE_STATS_NAMESPACE.encode()
+ if gcs_client is not None:
+ # Use the GCS client.
+ gcs_client.internal_kv_put(key, val, namespace=namespace)
+ else:
+ # Use internal kv.
+ assert _internal_kv_initialized()
+ _internal_kv_put(key, val, namespace=namespace)
+ except Exception as e:
+ logger.debug(f"Failed to put extra usage tag, {e}")
+
+
+def record_hardware_usage(hardware_usage: str):
+ """Record hardware usage (e.g. which CPU model is used)"""
+ assert _internal_kv_initialized()
+ _put_hardware_usage(hardware_usage)
+
+
+def record_library_usage(library_usage: str):
+ """Record library usage (e.g. which library is used)"""
+ with _recorded_library_usages_lock:
+ if library_usage in _recorded_library_usages:
+ return
+ _recorded_library_usages.add(library_usage)
+
+ if not _internal_kv_initialized():
+ # This happens if the library is imported before ray.init
+ return
+
+ # Only report lib usage for driver / ray client / workers. Otherwise,
+ # it can be reported if the library is imported from
+ # e.g., API server.
+ if (
+ ray._private.worker.global_worker.mode == ray.SCRIPT_MODE
+ or ray._private.worker.global_worker.mode == ray.WORKER_MODE
+ or ray.util.client.ray.is_connected()
+ ):
+ _put_library_usage(library_usage)
+
+
+def _put_pre_init_library_usages():
+ assert _internal_kv_initialized()
+ # NOTE: When the lib is imported from a worker, ray should
+ # always be initialized, so there's no need to register the
+ # pre init hook.
+ if not (
+ ray._private.worker.global_worker.mode == ray.SCRIPT_MODE
+ or ray.util.client.ray.is_connected()
+ ):
+ return
+
+ for library_usage in _recorded_library_usages:
+ _put_library_usage(library_usage)
+
+
+def _put_pre_init_extra_usage_tags():
+ assert _internal_kv_initialized()
+ for k, v in _recorded_extra_usage_tags.items():
+ _put_extra_usage_tag(k, v)
+
+
+def put_pre_init_usage_stats():
+ _put_pre_init_library_usages()
+ _put_pre_init_extra_usage_tags()
+
+
+def reset_global_state():
+ global _recorded_library_usages, _recorded_extra_usage_tags
+
+ with _recorded_library_usages_lock:
+ _recorded_library_usages = set()
+ with _recorded_extra_usage_tags_lock:
+ _recorded_extra_usage_tags = dict()
+
+
+ray._private.worker._post_init_hooks.append(put_pre_init_usage_stats)
+
+
+def _usage_stats_report_url():
+ # The usage collection server URL.
+ # The environment variable is testing-purpose only.
+ return os.getenv("RAY_USAGE_STATS_REPORT_URL", "https://usage-stats.ray.io/")
+
+
+def _usage_stats_report_interval_s():
+ return int(os.getenv("RAY_USAGE_STATS_REPORT_INTERVAL_S", 3600))
+
+
+def _usage_stats_config_path():
+ return os.getenv(
+ "RAY_USAGE_STATS_CONFIG_PATH", os.path.expanduser("~/.ray/config.json")
+ )
+
+
+def _usage_stats_enabledness() -> UsageStatsEnabledness:
+ # Env var has higher priority than config file.
+ usage_stats_enabled_env_var = os.getenv(usage_constant.USAGE_STATS_ENABLED_ENV_VAR)
+ if usage_stats_enabled_env_var == "0":
+ return UsageStatsEnabledness.DISABLED_EXPLICITLY
+ elif usage_stats_enabled_env_var == "1":
+ return UsageStatsEnabledness.ENABLED_EXPLICITLY
+ elif usage_stats_enabled_env_var is not None:
+ raise ValueError(
+ f"Valid value for {usage_constant.USAGE_STATS_ENABLED_ENV_VAR} "
+ f"env var is 0 or 1, but got {usage_stats_enabled_env_var}"
+ )
+
+ usage_stats_enabled_config_var = None
+ try:
+ with open(_usage_stats_config_path()) as f:
+ config = json.load(f)
+ usage_stats_enabled_config_var = config.get("usage_stats")
+ except FileNotFoundError:
+ pass
+ except Exception as e:
+ logger.debug(f"Failed to load usage stats config {e}")
+
+ if usage_stats_enabled_config_var is False:
+ return UsageStatsEnabledness.DISABLED_EXPLICITLY
+ elif usage_stats_enabled_config_var is True:
+ return UsageStatsEnabledness.ENABLED_EXPLICITLY
+ elif usage_stats_enabled_config_var is not None:
+ raise ValueError(
+ f"Valid value for 'usage_stats' in {_usage_stats_config_path()}"
+ f" is true or false, but got {usage_stats_enabled_config_var}"
+ )
+
+ # Usage stats is enabled by default.
+ return UsageStatsEnabledness.ENABLED_BY_DEFAULT
+
+
+def is_nightly_wheel() -> bool:
+ return ray.__commit__ != "{{RAY_COMMIT_SHA}}" and "dev" in ray.__version__
+
+
+def usage_stats_enabled() -> bool:
+ return _usage_stats_enabledness() is not UsageStatsEnabledness.DISABLED_EXPLICITLY
+
+
+def usage_stats_prompt_enabled():
+ return int(os.getenv("RAY_USAGE_STATS_PROMPT_ENABLED", "1")) == 1
+
+
+def _generate_cluster_metadata(*, ray_init_cluster: bool):
+ """Return a dictionary of cluster metadata.
+
+ Params:
+ ray_init_cluster: Whether the cluster is started by ray.init()
+
+ Returns:
+ A dictionary of cluster metadata.
+ """
+ ray_version, python_version = ray._private.utils.compute_version_info()
+ # These two metadata is necessary although usage report is not enabled
+ # to check version compatibility.
+ metadata = {
+ "ray_version": ray_version,
+ "python_version": python_version,
+ "ray_init_cluster": ray_init_cluster,
+ }
+ # Additional metadata is recorded only when usage stats are enabled.
+ if usage_stats_enabled():
+ metadata.update(
+ {
+ "git_commit": ray.__commit__,
+ "os": sys.platform,
+ "session_start_timestamp_ms": int(time.time() * 1000),
+ }
+ )
+ if sys.platform == "linux":
+ # Record llibc version
+ (lib, ver) = platform.libc_ver()
+ if not lib:
+ metadata.update({"libc_version": "NA"})
+ else:
+ metadata.update({"libc_version": f"{lib}:{ver}"})
+ return metadata
+
+
+def show_usage_stats_prompt(cli: bool) -> None:
+ if not usage_stats_prompt_enabled():
+ return
+
+ from ray.autoscaler._private.cli_logger import cli_logger
+
+ prompt_print = cli_logger.print if cli else print
+
+ usage_stats_enabledness = _usage_stats_enabledness()
+ if usage_stats_enabledness is UsageStatsEnabledness.DISABLED_EXPLICITLY:
+ prompt_print(usage_constant.USAGE_STATS_DISABLED_MESSAGE)
+ elif usage_stats_enabledness is UsageStatsEnabledness.ENABLED_BY_DEFAULT:
+ if not cli:
+ prompt_print(
+ usage_constant.USAGE_STATS_ENABLED_BY_DEFAULT_FOR_RAY_INIT_MESSAGE
+ )
+ elif cli_logger.interactive:
+ enabled = cli_logger.confirm(
+ False,
+ usage_constant.USAGE_STATS_CONFIRMATION_MESSAGE,
+ _default=True,
+ _timeout_s=10,
+ )
+ set_usage_stats_enabled_via_env_var(enabled)
+ # Remember user's choice.
+ try:
+ set_usage_stats_enabled_via_config(enabled)
+ except Exception as e:
+ logger.debug(
+ f"Failed to persist usage stats choice for future clusters: {e}"
+ )
+ if enabled:
+ prompt_print(usage_constant.USAGE_STATS_ENABLED_FOR_CLI_MESSAGE)
+ else:
+ prompt_print(usage_constant.USAGE_STATS_DISABLED_MESSAGE)
+ else:
+ prompt_print(
+ usage_constant.USAGE_STATS_ENABLED_BY_DEFAULT_FOR_CLI_MESSAGE,
+ )
+ else:
+ assert usage_stats_enabledness is UsageStatsEnabledness.ENABLED_EXPLICITLY
+ prompt_print(
+ usage_constant.USAGE_STATS_ENABLED_FOR_CLI_MESSAGE
+ if cli
+ else usage_constant.USAGE_STATS_ENABLED_FOR_RAY_INIT_MESSAGE
+ )
+
+
+def set_usage_stats_enabled_via_config(enabled) -> None:
+ config = {}
+ try:
+ with open(_usage_stats_config_path()) as f:
+ config = json.load(f)
+ if not isinstance(config, dict):
+ logger.debug(
+ f"Invalid ray config file, should be a json dict but got {type(config)}"
+ )
+ config = {}
+ except FileNotFoundError:
+ pass
+ except Exception as e:
+ logger.debug(f"Failed to load ray config file {e}")
+
+ config["usage_stats"] = enabled
+
+ try:
+ os.makedirs(os.path.dirname(_usage_stats_config_path()), exist_ok=True)
+ with open(_usage_stats_config_path(), "w") as f:
+ json.dump(config, f)
+ except Exception as e:
+ raise Exception(
+ "Failed to "
+ f'{"enable" if enabled else "disable"}'
+ ' usage stats by writing {"usage_stats": '
+ f'{"true" if enabled else "false"}'
+ "} to "
+ f"{_usage_stats_config_path()}"
+ ) from e
+
+
+def set_usage_stats_enabled_via_env_var(enabled) -> None:
+ os.environ[usage_constant.USAGE_STATS_ENABLED_ENV_VAR] = "1" if enabled else "0"
+
+
+def put_cluster_metadata(gcs_client: GcsClient, *, ray_init_cluster: bool) -> dict:
+ """Generate the cluster metadata and store it to GCS.
+
+ It is a blocking API.
+
+ Params:
+ gcs_client: The GCS client to perform KV operation PUT.
+ ray_init_cluster: Whether the cluster is started by ray.init()
+
+ Raises:
+ gRPC exceptions: If PUT fails.
+
+ Returns:
+ The cluster metadata.
+ """
+ metadata = _generate_cluster_metadata(ray_init_cluster=ray_init_cluster)
+ gcs_client.internal_kv_put(
+ usage_constant.CLUSTER_METADATA_KEY,
+ json.dumps(metadata).encode(),
+ overwrite=True,
+ namespace=ray_constants.KV_NAMESPACE_CLUSTER,
+ )
+ return metadata
+
+
+def get_total_num_running_jobs_to_report(gcs_client) -> Optional[int]:
+ """Return the total number of running jobs in the cluster excluding internal ones"""
+ try:
+ result = gcs_client.get_all_job_info(
+ skip_submission_job_info_field=True, skip_is_running_tasks_field=True
+ )
+ total_num_running_jobs = 0
+ for job_info in result.values():
+ if not job_info.is_dead and not job_info.config.ray_namespace.startswith(
+ "_ray_internal"
+ ):
+ total_num_running_jobs += 1
+ return total_num_running_jobs
+ except Exception as e:
+ logger.info(f"Failed to query number of running jobs in the cluster: {e}")
+ return None
+
+
+def get_total_num_alive_nodes_to_report(gcs_client, timeout=None) -> Optional[int]:
+ """Return the total number of alive nodes in the cluster"""
+ try:
+ result = gcs_client.get_all_node_info(
+ timeout=timeout, state_filter=gcs_pb2.GcsNodeInfo.GcsNodeState.ALIVE
+ )
+ return len(result.items())
+ except Exception as e:
+ logger.info(f"Failed to query number of nodes in the cluster: {e}")
+ return None
+
+
+def get_library_usages_to_report(gcs_client) -> List[str]:
+ return list(_get_usage_set(gcs_client, usage_constant.LIBRARY_USAGE_SET_NAME))
+
+
+def get_hardware_usages_to_report(gcs_client) -> List[str]:
+ return list(_get_usage_set(gcs_client, usage_constant.HARDWARE_USAGE_SET_NAME))
+
+
+def get_extra_usage_tags_to_report(gcs_client: GcsClient) -> Dict[str, str]:
+ """Get the extra usage tags from env var and gcs kv store.
+
+ The env var should be given this way; key=value;key=value.
+ If parsing is failed, it will return the empty data.
+
+ Params:
+ gcs_client: The GCS client.
+
+ Returns:
+ Extra usage tags as kv pairs.
+ """
+ extra_usage_tags = dict()
+
+ extra_usage_tags_env_var = os.getenv("RAY_USAGE_STATS_EXTRA_TAGS", None)
+ if extra_usage_tags_env_var:
+ try:
+ kvs = extra_usage_tags_env_var.strip(";").split(";")
+ for kv in kvs:
+ k, v = kv.split("=")
+ extra_usage_tags[k] = v
+ except Exception as e:
+ logger.info(f"Failed to parse extra usage tags env var: {e}")
+
+ valid_tag_keys = [tag_key.lower() for tag_key in TagKey.keys()]
+ try:
+ keys = gcs_client.internal_kv_keys(
+ usage_constant.EXTRA_USAGE_TAG_PREFIX.encode(),
+ namespace=usage_constant.USAGE_STATS_NAMESPACE.encode(),
+ )
+ kv = gcs_client.internal_kv_multi_get(
+ keys, namespace=usage_constant.USAGE_STATS_NAMESPACE.encode()
+ )
+ for key, value in kv.items():
+ key = key.decode("utf-8")
+ key = key[len(usage_constant.EXTRA_USAGE_TAG_PREFIX) :]
+ assert key in valid_tag_keys
+ extra_usage_tags[key] = value.decode("utf-8")
+ except Exception as e:
+ logger.info(f"Failed to get extra usage tags from kv store: {e}")
+ return extra_usage_tags
+
+
+def _get_cluster_status_to_report_v2(gcs_client: GcsClient) -> ClusterStatusToReport:
+ """
+ Get the current status of this cluster. A temporary proxy for the
+ autoscaler v2 API.
+
+ It is a blocking API.
+
+ Params:
+ gcs_client: The GCS client.
+
+ Returns:
+ The current cluster status or empty ClusterStatusToReport
+ if it fails to get that information.
+ """
+ from ray.autoscaler.v2.sdk import get_cluster_status
+
+ result = ClusterStatusToReport()
+ try:
+ cluster_status = get_cluster_status(gcs_client.address)
+ total_resources = cluster_status.total_resources()
+ result.total_num_cpus = int(total_resources.get("CPU", 0))
+ result.total_num_gpus = int(total_resources.get("GPU", 0))
+
+ to_GiB = 1 / 2**30
+ result.total_memory_gb = total_resources.get("memory", 0) * to_GiB
+ result.total_object_store_memory_gb = (
+ total_resources.get("object_store_memory", 0) * to_GiB
+ )
+ except Exception as e:
+ logger.info(f"Failed to get cluster status to report {e}")
+ finally:
+ return result
+
+
+def get_cluster_status_to_report(gcs_client: GcsClient) -> ClusterStatusToReport:
+ """Get the current status of this cluster.
+
+ It is a blocking API.
+
+ Params:
+ gcs_client: The GCS client to perform KV operation GET.
+
+ Returns:
+ The current cluster status or empty if it fails to get that information.
+ """
+ try:
+
+ from ray.autoscaler.v2.utils import is_autoscaler_v2
+
+ if is_autoscaler_v2():
+ return _get_cluster_status_to_report_v2(gcs_client)
+
+ cluster_status = gcs_client.internal_kv_get(
+ ray._private.ray_constants.DEBUG_AUTOSCALING_STATUS.encode(),
+ namespace=None,
+ )
+ if not cluster_status:
+ return ClusterStatusToReport()
+
+ result = ClusterStatusToReport()
+ to_GiB = 1 / 2**30
+ cluster_status = json.loads(cluster_status.decode("utf-8"))
+ if (
+ "load_metrics_report" not in cluster_status
+ or "usage" not in cluster_status["load_metrics_report"]
+ ):
+ return ClusterStatusToReport()
+
+ usage = cluster_status["load_metrics_report"]["usage"]
+ # usage is a map from resource to (used, total) pair
+ if "CPU" in usage:
+ result.total_num_cpus = int(usage["CPU"][1])
+ if "GPU" in usage:
+ result.total_num_gpus = int(usage["GPU"][1])
+ if "memory" in usage:
+ result.total_memory_gb = usage["memory"][1] * to_GiB
+ if "object_store_memory" in usage:
+ result.total_object_store_memory_gb = (
+ usage["object_store_memory"][1] * to_GiB
+ )
+ return result
+ except Exception as e:
+ logger.info(f"Failed to get cluster status to report {e}")
+ return ClusterStatusToReport()
+
+
+def get_cloud_from_metadata_requests() -> str:
+ def cloud_metadata_request(url: str, headers: Optional[Dict[str, str]]) -> bool:
+ try:
+ res = requests.get(url, headers=headers, timeout=1)
+ # Only accept successful responses (200 OK) to avoid false positives like 400 - Bad Request
+ # when multiple cloud providers use the same IP (169.254.169.254)
+ if res.status_code == 200:
+ return True
+ # ConnectionError is a superclass of ConnectTimeout
+ except requests.exceptions.ConnectionError:
+ pass
+ except Exception as e:
+ logger.info(
+ f"Unexpected exception when making cloud provider metadata request: {e}"
+ )
+ return False
+
+ AZURE_METADATA_URL = (
+ "http://169.254.169.254/metadata/instance?api-version=2021-12-13"
+ )
+ AZURE_METADATA_HEADERS = {"Metadata": "true"}
+ GCP_METADATA_URL = "http://metadata.google.internal/computeMetadata/v1"
+ GCP_METADATA_HEADERS = {"Metadata-Flavor": "Google"}
+ AWS_METADATA_URL = "http://169.254.169.254/latest/meta-data/"
+ AWS_METADATA_HEADERS = None
+
+ if cloud_metadata_request(AZURE_METADATA_URL, AZURE_METADATA_HEADERS):
+ return "azure"
+ elif cloud_metadata_request(GCP_METADATA_URL, GCP_METADATA_HEADERS):
+ return "gcp"
+ elif cloud_metadata_request(AWS_METADATA_URL, AWS_METADATA_HEADERS):
+ return "aws"
+ else:
+ return "unknown"
+
+
+def get_cluster_config_to_report(
+ cluster_config_file_path: str,
+) -> ClusterConfigToReport:
+ """Get the static cluster (autoscaler) config used to launch this cluster.
+
+ Params:
+ cluster_config_file_path: The file path to the cluster config file.
+
+ Returns:
+ The cluster (autoscaler) config or empty if it fails to get that information.
+ """
+
+ def get_instance_type(node_config):
+ if not node_config:
+ return None
+ if "InstanceType" in node_config:
+ # aws
+ return node_config["InstanceType"]
+ if "machineType" in node_config:
+ # gcp
+ return node_config["machineType"]
+ if (
+ "azure_arm_parameters" in node_config
+ and "vmSize" in node_config["azure_arm_parameters"]
+ ):
+ return node_config["azure_arm_parameters"]["vmSize"]
+ return None
+
+ try:
+ with open(cluster_config_file_path) as f:
+ config = yaml.safe_load(f)
+ result = ClusterConfigToReport()
+ if "min_workers" in config:
+ result.min_workers = config["min_workers"]
+ if "max_workers" in config:
+ result.max_workers = config["max_workers"]
+
+ if "provider" in config and "type" in config["provider"]:
+ result.cloud_provider = config["provider"]["type"]
+
+ if "head_node_type" not in config:
+ return result
+ if "available_node_types" not in config:
+ return result
+ head_node_type = config["head_node_type"]
+ available_node_types = config["available_node_types"]
+ for available_node_type in available_node_types:
+ if available_node_type == head_node_type:
+ head_node_instance_type = get_instance_type(
+ available_node_types[available_node_type].get("node_config")
+ )
+ if head_node_instance_type:
+ result.head_node_instance_type = head_node_instance_type
+ else:
+ worker_node_instance_type = get_instance_type(
+ available_node_types[available_node_type].get("node_config")
+ )
+ if worker_node_instance_type:
+ result.worker_node_instance_types = (
+ result.worker_node_instance_types or set()
+ )
+ result.worker_node_instance_types.add(worker_node_instance_type)
+ if result.worker_node_instance_types:
+ result.worker_node_instance_types = list(
+ result.worker_node_instance_types
+ )
+ return result
+ except FileNotFoundError:
+ # It's a manually started cluster or k8s cluster
+ result = ClusterConfigToReport()
+
+ # Check if we're on Kubernetes
+ if usage_constant.KUBERNETES_SERVICE_HOST_ENV in os.environ:
+ # Check if we're using KubeRay >= 0.4.0.
+ if usage_constant.KUBERAY_ENV in os.environ:
+ result.cloud_provider = usage_constant.PROVIDER_KUBERAY
+ # Else, we're on Kubernetes but not in either of the above categories.
+ else:
+ result.cloud_provider = usage_constant.PROVIDER_KUBERNETES_GENERIC
+
+ # if kubernetes was not set as cloud_provider vs. was set before
+ if result.cloud_provider is None:
+ result.cloud_provider = get_cloud_from_metadata_requests()
+ else:
+ result.cloud_provider += f"_${get_cloud_from_metadata_requests()}"
+
+ return result
+ except Exception as e:
+ logger.info(f"Failed to get cluster config to report {e}")
+ return ClusterConfigToReport()
+
+
+def get_cluster_metadata(gcs_client: GcsClient) -> dict:
+ """Get the cluster metadata from GCS.
+
+ It is a blocking API.
+
+ This will return None if `put_cluster_metadata` was never called.
+
+ Params:
+ gcs_client: The GCS client to perform KV operation GET.
+
+ Returns:
+ The cluster metadata in a dictionary.
+
+ Raises:
+ RuntimeError: If it fails to obtain cluster metadata from GCS.
+ """
+ return json.loads(
+ gcs_client.internal_kv_get(
+ usage_constant.CLUSTER_METADATA_KEY,
+ namespace=ray_constants.KV_NAMESPACE_CLUSTER,
+ ).decode("utf-8")
+ )
+
+
+def is_ray_init_cluster(gcs_client: ray._raylet.GcsClient) -> bool:
+ """Return whether the cluster is started by ray.init()"""
+ cluster_metadata = get_cluster_metadata(gcs_client)
+ return cluster_metadata["ray_init_cluster"]
+
+
+def generate_disabled_report_data() -> UsageStatsToReport:
+ """Generate the report data indicating usage stats is disabled"""
+ data = UsageStatsToReport(
+ schema_version=usage_constant.SCHEMA_VERSION,
+ source=os.getenv(
+ usage_constant.USAGE_STATS_SOURCE_ENV_VAR,
+ usage_constant.USAGE_STATS_SOURCE_OSS,
+ ),
+ collect_timestamp_ms=int(time.time() * 1000),
+ )
+ return data
+
+
+def generate_report_data(
+ cluster_config_to_report: ClusterConfigToReport,
+ total_success: int,
+ total_failed: int,
+ seq_number: int,
+ gcs_address: str,
+ cluster_id: str,
+) -> UsageStatsToReport:
+ """Generate the report data.
+
+ Params:
+ cluster_config_to_report: The cluster (autoscaler)
+ config generated by `get_cluster_config_to_report`.
+ total_success: The total number of successful report
+ for the lifetime of the cluster.
+ total_failed: The total number of failed report
+ for the lifetime of the cluster.
+ seq_number: The sequence number that's incremented whenever
+ a new report is sent.
+ gcs_address: the address of gcs to get data to report.
+ cluster_id: hex id of the cluster.
+
+ Returns:
+ UsageStats
+ """
+ assert cluster_id
+
+ gcs_client = ray._raylet.GcsClient(address=gcs_address, cluster_id=cluster_id)
+
+ cluster_metadata = get_cluster_metadata(gcs_client)
+ cluster_status_to_report = get_cluster_status_to_report(gcs_client)
+
+ data = UsageStatsToReport(
+ schema_version=usage_constant.SCHEMA_VERSION,
+ source=os.getenv(
+ usage_constant.USAGE_STATS_SOURCE_ENV_VAR,
+ usage_constant.USAGE_STATS_SOURCE_OSS,
+ ),
+ collect_timestamp_ms=int(time.time() * 1000),
+ total_success=total_success,
+ total_failed=total_failed,
+ seq_number=seq_number,
+ ray_version=cluster_metadata["ray_version"],
+ python_version=cluster_metadata["python_version"],
+ session_id=cluster_id,
+ git_commit=cluster_metadata["git_commit"],
+ os=cluster_metadata["os"],
+ session_start_timestamp_ms=cluster_metadata["session_start_timestamp_ms"],
+ cloud_provider=cluster_config_to_report.cloud_provider,
+ min_workers=cluster_config_to_report.min_workers,
+ max_workers=cluster_config_to_report.max_workers,
+ head_node_instance_type=cluster_config_to_report.head_node_instance_type,
+ worker_node_instance_types=cluster_config_to_report.worker_node_instance_types,
+ total_num_cpus=cluster_status_to_report.total_num_cpus,
+ total_num_gpus=cluster_status_to_report.total_num_gpus,
+ total_memory_gb=cluster_status_to_report.total_memory_gb,
+ total_object_store_memory_gb=cluster_status_to_report.total_object_store_memory_gb, # noqa: E501
+ library_usages=get_library_usages_to_report(gcs_client),
+ extra_usage_tags=get_extra_usage_tags_to_report(gcs_client),
+ total_num_nodes=get_total_num_alive_nodes_to_report(gcs_client),
+ total_num_running_jobs=get_total_num_running_jobs_to_report(gcs_client),
+ libc_version=cluster_metadata.get("libc_version"),
+ hardware_usages=get_hardware_usages_to_report(gcs_client),
+ )
+ return data
+
+
+def generate_write_data(
+ usage_stats: UsageStatsToReport,
+ error: str,
+) -> UsageStatsToWrite:
+ """Generate the report data.
+
+ Params:
+ usage_stats: The usage stats that were reported.
+ error: The error message of failed reports.
+
+ Returns:
+ UsageStatsToWrite
+ """
+ data = UsageStatsToWrite(
+ usage_stats=usage_stats,
+ success=error is None,
+ error=error,
+ )
+ return data
+
+
+class UsageReportClient:
+ """The client implementation for usage report.
+
+ It is in charge of writing usage stats to the directory
+ and report usage stats.
+ """
+
+ def write_usage_data(self, data: UsageStatsToWrite, dir_path: str) -> None:
+ """Write the usage data to the directory.
+
+ Params:
+ data: Data to report
+ dir_path: The path to the directory to write usage data.
+ """
+ # Atomically update the file.
+ dir_path = Path(dir_path)
+ destination = dir_path / usage_constant.USAGE_STATS_FILE
+ temp = dir_path / f"{usage_constant.USAGE_STATS_FILE}.tmp"
+ with temp.open(mode="w") as json_file:
+ json_file.write(json.dumps(asdict(data)))
+ if sys.platform == "win32":
+ # Windows 32 doesn't support atomic renaming, so we should delete
+ # the file first.
+ destination.unlink(missing_ok=True)
+ temp.rename(destination)
+
+ def report_usage_data(self, url: str, data: UsageStatsToReport) -> None:
+ """Report the usage data to the usage server.
+
+ Params:
+ url: The URL to update resource usage.
+ data: Data to report.
+
+ Raises:
+ requests.HTTPError: If requests fails.
+ """
+ r = requests.request(
+ "POST",
+ url,
+ headers={"Content-Type": "application/json"},
+ json=asdict(data),
+ timeout=10,
+ )
+ r.raise_for_status()
+ return r
diff --git a/lib/python3.12/site-packages/ray/_common/utils.py b/lib/python3.12/site-packages/ray/_common/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..32842113479c5303454ba453a256aea7db35d8ba
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_common/utils.py
@@ -0,0 +1,368 @@
+import asyncio
+import binascii
+import errno
+import importlib
+import inspect
+import os
+import random
+import string
+import sys
+import tempfile
+from inspect import signature
+from types import ModuleType
+from typing import Any, Coroutine, Dict, Optional, Tuple
+
+import psutil
+
+
+def import_module_and_attr(
+ full_path: str, *, reload_module: bool = False
+) -> Tuple[ModuleType, Any]:
+ """Given a full import path to a module attr, return the imported module and attr.
+
+ If `reload_module` is set, the module will be reloaded using `importlib.reload`.
+
+ Args:
+ full_path: The full import path to the module and attr.
+ reload_module: Whether to reload the module.
+
+ Returns:
+ A tuple of the imported module and attr.
+ """
+ if ":" in full_path:
+ if full_path.count(":") > 1:
+ raise ValueError(
+ f'Got invalid import path "{full_path}". An '
+ "import path may have at most one colon."
+ )
+ module_name, attr_name = full_path.split(":")
+ else:
+ last_period_idx = full_path.rfind(".")
+ module_name = full_path[:last_period_idx]
+ attr_name = full_path[last_period_idx + 1 :]
+ module = importlib.import_module(module_name)
+ if reload_module:
+ importlib.reload(module)
+ return module, getattr(module, attr_name)
+
+
+def import_attr(full_path: str, *, reload_module: bool = False) -> Any:
+ """Given a full import path to a module attr, return the imported attr.
+
+ If `reload_module` is set, the module will be reloaded using `importlib.reload`.
+
+ For example, the following are equivalent:
+ MyClass = import_attr("module.submodule:MyClass")
+ MyClass = import_attr("module.submodule.MyClass")
+ from module.submodule import MyClass
+
+ Returns:
+ Imported attr
+ """
+ return import_module_and_attr(full_path, reload_module=reload_module)[1]
+
+
+def get_or_create_event_loop() -> asyncio.AbstractEventLoop:
+ """Get a running async event loop if one exists, otherwise create one.
+
+ This function serves as a proxy for the deprecating get_event_loop().
+ It tries to get the running loop first, and if no running loop
+ could be retrieved:
+ - For python version <3.10: it falls back to the get_event_loop
+ call.
+ - For python version >= 3.10: it uses the same python implementation
+ of _get_event_loop() at asyncio/events.py.
+
+ Ideally, one should use high level APIs like asyncio.run() with python
+ version >= 3.7, if not possible, one should create and manage the event
+ loops explicitly.
+ """
+ vers_info = sys.version_info
+ if vers_info.major >= 3 and vers_info.minor >= 10:
+ # This follows the implementation of the deprecating `get_event_loop`
+ # in python3.10's asyncio. See python3.10/asyncio/events.py
+ # _get_event_loop()
+ try:
+ loop = asyncio.get_running_loop()
+ assert loop is not None
+ return loop
+ except RuntimeError as e:
+ # No running loop, relying on the error message as for now to
+ # differentiate runtime errors.
+ assert "no running event loop" in str(e)
+ return asyncio.get_event_loop_policy().get_event_loop()
+
+ return asyncio.get_event_loop()
+
+
+_BACKGROUND_TASKS = set()
+
+
+def run_background_task(coroutine: Coroutine) -> asyncio.Task:
+ """Schedule a task reliably to the event loop.
+
+ This API is used when you don't want to cache the reference of `asyncio.Task`.
+ For example,
+
+ ```
+ get_event_loop().create_task(coroutine(*args))
+ ```
+
+ The above code doesn't guarantee to schedule the coroutine to the event loops
+
+ When using create_task in a "fire and forget" way, we should keep the references
+ alive for the reliable execution. This API is used to fire and forget
+ asynchronous execution.
+
+ https://docs.python.org/3/library/asyncio-task.html#creating-tasks
+ """
+ task = get_or_create_event_loop().create_task(coroutine)
+ # Add task to the set. This creates a strong reference.
+ _BACKGROUND_TASKS.add(task)
+
+ # To prevent keeping references to finished tasks forever,
+ # make each task remove its own reference from the set after
+ # completion:
+ task.add_done_callback(_BACKGROUND_TASKS.discard)
+ return task
+
+
+# Used in gpu detection
+RESOURCE_CONSTRAINT_PREFIX = "accelerator_type:"
+PLACEMENT_GROUP_BUNDLE_RESOURCE_NAME = "bundle"
+
+
+def resources_from_ray_options(options_dict: Dict[str, Any]) -> Dict[str, Any]:
+ """Determine a task's resource requirements.
+
+ Args:
+ options_dict: The dictionary that contains resources requirements.
+
+ Returns:
+ A dictionary of the resource requirements for the task.
+ """
+ resources = (options_dict.get("resources") or {}).copy()
+
+ if "CPU" in resources or "GPU" in resources:
+ raise ValueError(
+ "The resources dictionary must not contain the key 'CPU' or 'GPU'"
+ )
+ elif "memory" in resources or "object_store_memory" in resources:
+ raise ValueError(
+ "The resources dictionary must not "
+ "contain the key 'memory' or 'object_store_memory'"
+ )
+ elif PLACEMENT_GROUP_BUNDLE_RESOURCE_NAME in resources:
+ raise ValueError(
+ "The resource should not include `bundle` which "
+ f"is reserved for Ray. resources: {resources}"
+ )
+
+ num_cpus = options_dict.get("num_cpus")
+ num_gpus = options_dict.get("num_gpus")
+ memory = options_dict.get("memory")
+ object_store_memory = options_dict.get("object_store_memory")
+ accelerator_type = options_dict.get("accelerator_type")
+
+ if num_cpus is not None:
+ resources["CPU"] = num_cpus
+ if num_gpus is not None:
+ resources["GPU"] = num_gpus
+ if memory is not None:
+ resources["memory"] = int(memory)
+ if object_store_memory is not None:
+ resources["object_store_memory"] = object_store_memory
+ if accelerator_type is not None:
+ resources[f"{RESOURCE_CONSTRAINT_PREFIX}{accelerator_type}"] = 0.001
+
+ return resources
+
+
+# Match the standard alphabet used for UUIDs.
+RANDOM_STRING_ALPHABET = string.ascii_lowercase + string.digits
+
+
+def get_random_alphanumeric_string(length: int):
+ """Generates random string of length consisting exclusively of
+ - Lower-case ASCII chars
+ - Digits
+ """
+ return "".join(random.choices(RANDOM_STRING_ALPHABET, k=length))
+
+
+_PRINTED_WARNING = set()
+
+
+def get_call_location(back: int = 1):
+ """
+ Get the location (filename and line number) of a function caller, `back`
+ frames up the stack.
+
+ Args:
+ back: The number of frames to go up the stack, not including this
+ function.
+
+ Returns:
+ A string with the filename and line number of the caller.
+ For example, "myfile.py:123".
+ """
+ stack = inspect.stack()
+ try:
+ frame = stack[back + 1]
+ return f"{frame.filename}:{frame.lineno}"
+ except IndexError:
+ return "UNKNOWN"
+
+
+def get_user_temp_dir():
+ if "RAY_TMPDIR" in os.environ:
+ return os.environ["RAY_TMPDIR"]
+ elif sys.platform.startswith("linux") and "TMPDIR" in os.environ:
+ return os.environ["TMPDIR"]
+ elif sys.platform.startswith("darwin") or sys.platform.startswith("linux"):
+ # Ideally we wouldn't need this fallback, but keep it for now for
+ # for compatibility
+ tempdir = os.path.join(os.sep, "tmp")
+ else:
+ tempdir = tempfile.gettempdir()
+ return tempdir
+
+
+def get_ray_temp_dir():
+ return os.path.join(get_user_temp_dir(), "ray")
+
+
+def get_ray_address_file(temp_dir: Optional[str]):
+ if temp_dir is None:
+ temp_dir = get_ray_temp_dir()
+ return os.path.join(temp_dir, "ray_current_cluster")
+
+
+def reset_ray_address(temp_dir: Optional[str] = None):
+ address_file = get_ray_address_file(temp_dir)
+ if os.path.exists(address_file):
+ try:
+ os.remove(address_file)
+ except OSError:
+ pass
+
+
+def load_class(path):
+ """Load a class at runtime given a full path.
+
+ Example of the path: mypkg.mysubpkg.myclass
+ """
+ class_data = path.split(".")
+ if len(class_data) < 2:
+ raise ValueError("You need to pass a valid path like mymodule.provider_class")
+ module_path = ".".join(class_data[:-1])
+ class_str = class_data[-1]
+ module = importlib.import_module(module_path)
+ return getattr(module, class_str)
+
+
+def get_system_memory(
+ # For cgroups v1:
+ memory_limit_filename: str = "/sys/fs/cgroup/memory/memory.limit_in_bytes",
+ # For cgroups v2:
+ memory_limit_filename_v2: str = "/sys/fs/cgroup/memory.max",
+):
+ """Return the total amount of system memory in bytes.
+
+ Args:
+ memory_limit_filename: The path to the file that contains the memory
+ limit for the Docker container. Defaults to
+ /sys/fs/cgroup/memory/memory.limit_in_bytes.
+ memory_limit_filename_v2: The path to the file that contains the memory
+ limit for the Docker container in cgroups v2. Defaults to
+ /sys/fs/cgroup/memory.max.
+
+ Returns:
+ The total amount of system memory in bytes.
+ """
+ # Try to accurately figure out the memory limit if we are in a docker
+ # container. Note that this file is not specific to Docker and its value is
+ # often much larger than the actual amount of memory.
+ docker_limit = None
+ if os.path.exists(memory_limit_filename):
+ with open(memory_limit_filename, "r") as f:
+ docker_limit = int(f.read().strip())
+ elif os.path.exists(memory_limit_filename_v2):
+ with open(memory_limit_filename_v2, "r") as f:
+ # Don't forget to strip() the newline:
+ max_file = f.read().strip()
+ if max_file.isnumeric():
+ docker_limit = int(max_file)
+ else:
+ # max_file is "max", i.e. is unset.
+ docker_limit = None
+
+ # Use psutil if it is available.
+ psutil_memory_in_bytes = psutil.virtual_memory().total
+
+ if docker_limit is not None:
+ # We take the min because the cgroup limit is very large if we aren't
+ # in Docker.
+ return min(docker_limit, psutil_memory_in_bytes)
+
+ return psutil_memory_in_bytes
+
+
+def binary_to_hex(identifier):
+ hex_identifier = binascii.hexlify(identifier)
+ hex_identifier = hex_identifier.decode()
+ return hex_identifier
+
+
+def hex_to_binary(hex_identifier):
+ return binascii.unhexlify(hex_identifier)
+
+
+def try_make_directory_shared(directory_path):
+ try:
+ os.chmod(directory_path, 0o0777)
+ except OSError as e:
+ # Silently suppress the PermissionError that is thrown by the chmod.
+ # This is done because the user attempting to change the permissions
+ # on a directory may not own it. The chmod is attempted whether the
+ # directory is new or not to avoid race conditions.
+ # ray-project/ray/#3591
+ if e.errno in [errno.EACCES, errno.EPERM]:
+ pass
+ else:
+ raise
+
+
+def try_to_create_directory(directory_path):
+ # Attempt to create a directory that is globally readable/writable.
+ directory_path = os.path.expanduser(directory_path)
+ os.makedirs(directory_path, exist_ok=True)
+ # Change the log directory permissions so others can use it. This is
+ # important when multiple people are using the same machine.
+ try_make_directory_shared(directory_path)
+
+
+def get_function_args(callable):
+ all_parameters = frozenset(signature(callable).parameters)
+ return list(all_parameters)
+
+
+def decode(byte_str: str, allow_none: bool = False, encode_type: str = "utf-8"):
+ """Make this unicode in Python 3, otherwise leave it as bytes.
+
+ Args:
+ byte_str: The byte string to decode.
+ allow_none: If true, then we will allow byte_str to be None in which
+ case we will return an empty string. TODO(rkn): Remove this flag.
+ This is only here to simplify upgrading to flatbuffers 1.10.0.
+ encode_type: The encoding type to use for decoding. Defaults to "utf-8".
+
+ Returns:
+ A byte string in Python 2 and a unicode string in Python 3.
+ """
+ if byte_str is None and allow_none:
+ return ""
+
+ if not isinstance(byte_str, bytes):
+ raise ValueError(f"The argument {byte_str} must be a bytes object.")
+ return byte_str.decode(encode_type)
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d4ddc25c5129d5ea55f2aab6b77d941f770d692e
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/_clonevirtualenv.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/_clonevirtualenv.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5deb6903e7b496641f82087ad11d21ee736ccb6f
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/_clonevirtualenv.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/conda.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/conda.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..889f098fec9d318eda327442b47175557e1b7cac
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/conda.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/conda_utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/conda_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2c35949241e240bb43c5830148c5d00887ddbbe0
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/conda_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/constants.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/constants.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7412bb53347ec56420975dbd93743b707c43bc3c
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/constants.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/context.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/context.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..44c3f0839e9edf972e3e8c56553a0ac7c5ff37c1
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/context.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/default_impl.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/default_impl.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..01d53acefebedff9cd2489d9ac297738e8b1e4f5
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/default_impl.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/dependency_utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/dependency_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9c53ecb19e7aa6b31e8e52850728884c7278c71a
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/dependency_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/image_uri.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/image_uri.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..84f1108b85a6dfd0dd652514dfdfa3b57e269330
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/image_uri.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/java_jars.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/java_jars.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4f97dd7b6090fecee339dff5866230c6aa1a4382
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/java_jars.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/nsight.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/nsight.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5b5e7e924238f89d235973b1bd2b4d67ee3da762
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/nsight.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/packaging.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/packaging.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..8bdebe60209a56844edc1faba04600cc53846d06
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/packaging.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/pip.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/pip.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f450107131cbf1ca77031e864a9d21244e30c32c
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/pip.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/plugin.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/plugin.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..72a91eda9e512250f43b39dc08eb3be7b6f97fe5
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/plugin.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/plugin_schema_manager.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/plugin_schema_manager.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7bfd5b85f1c7623ab9bb722d67cd3d971362ca55
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/plugin_schema_manager.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/protocol.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/protocol.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2425e4ebb91e4ba38951132665ccf32b026ac5e4
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/protocol.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/py_executable.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/py_executable.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0282ad9dce3f78dabb4e370ab39e497255c954bf
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/py_executable.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/py_modules.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/py_modules.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d6fd10f101e7ca9bd93e0e6c93aa36c0138e8665
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/py_modules.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/rocprof_sys.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/rocprof_sys.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..226dc34c017f0ee220f4d4d0e878812493d364cd
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/rocprof_sys.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/setup_hook.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/setup_hook.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7567a7bb6d953d425ec7cb0c2a30e94942b2094e
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/setup_hook.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/uri_cache.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/uri_cache.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1a3aa77a20ce6b4814178d095e817151a2e7e549
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/uri_cache.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0ce15bc706783dc1189948135707a1751e9df5ed
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/uv.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/uv.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d5672c7d5a5edce66fdb68fc0bcf368114ecbb15
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/uv.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/uv_runtime_env_hook.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/uv_runtime_env_hook.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..047733b4992302190a1761ca58a2ed0ba3976476
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/uv_runtime_env_hook.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/validation.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/validation.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ef0e5d3afb0a31057fa1763fb60eb28d431e7864
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/validation.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/virtualenv_utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/virtualenv_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b22dc5636fd1d8629eb07fdf4c1bb3aeb5407ccd
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/virtualenv_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/working_dir.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/working_dir.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..91709f6192984e20eaa52599800fc2493b59bc87
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/__pycache__/working_dir.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..694f72b836333743a6416b372fe8a7230566c876
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/main.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/main.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..31030a1e9df5b918f2b98b6e39b9ee5cc8427027
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/main.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/runtime_env_agent.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/runtime_env_agent.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a361a8aa3b6247cd08d72fce8049b70e1671652f
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/runtime_env_agent.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/runtime_env_consts.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/runtime_env_consts.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..62b35f4e253267290b009e9046f711f1b4bdca53
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/__pycache__/runtime_env_consts.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/INSTALLER b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/LICENSE b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..f26bcf4d2de6eb136e31006ca3ab447d5e488adf
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/LICENSE
@@ -0,0 +1,279 @@
+A. HISTORY OF THE SOFTWARE
+==========================
+
+Python was created in the early 1990s by Guido van Rossum at Stichting
+Mathematisch Centrum (CWI, see https://www.cwi.nl) in the Netherlands
+as a successor of a language called ABC. Guido remains Python's
+principal author, although it includes many contributions from others.
+
+In 1995, Guido continued his work on Python at the Corporation for
+National Research Initiatives (CNRI, see https://www.cnri.reston.va.us)
+in Reston, Virginia where he released several versions of the
+software.
+
+In May 2000, Guido and the Python core development team moved to
+BeOpen.com to form the BeOpen PythonLabs team. In October of the same
+year, the PythonLabs team moved to Digital Creations, which became
+Zope Corporation. In 2001, the Python Software Foundation (PSF, see
+https://www.python.org/psf/) was formed, a non-profit organization
+created specifically to own Python-related Intellectual Property.
+Zope Corporation was a sponsoring member of the PSF.
+
+All Python releases are Open Source (see https://opensource.org for
+the Open Source Definition). Historically, most, but not all, Python
+releases have also been GPL-compatible; the table below summarizes
+the various releases.
+
+ Release Derived Year Owner GPL-
+ from compatible? (1)
+
+ 0.9.0 thru 1.2 1991-1995 CWI yes
+ 1.3 thru 1.5.2 1.2 1995-1999 CNRI yes
+ 1.6 1.5.2 2000 CNRI no
+ 2.0 1.6 2000 BeOpen.com no
+ 1.6.1 1.6 2001 CNRI yes (2)
+ 2.1 2.0+1.6.1 2001 PSF no
+ 2.0.1 2.0+1.6.1 2001 PSF yes
+ 2.1.1 2.1+2.0.1 2001 PSF yes
+ 2.1.2 2.1.1 2002 PSF yes
+ 2.1.3 2.1.2 2002 PSF yes
+ 2.2 and above 2.1.1 2001-now PSF yes
+
+Footnotes:
+
+(1) GPL-compatible doesn't mean that we're distributing Python under
+ the GPL. All Python licenses, unlike the GPL, let you distribute
+ a modified version without making your changes open source. The
+ GPL-compatible licenses make it possible to combine Python with
+ other software that is released under the GPL; the others don't.
+
+(2) According to Richard Stallman, 1.6.1 is not GPL-compatible,
+ because its license has a choice of law clause. According to
+ CNRI, however, Stallman's lawyer has told CNRI's lawyer that 1.6.1
+ is "not incompatible" with the GPL.
+
+Thanks to the many outside volunteers who have worked under Guido's
+direction to make these releases possible.
+
+
+B. TERMS AND CONDITIONS FOR ACCESSING OR OTHERWISE USING PYTHON
+===============================================================
+
+Python software and documentation are licensed under the
+Python Software Foundation License Version 2.
+
+Starting with Python 3.8.6, examples, recipes, and other code in
+the documentation are dual licensed under the PSF License Version 2
+and the Zero-Clause BSD license.
+
+Some software incorporated into Python is under different licenses.
+The licenses are listed with code falling under that license.
+
+
+PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
+--------------------------------------------
+
+1. This LICENSE AGREEMENT is between the Python Software Foundation
+("PSF"), and the Individual or Organization ("Licensee") accessing and
+otherwise using this software ("Python") in source or binary form and
+its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, PSF hereby
+grants Licensee a nonexclusive, royalty-free, world-wide license to reproduce,
+analyze, test, perform and/or display publicly, prepare derivative works,
+distribute, and otherwise use Python alone or in any derivative version,
+provided, however, that PSF's License Agreement and PSF's notice of copyright,
+i.e., "Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010,
+2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022, 2023 Python Software Foundation;
+All Rights Reserved" are retained in Python alone or in any derivative version
+prepared by Licensee.
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python.
+
+4. PSF is making Python available to Licensee on an "AS IS"
+basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. Nothing in this License Agreement shall be deemed to create any
+relationship of agency, partnership, or joint venture between PSF and
+Licensee. This License Agreement does not grant permission to use PSF
+trademarks or trade name in a trademark sense to endorse or promote
+products or services of Licensee, or any third party.
+
+8. By copying, installing or otherwise using Python, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+BEOPEN.COM LICENSE AGREEMENT FOR PYTHON 2.0
+-------------------------------------------
+
+BEOPEN PYTHON OPEN SOURCE LICENSE AGREEMENT VERSION 1
+
+1. This LICENSE AGREEMENT is between BeOpen.com ("BeOpen"), having an
+office at 160 Saratoga Avenue, Santa Clara, CA 95051, and the
+Individual or Organization ("Licensee") accessing and otherwise using
+this software in source or binary form and its associated
+documentation ("the Software").
+
+2. Subject to the terms and conditions of this BeOpen Python License
+Agreement, BeOpen hereby grants Licensee a non-exclusive,
+royalty-free, world-wide license to reproduce, analyze, test, perform
+and/or display publicly, prepare derivative works, distribute, and
+otherwise use the Software alone or in any derivative version,
+provided, however, that the BeOpen Python License is retained in the
+Software, alone or in any derivative version prepared by Licensee.
+
+3. BeOpen is making the Software available to Licensee on an "AS IS"
+basis. BEOPEN MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, BEOPEN MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+4. BEOPEN SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF THE
+SOFTWARE FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS
+AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THE SOFTWARE, OR ANY
+DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+5. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+6. This License Agreement shall be governed by and interpreted in all
+respects by the law of the State of California, excluding conflict of
+law provisions. Nothing in this License Agreement shall be deemed to
+create any relationship of agency, partnership, or joint venture
+between BeOpen and Licensee. This License Agreement does not grant
+permission to use BeOpen trademarks or trade names in a trademark
+sense to endorse or promote products or services of Licensee, or any
+third party. As an exception, the "BeOpen Python" logos available at
+http://www.pythonlabs.com/logos.html may be used according to the
+permissions granted on that web page.
+
+7. By copying, installing or otherwise using the software, Licensee
+agrees to be bound by the terms and conditions of this License
+Agreement.
+
+
+CNRI LICENSE AGREEMENT FOR PYTHON 1.6.1
+---------------------------------------
+
+1. This LICENSE AGREEMENT is between the Corporation for National
+Research Initiatives, having an office at 1895 Preston White Drive,
+Reston, VA 20191 ("CNRI"), and the Individual or Organization
+("Licensee") accessing and otherwise using Python 1.6.1 software in
+source or binary form and its associated documentation.
+
+2. Subject to the terms and conditions of this License Agreement, CNRI
+hereby grants Licensee a nonexclusive, royalty-free, world-wide
+license to reproduce, analyze, test, perform and/or display publicly,
+prepare derivative works, distribute, and otherwise use Python 1.6.1
+alone or in any derivative version, provided, however, that CNRI's
+License Agreement and CNRI's notice of copyright, i.e., "Copyright (c)
+1995-2001 Corporation for National Research Initiatives; All Rights
+Reserved" are retained in Python 1.6.1 alone or in any derivative
+version prepared by Licensee. Alternately, in lieu of CNRI's License
+Agreement, Licensee may substitute the following text (omitting the
+quotes): "Python 1.6.1 is made available subject to the terms and
+conditions in CNRI's License Agreement. This Agreement together with
+Python 1.6.1 may be located on the internet using the following
+unique, persistent identifier (known as a handle): 1895.22/1013. This
+Agreement may also be obtained from a proxy server on the internet
+using the following URL: http://hdl.handle.net/1895.22/1013".
+
+3. In the event Licensee prepares a derivative work that is based on
+or incorporates Python 1.6.1 or any part thereof, and wants to make
+the derivative work available to others as provided herein, then
+Licensee hereby agrees to include in any such work a brief summary of
+the changes made to Python 1.6.1.
+
+4. CNRI is making Python 1.6.1 available to Licensee on an "AS IS"
+basis. CNRI MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, CNRI MAKES NO AND
+DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 1.6.1 WILL NOT
+INFRINGE ANY THIRD PARTY RIGHTS.
+
+5. CNRI SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+1.6.1 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 1.6.1,
+OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+6. This License Agreement will automatically terminate upon a material
+breach of its terms and conditions.
+
+7. This License Agreement shall be governed by the federal
+intellectual property law of the United States, including without
+limitation the federal copyright law, and, to the extent such
+U.S. federal law does not apply, by the law of the Commonwealth of
+Virginia, excluding Virginia's conflict of law provisions.
+Notwithstanding the foregoing, with regard to derivative works based
+on Python 1.6.1 that incorporate non-separable material that was
+previously distributed under the GNU General Public License (GPL), the
+law of the Commonwealth of Virginia shall govern this License
+Agreement only as to issues arising under or with respect to
+Paragraphs 4, 5, and 7 of this License Agreement. Nothing in this
+License Agreement shall be deemed to create any relationship of
+agency, partnership, or joint venture between CNRI and Licensee. This
+License Agreement does not grant permission to use CNRI trademarks or
+trade name in a trademark sense to endorse or promote products or
+services of Licensee, or any third party.
+
+8. By clicking on the "ACCEPT" button where indicated, or by copying,
+installing or otherwise using Python 1.6.1, Licensee agrees to be
+bound by the terms and conditions of this License Agreement.
+
+ ACCEPT
+
+
+CWI LICENSE AGREEMENT FOR PYTHON 0.9.0 THROUGH 1.2
+--------------------------------------------------
+
+Copyright (c) 1991 - 1995, Stichting Mathematisch Centrum Amsterdam,
+The Netherlands. All rights reserved.
+
+Permission to use, copy, modify, and distribute this software and its
+documentation for any purpose and without fee is hereby granted,
+provided that the above copyright notice appear in all copies and that
+both that copyright notice and this permission notice appear in
+supporting documentation, and that the name of Stichting Mathematisch
+Centrum or CWI not be used in advertising or publicity pertaining to
+distribution of the software without specific, written prior
+permission.
+
+STICHTING MATHEMATISCH CENTRUM DISCLAIMS ALL WARRANTIES WITH REGARD TO
+THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS, IN NO EVENT SHALL STICHTING MATHEMATISCH CENTRUM BE LIABLE
+FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ZERO-CLAUSE BSD LICENSE FOR CODE IN THE PYTHON DOCUMENTATION
+----------------------------------------------------------------------
+
+Permission to use, copy, modify, and/or distribute this software for any
+purpose with or without fee is hereby granted.
+
+THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
+REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
+OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+PERFORMANCE OF THIS SOFTWARE.
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/METADATA b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..c632040d66bf120a377fc3785940934361273a66
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/METADATA
@@ -0,0 +1,123 @@
+Metadata-Version: 2.3
+Name: aiohappyeyeballs
+Version: 2.6.1
+Summary: Happy Eyeballs for asyncio
+License: PSF-2.0
+Author: J. Nick Koston
+Author-email: nick@koston.org
+Requires-Python: >=3.9
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Natural Language :: English
+Classifier: Operating System :: OS Independent
+Classifier: Topic :: Software Development :: Libraries
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: License :: OSI Approved :: Python Software Foundation License
+Project-URL: Bug Tracker, https://github.com/aio-libs/aiohappyeyeballs/issues
+Project-URL: Changelog, https://github.com/aio-libs/aiohappyeyeballs/blob/main/CHANGELOG.md
+Project-URL: Documentation, https://aiohappyeyeballs.readthedocs.io
+Project-URL: Repository, https://github.com/aio-libs/aiohappyeyeballs
+Description-Content-Type: text/markdown
+
+# aiohappyeyeballs
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+---
+
+**Documentation**: https://aiohappyeyeballs.readthedocs.io
+
+**Source Code**: https://github.com/aio-libs/aiohappyeyeballs
+
+---
+
+[Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs)
+([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305.html))
+
+## Use case
+
+This library exists to allow connecting with
+[Happy Eyeballs](https://en.wikipedia.org/wiki/Happy_Eyeballs)
+([RFC 8305](https://www.rfc-editor.org/rfc/rfc8305.html))
+when you
+already have a list of addrinfo and not a DNS name.
+
+The stdlib version of `loop.create_connection()`
+will only work when you pass in an unresolved name which
+is not a good fit when using DNS caching or resolving
+names via another method such as `zeroconf`.
+
+## Installation
+
+Install this via pip (or your favourite package manager):
+
+`pip install aiohappyeyeballs`
+
+## License
+
+[aiohappyeyeballs is licensed under the same terms as cpython itself.](https://github.com/python/cpython/blob/main/LICENSE)
+
+## Example usage
+
+```python
+
+addr_infos = await loop.getaddrinfo("example.org", 80)
+
+socket = await start_connection(addr_infos)
+socket = await start_connection(addr_infos, local_addr_infos=local_addr_infos, happy_eyeballs_delay=0.2)
+
+transport, protocol = await loop.create_connection(
+ MyProtocol, sock=socket, ...)
+
+# Remove the first address for each family from addr_info
+pop_addr_infos_interleave(addr_info, 1)
+
+# Remove all matching address from addr_info
+remove_addr_infos(addr_info, "dead::beef::")
+
+# Convert a local_addr to local_addr_infos
+local_addr_infos = addr_to_addr_infos(("127.0.0.1",0))
+```
+
+## Credits
+
+This package contains code from cpython and is licensed under the same terms as cpython itself.
+
+This package was created with
+[Copier](https://copier.readthedocs.io/) and the
+[browniebroke/pypackage-template](https://github.com/browniebroke/pypackage-template)
+project template.
+
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/RECORD b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..9e0e2023f21e2df03b9b2d75aae5b116887feede
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/RECORD
@@ -0,0 +1,16 @@
+aiohappyeyeballs-2.6.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+aiohappyeyeballs-2.6.1.dist-info/LICENSE,sha256=Oy-B_iHRgcSZxZolbI4ZaEVdZonSaaqFNzv7avQdo78,13936
+aiohappyeyeballs-2.6.1.dist-info/METADATA,sha256=NSXlhJwAfi380eEjAo7BQ4P_TVal9xi0qkyZWibMsVM,5915
+aiohappyeyeballs-2.6.1.dist-info/RECORD,,
+aiohappyeyeballs-2.6.1.dist-info/WHEEL,sha256=XbeZDeTWKc1w7CSIyre5aMDU_-PohRwTQceYnisIYYY,88
+aiohappyeyeballs/__init__.py,sha256=x7kktHEtaD9quBcWDJPuLeKyjuVAI-Jj14S9B_5hcTs,361
+aiohappyeyeballs/__pycache__/__init__.cpython-312.pyc,,
+aiohappyeyeballs/__pycache__/_staggered.cpython-312.pyc,,
+aiohappyeyeballs/__pycache__/impl.cpython-312.pyc,,
+aiohappyeyeballs/__pycache__/types.cpython-312.pyc,,
+aiohappyeyeballs/__pycache__/utils.cpython-312.pyc,,
+aiohappyeyeballs/_staggered.py,sha256=edfVowFx-P-ywJjIEF3MdPtEMVODujV6CeMYr65otac,6900
+aiohappyeyeballs/impl.py,sha256=Dlcm2mTJ28ucrGnxkb_fo9CZzLAkOOBizOt7dreBbXE,9681
+aiohappyeyeballs/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+aiohappyeyeballs/types.py,sha256=YZJIAnyoV4Dz0WFtlaf_OyE4EW7Xus1z7aIfNI6tDDQ,425
+aiohappyeyeballs/utils.py,sha256=on9GxIR0LhEfZu8P6Twi9hepX9zDanuZM20MWsb3xlQ,3028
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/WHEEL b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..0582547b15f02d3a51659106262832565d5dc5ea
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohappyeyeballs-2.6.1.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: poetry-core 2.1.1
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/INSTALLER b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/METADATA b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..ba4116f99ff6066129469d5ec0a8f596fde9a7c7
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/METADATA
@@ -0,0 +1,262 @@
+Metadata-Version: 2.4
+Name: aiohttp
+Version: 3.13.2
+Summary: Async http client/server framework (asyncio)
+Maintainer-email: aiohttp team
+License: Apache-2.0 AND MIT
+Project-URL: Homepage, https://github.com/aio-libs/aiohttp
+Project-URL: Chat: Matrix, https://matrix.to/#/#aio-libs:matrix.org
+Project-URL: Chat: Matrix Space, https://matrix.to/#/#aio-libs-space:matrix.org
+Project-URL: CI: GitHub Actions, https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI
+Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/aiohttp
+Project-URL: Docs: Changelog, https://docs.aiohttp.org/en/stable/changes.html
+Project-URL: Docs: RTD, https://docs.aiohttp.org
+Project-URL: GitHub: issues, https://github.com/aio-libs/aiohttp/issues
+Project-URL: GitHub: repo, https://github.com/aio-libs/aiohttp
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Framework :: AsyncIO
+Classifier: Intended Audience :: Developers
+Classifier: Operating System :: POSIX
+Classifier: Operating System :: MacOS :: MacOS X
+Classifier: Operating System :: Microsoft :: Windows
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Classifier: Topic :: Internet :: WWW/HTTP
+Requires-Python: >=3.9
+Description-Content-Type: text/x-rst
+License-File: LICENSE.txt
+License-File: vendor/llhttp/LICENSE
+Requires-Dist: aiohappyeyeballs>=2.5.0
+Requires-Dist: aiosignal>=1.4.0
+Requires-Dist: async-timeout<6.0,>=4.0; python_version < "3.11"
+Requires-Dist: attrs>=17.3.0
+Requires-Dist: frozenlist>=1.1.1
+Requires-Dist: multidict<7.0,>=4.5
+Requires-Dist: propcache>=0.2.0
+Requires-Dist: yarl<2.0,>=1.17.0
+Provides-Extra: speedups
+Requires-Dist: aiodns>=3.3.0; extra == "speedups"
+Requires-Dist: Brotli; platform_python_implementation == "CPython" and extra == "speedups"
+Requires-Dist: brotlicffi; platform_python_implementation != "CPython" and extra == "speedups"
+Requires-Dist: backports.zstd; (platform_python_implementation == "CPython" and python_version < "3.14") and extra == "speedups"
+Dynamic: license-file
+
+==================================
+Async http client/server framework
+==================================
+
+.. image:: https://raw.githubusercontent.com/aio-libs/aiohttp/master/docs/aiohttp-plain.svg
+ :height: 64px
+ :width: 64px
+ :alt: aiohttp logo
+
+|
+
+.. image:: https://github.com/aio-libs/aiohttp/workflows/CI/badge.svg
+ :target: https://github.com/aio-libs/aiohttp/actions?query=workflow%3ACI
+ :alt: GitHub Actions status for master branch
+
+.. image:: https://codecov.io/gh/aio-libs/aiohttp/branch/master/graph/badge.svg
+ :target: https://codecov.io/gh/aio-libs/aiohttp
+ :alt: codecov.io status for master branch
+
+.. image:: https://badge.fury.io/py/aiohttp.svg
+ :target: https://pypi.org/project/aiohttp
+ :alt: Latest PyPI package version
+
+.. image:: https://img.shields.io/pypi/dm/aiohttp
+ :target: https://pypistats.org/packages/aiohttp
+ :alt: Downloads count
+
+.. image:: https://readthedocs.org/projects/aiohttp/badge/?version=latest
+ :target: https://docs.aiohttp.org/
+ :alt: Latest Read The Docs
+
+.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json
+ :target: https://codspeed.io/aio-libs/aiohttp
+ :alt: Codspeed.io status for aiohttp
+
+
+Key Features
+============
+
+- Supports both client and server side of HTTP protocol.
+- Supports both client and server Web-Sockets out-of-the-box and avoids
+ Callback Hell.
+- Provides Web-server with middleware and pluggable routing.
+
+
+Getting started
+===============
+
+Client
+------
+
+To get something from the web:
+
+.. code-block:: python
+
+ import aiohttp
+ import asyncio
+
+ async def main():
+
+ async with aiohttp.ClientSession() as session:
+ async with session.get('http://python.org') as response:
+
+ print("Status:", response.status)
+ print("Content-type:", response.headers['content-type'])
+
+ html = await response.text()
+ print("Body:", html[:15], "...")
+
+ asyncio.run(main())
+
+This prints:
+
+.. code-block::
+
+ Status: 200
+ Content-type: text/html; charset=utf-8
+ Body: ...
+
+Coming from `requests `_ ? Read `why we need so many lines `_.
+
+Server
+------
+
+An example using a simple server:
+
+.. code-block:: python
+
+ # examples/server_simple.py
+ from aiohttp import web
+
+ async def handle(request):
+ name = request.match_info.get('name', "Anonymous")
+ text = "Hello, " + name
+ return web.Response(text=text)
+
+ async def wshandle(request):
+ ws = web.WebSocketResponse()
+ await ws.prepare(request)
+
+ async for msg in ws:
+ if msg.type == web.WSMsgType.text:
+ await ws.send_str("Hello, {}".format(msg.data))
+ elif msg.type == web.WSMsgType.binary:
+ await ws.send_bytes(msg.data)
+ elif msg.type == web.WSMsgType.close:
+ break
+
+ return ws
+
+
+ app = web.Application()
+ app.add_routes([web.get('/', handle),
+ web.get('/echo', wshandle),
+ web.get('/{name}', handle)])
+
+ if __name__ == '__main__':
+ web.run_app(app)
+
+
+Documentation
+=============
+
+https://aiohttp.readthedocs.io/
+
+
+Demos
+=====
+
+https://github.com/aio-libs/aiohttp-demos
+
+
+External links
+==============
+
+* `Third party libraries
+ `_
+* `Built with aiohttp
+ `_
+* `Powered by aiohttp
+ `_
+
+Feel free to make a Pull Request for adding your link to these pages!
+
+
+Communication channels
+======================
+
+*aio-libs Discussions*: https://github.com/aio-libs/aiohttp/discussions
+
+*Matrix*: `#aio-libs:matrix.org `_
+
+We support `Stack Overflow
+`_.
+Please add *aiohttp* tag to your question there.
+
+Requirements
+============
+
+- attrs_
+- multidict_
+- yarl_
+- frozenlist_
+
+Optionally you may install the aiodns_ library (highly recommended for sake of speed).
+
+.. _aiodns: https://pypi.python.org/pypi/aiodns
+.. _attrs: https://github.com/python-attrs/attrs
+.. _multidict: https://pypi.python.org/pypi/multidict
+.. _frozenlist: https://pypi.org/project/frozenlist/
+.. _yarl: https://pypi.python.org/pypi/yarl
+.. _async-timeout: https://pypi.python.org/pypi/async_timeout
+
+License
+=======
+
+``aiohttp`` is offered under the Apache 2 license.
+
+
+Keepsafe
+========
+
+The aiohttp community would like to thank Keepsafe
+(https://www.getkeepsafe.com) for its support in the early days of
+the project.
+
+
+Source code
+===========
+
+The latest developer version is available in a GitHub repository:
+https://github.com/aio-libs/aiohttp
+
+Benchmarks
+==========
+
+If you are interested in efficiency, the AsyncIO community maintains a
+list of benchmarks on the official wiki:
+https://github.com/python/asyncio/wiki/Benchmarks
+
+--------
+
+.. image:: https://img.shields.io/matrix/aio-libs:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
+ :target: https://matrix.to/#/%23aio-libs:matrix.org
+ :alt: Matrix Room — #aio-libs:matrix.org
+
+.. image:: https://img.shields.io/matrix/aio-libs-space:matrix.org?label=Discuss%20on%20Matrix%20at%20%23aio-libs-space%3Amatrix.org&logo=matrix&server_fqdn=matrix.org&style=flat
+ :target: https://matrix.to/#/%23aio-libs-space:matrix.org
+ :alt: Matrix Space — #aio-libs-space:matrix.org
+
+.. image:: https://insights.linuxfoundation.org/api/badge/health-score?project=aiohttp
+ :target: https://insights.linuxfoundation.org/project/aiohttp
+ :alt: LFX Health Score
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/RECORD b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..edd2864e9e76b5e1773e2dd2a4cbfbf53e5597fc
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/RECORD
@@ -0,0 +1,139 @@
+aiohttp-3.13.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+aiohttp-3.13.2.dist-info/METADATA,sha256=3xr8ZyYTInh909TqCdZhKIC37g5nTgyP-Nj_yglCs5A,8135
+aiohttp-3.13.2.dist-info/RECORD,,
+aiohttp-3.13.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+aiohttp-3.13.2.dist-info/WHEEL,sha256=DxRnWQz-Kp9-4a4hdDHsSv0KUC3H7sN9Nbef3-8RjXU,190
+aiohttp-3.13.2.dist-info/licenses/LICENSE.txt,sha256=n4DQ2311WpQdtFchcsJw7L2PCCuiFd3QlZhZQu2Uqes,588
+aiohttp-3.13.2.dist-info/licenses/vendor/llhttp/LICENSE,sha256=68qFTgE0zSVtZzYnwgSZ9CV363S6zwi58ltianPJEnc,1105
+aiohttp-3.13.2.dist-info/top_level.txt,sha256=iv-JIaacmTl-hSho3QmphcKnbRRYx1st47yjz_178Ro,8
+aiohttp/.hash/_cparser.pxd.hash,sha256=pjs-sEXNw_eijXGAedwG-BHnlFp8B7sOCgUagIWaU2A,121
+aiohttp/.hash/_find_header.pxd.hash,sha256=_mbpD6vM-CVCKq3ulUvsOAz5Wdo88wrDzfpOsMQaMNA,125
+aiohttp/.hash/_http_parser.pyx.hash,sha256=ju4DG_uNv8rTD6pu3IunE1ysx3ZbH4OjiQHUb_URSoA,125
+aiohttp/.hash/_http_writer.pyx.hash,sha256=9txOh7t7c3y-vLmiuEY5dltmXvEo0CYyU4U853yyv9E,125
+aiohttp/.hash/hdrs.py.hash,sha256=v6IaKbsxjsdQxBzhb5AjP0x_9G3rUe84D7avf7AI4cs,116
+aiohttp/__init__.py,sha256=YJ2jOOSU0hSTbloGbi5-jtcDfSmpBp2RTQEQAt0ccOA,8302
+aiohttp/__pycache__/__init__.cpython-312.pyc,,
+aiohttp/__pycache__/_cookie_helpers.cpython-312.pyc,,
+aiohttp/__pycache__/abc.cpython-312.pyc,,
+aiohttp/__pycache__/base_protocol.cpython-312.pyc,,
+aiohttp/__pycache__/client.cpython-312.pyc,,
+aiohttp/__pycache__/client_exceptions.cpython-312.pyc,,
+aiohttp/__pycache__/client_middleware_digest_auth.cpython-312.pyc,,
+aiohttp/__pycache__/client_middlewares.cpython-312.pyc,,
+aiohttp/__pycache__/client_proto.cpython-312.pyc,,
+aiohttp/__pycache__/client_reqrep.cpython-312.pyc,,
+aiohttp/__pycache__/client_ws.cpython-312.pyc,,
+aiohttp/__pycache__/compression_utils.cpython-312.pyc,,
+aiohttp/__pycache__/connector.cpython-312.pyc,,
+aiohttp/__pycache__/cookiejar.cpython-312.pyc,,
+aiohttp/__pycache__/formdata.cpython-312.pyc,,
+aiohttp/__pycache__/hdrs.cpython-312.pyc,,
+aiohttp/__pycache__/helpers.cpython-312.pyc,,
+aiohttp/__pycache__/http.cpython-312.pyc,,
+aiohttp/__pycache__/http_exceptions.cpython-312.pyc,,
+aiohttp/__pycache__/http_parser.cpython-312.pyc,,
+aiohttp/__pycache__/http_websocket.cpython-312.pyc,,
+aiohttp/__pycache__/http_writer.cpython-312.pyc,,
+aiohttp/__pycache__/log.cpython-312.pyc,,
+aiohttp/__pycache__/multipart.cpython-312.pyc,,
+aiohttp/__pycache__/payload.cpython-312.pyc,,
+aiohttp/__pycache__/payload_streamer.cpython-312.pyc,,
+aiohttp/__pycache__/pytest_plugin.cpython-312.pyc,,
+aiohttp/__pycache__/resolver.cpython-312.pyc,,
+aiohttp/__pycache__/streams.cpython-312.pyc,,
+aiohttp/__pycache__/tcp_helpers.cpython-312.pyc,,
+aiohttp/__pycache__/test_utils.cpython-312.pyc,,
+aiohttp/__pycache__/tracing.cpython-312.pyc,,
+aiohttp/__pycache__/typedefs.cpython-312.pyc,,
+aiohttp/__pycache__/web.cpython-312.pyc,,
+aiohttp/__pycache__/web_app.cpython-312.pyc,,
+aiohttp/__pycache__/web_exceptions.cpython-312.pyc,,
+aiohttp/__pycache__/web_fileresponse.cpython-312.pyc,,
+aiohttp/__pycache__/web_log.cpython-312.pyc,,
+aiohttp/__pycache__/web_middlewares.cpython-312.pyc,,
+aiohttp/__pycache__/web_protocol.cpython-312.pyc,,
+aiohttp/__pycache__/web_request.cpython-312.pyc,,
+aiohttp/__pycache__/web_response.cpython-312.pyc,,
+aiohttp/__pycache__/web_routedef.cpython-312.pyc,,
+aiohttp/__pycache__/web_runner.cpython-312.pyc,,
+aiohttp/__pycache__/web_server.cpython-312.pyc,,
+aiohttp/__pycache__/web_urldispatcher.cpython-312.pyc,,
+aiohttp/__pycache__/web_ws.cpython-312.pyc,,
+aiohttp/__pycache__/worker.cpython-312.pyc,,
+aiohttp/_cookie_helpers.py,sha256=INC-1MTQU7yJqBVmV48Fw30kzZH47KdVUrP_bbfpGvs,13647
+aiohttp/_cparser.pxd,sha256=UnbUYCHg4NdXfgyRVYAMv2KTLWClB4P-xCrvtj_r7ew,4295
+aiohttp/_find_header.pxd,sha256=0GfwFCPN2zxEKTO1_MA5sYq2UfzsG8kcV3aTqvwlz3g,68
+aiohttp/_headers.pxi,sha256=n701k28dVPjwRnx5j6LpJhLTfj7dqu2vJt7f0O60Oyg,2007
+aiohttp/_http_parser.cpython-312-x86_64-linux-gnu.so,sha256=3YrWjkYJoctR2UCDpht9IcnT4PE18AnSoHlgI11fN5k,2824440
+aiohttp/_http_parser.pyx,sha256=tmA1PaJn7H8U1nyXtoHJV44pxYVzqXAf1UgJaYPaw28,28219
+aiohttp/_http_writer.cpython-312-x86_64-linux-gnu.so,sha256=mWC-4rsbntVD1V5ZEKEpSW_sm63V0XRe1fDR0lygipo,539144
+aiohttp/_http_writer.pyx,sha256=VlFEBM6HoVv8a0AAJtc6JwFlsv2-cDE8-gB94p3dfhQ,4664
+aiohttp/_websocket/.hash/mask.pxd.hash,sha256=Y0zBddk_ck3pi9-BFzMcpkcvCKvwvZ4GTtZFb9u1nxQ,128
+aiohttp/_websocket/.hash/mask.pyx.hash,sha256=90owpXYM8_kIma4KUcOxhWSk-Uv4NVMBoCYeFM1B3d0,128
+aiohttp/_websocket/.hash/reader_c.pxd.hash,sha256=5xf3oobk6vx4xbJm-xtZ1_QufB8fYFtLQV2MNdqUc1w,132
+aiohttp/_websocket/__init__.py,sha256=Mar3R9_vBN_Ea4lsW7iTAVXD7OKswKPGqF5xgSyt77k,44
+aiohttp/_websocket/__pycache__/__init__.cpython-312.pyc,,
+aiohttp/_websocket/__pycache__/helpers.cpython-312.pyc,,
+aiohttp/_websocket/__pycache__/models.cpython-312.pyc,,
+aiohttp/_websocket/__pycache__/reader.cpython-312.pyc,,
+aiohttp/_websocket/__pycache__/reader_c.cpython-312.pyc,,
+aiohttp/_websocket/__pycache__/reader_py.cpython-312.pyc,,
+aiohttp/_websocket/__pycache__/writer.cpython-312.pyc,,
+aiohttp/_websocket/helpers.py,sha256=P-XLv8IUaihKzDenVUqfKU5DJbWE5HvG8uhvUZK8Ic4,5038
+aiohttp/_websocket/mask.cpython-312-x86_64-linux-gnu.so,sha256=EpRwPJm1K1yavMCd9llAWdT4AsqKx_QEN0rb0eJH_Kc,263512
+aiohttp/_websocket/mask.pxd,sha256=sBmZ1Amym9kW4Ge8lj1fLZ7mPPya4LzLdpkQExQXv5M,112
+aiohttp/_websocket/mask.pyx,sha256=BHjOtV0O0w7xp9p0LNADRJvGmgfPn9sGeJvSs0fL__4,1397
+aiohttp/_websocket/models.py,sha256=XAzjs_8JYszWXIgZ6R3ZRrF-tX9Q_6LiD49WRYojopM,2121
+aiohttp/_websocket/reader.py,sha256=eC4qS0c5sOeQ2ebAHLaBpIaTVFaSKX79pY2xvh3Pqyw,1030
+aiohttp/_websocket/reader_c.cpython-312-x86_64-linux-gnu.so,sha256=G-fK_d5U4UcA5kBo53v_5AYeOxksxvuy5b3y_XrZD04,1824528
+aiohttp/_websocket/reader_c.pxd,sha256=nl_njtDrzlQU0rjgGGjZDB-swguE0tX_bCPobkShVa4,2625
+aiohttp/_websocket/reader_c.py,sha256=gSsE_iSBr7-ORvOmgkCT7Jpj4_j3854i_Cp88Se1_6E,18791
+aiohttp/_websocket/reader_py.py,sha256=gSsE_iSBr7-ORvOmgkCT7Jpj4_j3854i_Cp88Se1_6E,18791
+aiohttp/_websocket/writer.py,sha256=2OvSktPmNh_g20h1cXJt2Xu8u6IvswnPjdur7OwBbJk,11261
+aiohttp/abc.py,sha256=M66F4S6m00bIEn7y4ha_XLTMDmVQ9dPihfOVB0pGfOo,7149
+aiohttp/base_protocol.py,sha256=Tp8cxUPQvv9kUPk3w6lAzk6d2MAzV3scwI_3Go3C47c,3025
+aiohttp/client.py,sha256=fOQfwcIUL1NGAVRV4DDj6-wipBzeD8KZpmzhO-LLKp4,58357
+aiohttp/client_exceptions.py,sha256=uyKbxI2peZhKl7lELBMx3UeusNkfpemPWpGFq0r6JeM,11367
+aiohttp/client_middleware_digest_auth.py,sha256=BIoQJ5eWL5NNkPOmezTGrceWIho8ETDvS8NKvX-3Xdw,17088
+aiohttp/client_middlewares.py,sha256=kP5N9CMzQPMGPIEydeVUiLUTLsw8Vl8Gr4qAWYdu3vM,1918
+aiohttp/client_proto.py,sha256=56_WtLStZGBFPYKzgEgY6v24JkhV1y6JEmmuxeJT2So,12110
+aiohttp/client_reqrep.py,sha256=eEREDrZ0M8ZFTt1wjHduR-P8_sm40K65gNz-iMGYask,53391
+aiohttp/client_ws.py,sha256=1CIjIXwyzOMIYw6AjUES4-qUwbyVHW1seJKQfg_Rta8,15109
+aiohttp/compression_utils.py,sha256=Cmn4bim6iDYUST1Fp66EBRDzIz_3gUQBLg4HkbEljrc,10408
+aiohttp/connector.py,sha256=WQetKoSW7XnHA9r4o9OWwO3-n7ymOwBd2Tg_xHNw0Bs,68456
+aiohttp/cookiejar.py,sha256=e28ZMQwJ5P0vbPX1OX4Se7-k3zeGvocFEqzGhwpG53k,18922
+aiohttp/formdata.py,sha256=xqYMbUo1qoLYPuzY92XeR4pyEe-w-DNcToARDF3GUhA,6384
+aiohttp/hdrs.py,sha256=2rj5MyA-6yRdYPhW5UKkW4iNWhEAlGIOSBH5D4FmKNE,5111
+aiohttp/helpers.py,sha256=Q1307PCEnWz4RP8crUw8dk58c0YF2Ei3JywkKfRxz5E,30629
+aiohttp/http.py,sha256=8o8j8xH70OWjnfTWA9V44NR785QPxEPrUtzMXiAVpwc,1842
+aiohttp/http_exceptions.py,sha256=AZafFHgtAkAgrKZf8zYPU8VX2dq32-VAoP-UZxBLU0c,2960
+aiohttp/http_parser.py,sha256=fACBNI47n9hnVPWfm5AJufuezsoYOF_VLp4bptYjvQI,37377
+aiohttp/http_websocket.py,sha256=8VXFKw6KQUEmPg48GtRMB37v0gTK7A0inoxXuDxMZEc,842
+aiohttp/http_writer.py,sha256=fbRtKPYSqRbtAdr_gqpjF2-4sI1ESL8dPDF-xY_mAMY,12446
+aiohttp/log.py,sha256=BbNKx9e3VMIm0xYjZI0IcBBoS7wjdeIeSaiJE7-qK2g,325
+aiohttp/multipart.py,sha256=6q6QRjKFVqaWzTbc7bkuBtXsTaQq5b2BhHxLBvAElac,40040
+aiohttp/payload.py,sha256=O6nsYNULL7AeM2cyJ6TYX73ncVnL5xJwt5AegxwMKqw,40874
+aiohttp/payload_streamer.py,sha256=ZzEYyfzcjGWkVkK3XR2pBthSCSIykYvY3Wr5cGQ2eTc,2211
+aiohttp/py.typed,sha256=sow9soTwP9T_gEAQSVh7Gb8855h04Nwmhs2We-JRgZM,7
+aiohttp/pytest_plugin.py,sha256=z4XwqmsKdyJCKxbGiA5kFf90zcedvomqk4RqjZbhKNk,12901
+aiohttp/resolver.py,sha256=gsrfUpFf8iHlcHfJvY-1fiBHW3PRvRVNb5lNZBg3zlY,10031
+aiohttp/streams.py,sha256=cQxo6Fyu_HDWDpbezGRVPIVYtVtTbSLRF7g511DNmSs,22601
+aiohttp/tcp_helpers.py,sha256=BSadqVWaBpMFDRWnhaaR941N9MiDZ7bdTrxgCb0CW-M,961
+aiohttp/test_utils.py,sha256=ZJSzZWjC76KSbtwddTKcP6vHpUl_ozfAf3F93ewmHRU,23016
+aiohttp/tracing.py,sha256=-6aaW6l0J9uJD45LzR4cijYH0j62pt0U_nn_aVzFku4,14558
+aiohttp/typedefs.py,sha256=wUlqwe9Mw9W8jT3HsYJcYk00qP3EMPz3nTkYXmeNN48,1657
+aiohttp/web.py,sha256=JzSNmejg5G6YeFAnkIgZfytqbU86sNu844yYKmoUpqs,17852
+aiohttp/web_app.py,sha256=lGU_aAMN-h3wy-LTTHi6SeKH8ydt1G51BXcCspgD5ZA,19452
+aiohttp/web_exceptions.py,sha256=7nIuiwhZ39vJJ9KrWqArA5QcWbUdqkz2CLwEpJapeN8,10360
+aiohttp/web_fileresponse.py,sha256=Xzau8EMrWNrFg3u46h4UEteg93G4zYq94CU6vy0HiqE,16362
+aiohttp/web_log.py,sha256=rX5D7xLOX2B6BMdiZ-chme_KfJfW5IXEoFwLfkfkajs,7865
+aiohttp/web_middlewares.py,sha256=sFI0AgeNjdyAjuz92QtMIpngmJSOxrqe2Jfbs4BNUu0,4165
+aiohttp/web_protocol.py,sha256=c8a0PKGqfhIAiq2RboMsy1NRza4dnj6gnXIWvJUeCF0,27015
+aiohttp/web_request.py,sha256=zN96OlMRlrCFOMRpdh7y9rvHP0Hm8zavC0OFCj0wlSg,29833
+aiohttp/web_response.py,sha256=PKcziNU4LmftXqKVvoRMrAbOeVClpSN-iznHsiWezmU,29341
+aiohttp/web_routedef.py,sha256=VT1GAx6BrawoDh5RwBwBu5wSABSqgWwAe74AUCyZAEo,6110
+aiohttp/web_runner.py,sha256=v1G1nKiOOQgFnTSR4IMc6I9ReEFDMaHtMLvO_roDM-A,11786
+aiohttp/web_server.py,sha256=-9WDKUAiR9ll-rSdwXSqG6YjaoW79d1R4y0BGSqgUMA,2888
+aiohttp/web_urldispatcher.py,sha256=3ryu1ZOpcq79IYNMd6EjYWmQ_i6JbsJzS_IaV0yoYBg,44203
+aiohttp/web_ws.py,sha256=lItgmyatkXh0M6EY7JoZnSZkUl6R0wv8B88X4ILqQbU,22739
+aiohttp/worker.py,sha256=zT0iWN5Xze194bO6_VjHou0x7lR_k0MviN6Kadnk22g,8152
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/REQUESTED b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/REQUESTED
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/WHEEL b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..f3e8a970f16adf4526f1722547053522d94bf860
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/WHEEL
@@ -0,0 +1,7 @@
+Wheel-Version: 1.0
+Generator: setuptools (80.9.0)
+Root-Is-Purelib: false
+Tag: cp312-cp312-manylinux_2_17_x86_64
+Tag: cp312-cp312-manylinux2014_x86_64
+Tag: cp312-cp312-manylinux_2_28_x86_64
+
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/licenses/LICENSE.txt b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/licenses/LICENSE.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e497a322f2091d022983b9c5c043082ab61d1a8c
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/licenses/LICENSE.txt
@@ -0,0 +1,13 @@
+ Copyright aio-libs contributors.
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/licenses/vendor/llhttp/LICENSE b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/licenses/vendor/llhttp/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..6c1512dd6bcd6de314673f8c2c7ff3fd6139f28e
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/licenses/vendor/llhttp/LICENSE
@@ -0,0 +1,22 @@
+This software is licensed under the MIT License.
+
+Copyright Fedor Indutny, 2018.
+
+Permission is hereby granted, free of charge, to any person obtaining a
+copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to permit
+persons to whom the Software is furnished to do so, subject to the
+following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
+NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
+USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/top_level.txt b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/top_level.txt
new file mode 100644
index 0000000000000000000000000000000000000000..ee4ba4f3d739e094878215c84eb41ba85c80e4a8
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiohttp-3.13.2.dist-info/top_level.txt
@@ -0,0 +1 @@
+aiohttp
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiosignal/__init__.py b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiosignal/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..5ede0090ad93cacd642b11645c1c71bcd3021e2b
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiosignal/__init__.py
@@ -0,0 +1,59 @@
+import sys
+from typing import Any, Awaitable, Callable, TypeVar
+
+from frozenlist import FrozenList
+
+if sys.version_info >= (3, 11):
+ from typing import Unpack
+else:
+ from typing_extensions import Unpack
+
+if sys.version_info >= (3, 13):
+ from typing import TypeVarTuple
+else:
+ from typing_extensions import TypeVarTuple
+
+_T = TypeVar("_T")
+_Ts = TypeVarTuple("_Ts", default=Unpack[tuple[()]])
+
+__version__ = "1.4.0"
+
+__all__ = ("Signal",)
+
+
+class Signal(FrozenList[Callable[[Unpack[_Ts]], Awaitable[object]]]):
+ """Coroutine-based signal implementation.
+
+ To connect a callback to a signal, use any list method.
+
+ Signals are fired using the send() coroutine, which takes named
+ arguments.
+ """
+
+ __slots__ = ("_owner",)
+
+ def __init__(self, owner: object):
+ super().__init__()
+ self._owner = owner
+
+ def __repr__(self) -> str:
+ return "".format(
+ self._owner, self.frozen, list(self)
+ )
+
+ async def send(self, *args: Unpack[_Ts], **kwargs: Any) -> None:
+ """
+ Sends data to all registered receivers.
+ """
+ if not self.frozen:
+ raise RuntimeError("Cannot send non-frozen signal.")
+
+ for receiver in self:
+ await receiver(*args, **kwargs)
+
+ def __call__(
+ self, func: Callable[[Unpack[_Ts]], Awaitable[_T]]
+ ) -> Callable[[Unpack[_Ts]], Awaitable[_T]]:
+ """Decorator to add a function to this Signal."""
+ self.append(func)
+ return func
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiosignal/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiosignal/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5a45ca2c26c7ddee1c45f1c2893cb8ae933eb5f1
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiosignal/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiosignal/py.typed b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/aiosignal/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/INSTALLER b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/INSTALLER
new file mode 100644
index 0000000000000000000000000000000000000000..a1b589e38a32041e49332e5e81c2d363dc418d68
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/INSTALLER
@@ -0,0 +1 @@
+pip
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/METADATA b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/METADATA
new file mode 100644
index 0000000000000000000000000000000000000000..7a4a4b7a7e9adbe4542d98f6b2f40e65ee64b182
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/METADATA
@@ -0,0 +1,209 @@
+Metadata-Version: 2.4
+Name: idna
+Version: 3.11
+Summary: Internationalized Domain Names in Applications (IDNA)
+Author-email: Kim Davies
+Requires-Python: >=3.8
+Description-Content-Type: text/x-rst
+License-Expression: BSD-3-Clause
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: System Administrators
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3 :: Only
+Classifier: Programming Language :: Python :: 3.8
+Classifier: Programming Language :: Python :: 3.9
+Classifier: Programming Language :: Python :: 3.10
+Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
+Classifier: Programming Language :: Python :: Implementation :: CPython
+Classifier: Programming Language :: Python :: Implementation :: PyPy
+Classifier: Topic :: Internet :: Name Service (DNS)
+Classifier: Topic :: Software Development :: Libraries :: Python Modules
+Classifier: Topic :: Utilities
+License-File: LICENSE.md
+Requires-Dist: ruff >= 0.6.2 ; extra == "all"
+Requires-Dist: mypy >= 1.11.2 ; extra == "all"
+Requires-Dist: pytest >= 8.3.2 ; extra == "all"
+Requires-Dist: flake8 >= 7.1.1 ; extra == "all"
+Project-URL: Changelog, https://github.com/kjd/idna/blob/master/HISTORY.rst
+Project-URL: Issue tracker, https://github.com/kjd/idna/issues
+Project-URL: Source, https://github.com/kjd/idna
+Provides-Extra: all
+
+Internationalized Domain Names in Applications (IDNA)
+=====================================================
+
+Support for `Internationalized Domain Names in
+Applications (IDNA) `_
+and `Unicode IDNA Compatibility Processing
+`_.
+
+The latest versions of these standards supplied here provide
+more comprehensive language coverage and reduce the potential of
+allowing domains with known security vulnerabilities. This library
+is a suitable replacement for the “encodings.idna”
+module that comes with the Python standard library, but which
+only supports an older superseded IDNA specification from 2003.
+
+Basic functions are simply executed:
+
+.. code-block:: pycon
+
+ >>> import idna
+ >>> idna.encode('ドメイン.テスト')
+ b'xn--eckwd4c7c.xn--zckzah'
+ >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah'))
+ ドメイン.テスト
+
+
+Installation
+------------
+
+This package is available for installation from PyPI via the
+typical mechanisms, such as:
+
+.. code-block:: bash
+
+ $ python3 -m pip install idna
+
+
+Usage
+-----
+
+For typical usage, the ``encode`` and ``decode`` functions will take a
+domain name argument and perform a conversion to ASCII compatible encoding
+(known as A-labels), or to Unicode strings (known as U-labels)
+respectively.
+
+.. code-block:: pycon
+
+ >>> import idna
+ >>> idna.encode('ドメイン.テスト')
+ b'xn--eckwd4c7c.xn--zckzah'
+ >>> print(idna.decode('xn--eckwd4c7c.xn--zckzah'))
+ ドメイン.テスト
+
+Conversions can be applied at a per-label basis using the ``ulabel`` or
+``alabel`` functions if necessary:
+
+.. code-block:: pycon
+
+ >>> idna.alabel('测试')
+ b'xn--0zwm56d'
+
+
+Compatibility Mapping (UTS #46)
++++++++++++++++++++++++++++++++
+
+This library provides support for `Unicode IDNA Compatibility
+Processing `_ which normalizes input from
+different potential ways a user may input a domain prior to performing the IDNA
+conversion operations. This functionality, known as a
+`mapping `_, is considered by the
+specification to be a local user-interface issue distinct from IDNA
+conversion functionality.
+
+For example, “Königsgäßchen” is not a permissible label as *LATIN
+CAPITAL LETTER K* is not allowed (nor are capital letters in general).
+UTS 46 will convert this into lower case prior to applying the IDNA
+conversion.
+
+.. code-block:: pycon
+
+ >>> import idna
+ >>> idna.encode('Königsgäßchen')
+ ...
+ idna.core.InvalidCodepoint: Codepoint U+004B at position 1 of 'Königsgäßchen' not allowed
+ >>> idna.encode('Königsgäßchen', uts46=True)
+ b'xn--knigsgchen-b4a3dun'
+ >>> print(idna.decode('xn--knigsgchen-b4a3dun'))
+ königsgäßchen
+
+
+Exceptions
+----------
+
+All errors raised during the conversion following the specification
+should raise an exception derived from the ``idna.IDNAError`` base
+class.
+
+More specific exceptions that may be generated as ``idna.IDNABidiError``
+when the error reflects an illegal combination of left-to-right and
+right-to-left characters in a label; ``idna.InvalidCodepoint`` when
+a specific codepoint is an illegal character in an IDN label (i.e.
+INVALID); and ``idna.InvalidCodepointContext`` when the codepoint is
+illegal based on its position in the string (i.e. it is CONTEXTO or CONTEXTJ
+but the contextual requirements are not satisfied.)
+
+Building and Diagnostics
+------------------------
+
+The IDNA and UTS 46 functionality relies upon pre-calculated lookup
+tables for performance. These tables are derived from computing against
+eligibility criteria in the respective standards using the command-line
+script ``tools/idna-data``.
+
+This tool will fetch relevant codepoint data from the Unicode repository
+and perform the required calculations to identify eligibility. There are
+three main modes:
+
+* ``idna-data make-libdata``. Generates ``idnadata.py`` and
+ ``uts46data.py``, the pre-calculated lookup tables used for IDNA and
+ UTS 46 conversions. Implementers who wish to track this library against
+ a different Unicode version may use this tool to manually generate a
+ different version of the ``idnadata.py`` and ``uts46data.py`` files.
+
+* ``idna-data make-table``. Generate a table of the IDNA disposition
+ (e.g. PVALID, CONTEXTJ, CONTEXTO) in the format found in Appendix
+ B.1 of RFC 5892 and the pre-computed tables published by `IANA
+ `_.
+
+* ``idna-data U+0061``. Prints debugging output on the various
+ properties associated with an individual Unicode codepoint (in this
+ case, U+0061), that are used to assess the IDNA and UTS 46 status of a
+ codepoint. This is helpful in debugging or analysis.
+
+The tool accepts a number of arguments, described using ``idna-data
+-h``. Most notably, the ``--version`` argument allows the specification
+of the version of Unicode to be used in computing the table data. For
+example, ``idna-data --version 9.0.0 make-libdata`` will generate
+library data against Unicode 9.0.0.
+
+
+Additional Notes
+----------------
+
+* **Packages**. The latest tagged release version is published in the
+ `Python Package Index `_.
+
+* **Version support**. This library supports Python 3.8 and higher.
+ As this library serves as a low-level toolkit for a variety of
+ applications, many of which strive for broad compatibility with older
+ Python versions, there is no rush to remove older interpreter support.
+ Support for older versions are likely to be removed from new releases
+ as automated tests can no longer easily be run, i.e. once the Python
+ version is officially end-of-life.
+
+* **Testing**. The library has a test suite based on each rule of the
+ IDNA specification, as well as tests that are provided as part of the
+ Unicode Technical Standard 46, `Unicode IDNA Compatibility Processing
+ `_.
+
+* **Emoji**. It is an occasional request to support emoji domains in
+ this library. Encoding of symbols like emoji is expressly prohibited by
+ the technical standard IDNA 2008 and emoji domains are broadly phased
+ out across the domain industry due to associated security risks. For
+ now, applications that need to support these non-compliant labels
+ may wish to consider trying the encode/decode operation in this library
+ first, and then falling back to using `encodings.idna`. See `the Github
+ project `_ for more discussion.
+
+* **Transitional processing**. Unicode 16.0.0 removed transitional
+ processing so the `transitional` argument for the encode() method
+ no longer has any effect and will be removed at a later date.
+
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/RECORD b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/RECORD
new file mode 100644
index 0000000000000000000000000000000000000000..8525b6daf9b59dd5e02fdf5797c63da32f780ee8
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/RECORD
@@ -0,0 +1,22 @@
+idna-3.11.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
+idna-3.11.dist-info/METADATA,sha256=fCwSww9SuiN8TIHllFSASUQCW55hAs8dzKnr9RaEEbA,8378
+idna-3.11.dist-info/RECORD,,
+idna-3.11.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
+idna-3.11.dist-info/licenses/LICENSE.md,sha256=t6M2q_OwThgOwGXN0W5wXQeeHMehT5EKpukYfza5zYc,1541
+idna/__init__.py,sha256=MPqNDLZbXqGaNdXxAFhiqFPKEQXju2jNQhCey6-5eJM,868
+idna/__pycache__/__init__.cpython-312.pyc,,
+idna/__pycache__/codec.cpython-312.pyc,,
+idna/__pycache__/compat.cpython-312.pyc,,
+idna/__pycache__/core.cpython-312.pyc,,
+idna/__pycache__/idnadata.cpython-312.pyc,,
+idna/__pycache__/intranges.cpython-312.pyc,,
+idna/__pycache__/package_data.cpython-312.pyc,,
+idna/__pycache__/uts46data.cpython-312.pyc,,
+idna/codec.py,sha256=M2SGWN7cs_6B32QmKTyTN6xQGZeYQgQ2wiX3_DR6loE,3438
+idna/compat.py,sha256=RzLy6QQCdl9784aFhb2EX9EKGCJjg0P3PilGdeXXcx8,316
+idna/core.py,sha256=P26_XVycuMTZ1R2mNK1ZREVzM5mvTzdabBXfyZVU1Lc,13246
+idna/idnadata.py,sha256=SG8jhaGE53iiD6B49pt2pwTv_UvClciWE-N54oR2p4U,79623
+idna/intranges.py,sha256=amUtkdhYcQG8Zr-CoMM_kVRacxkivC1WgxN1b63KKdU,1898
+idna/package_data.py,sha256=_CUavOxobnbyNG2FLyHoN8QHP3QM9W1tKuw7eq9QwBk,21
+idna/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
+idna/uts46data.py,sha256=H9J35VkD0F9L9mKOqjeNGd2A-Va6FlPoz6Jz4K7h-ps,243725
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/WHEEL b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/WHEEL
new file mode 100644
index 0000000000000000000000000000000000000000..d8b9936dad9ab2513fa6979f411560d3b6b57e37
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/WHEEL
@@ -0,0 +1,4 @@
+Wheel-Version: 1.0
+Generator: flit 3.12.0
+Root-Is-Purelib: true
+Tag: py3-none-any
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/licenses/LICENSE.md b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/licenses/LICENSE.md
new file mode 100644
index 0000000000000000000000000000000000000000..256ba90cd91190a6c980bd44663dc51c201c14d3
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna-3.11.dist-info/licenses/LICENSE.md
@@ -0,0 +1,31 @@
+BSD 3-Clause License
+
+Copyright (c) 2013-2025, Kim Davies and contributors.
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__init__.py b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..cfdc030a751b089fc7e38fc88093b791605d501d
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__init__.py
@@ -0,0 +1,45 @@
+from .core import (
+ IDNABidiError,
+ IDNAError,
+ InvalidCodepoint,
+ InvalidCodepointContext,
+ alabel,
+ check_bidi,
+ check_hyphen_ok,
+ check_initial_combiner,
+ check_label,
+ check_nfc,
+ decode,
+ encode,
+ ulabel,
+ uts46_remap,
+ valid_contextj,
+ valid_contexto,
+ valid_label_length,
+ valid_string_length,
+)
+from .intranges import intranges_contain
+from .package_data import __version__
+
+__all__ = [
+ "__version__",
+ "IDNABidiError",
+ "IDNAError",
+ "InvalidCodepoint",
+ "InvalidCodepointContext",
+ "alabel",
+ "check_bidi",
+ "check_hyphen_ok",
+ "check_initial_combiner",
+ "check_label",
+ "check_nfc",
+ "decode",
+ "encode",
+ "intranges_contain",
+ "ulabel",
+ "uts46_remap",
+ "valid_contextj",
+ "valid_contexto",
+ "valid_label_length",
+ "valid_string_length",
+]
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4133a9d2a342c9ed6dea71ae6b57e7123992ac30
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/codec.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/codec.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..e2fb64f9775441d7dc7cbae563418d325c9cb5cd
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/codec.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/compat.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/compat.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..98bb1b1d100deddecf4c2d1f9b8be283aad78401
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/compat.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/core.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/core.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6f63750b57f1c49dc22a069efd2bbaa6b6d1dacc
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/core.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/intranges.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/intranges.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..490bb32474c13c0ebe6a95a8748310e2d083286b
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/intranges.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/package_data.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/package_data.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..23e18862541c7334b11accb06c149bb8f13264f0
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/__pycache__/package_data.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/codec.py b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/codec.py
new file mode 100644
index 0000000000000000000000000000000000000000..cbc2e4ff4ec3e2318d47615bab44ea0ca3dba978
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/codec.py
@@ -0,0 +1,122 @@
+import codecs
+import re
+from typing import Any, Optional, Tuple
+
+from .core import IDNAError, alabel, decode, encode, ulabel
+
+_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]")
+
+
+class Codec(codecs.Codec):
+ def encode(self, data: str, errors: str = "strict") -> Tuple[bytes, int]:
+ if errors != "strict":
+ raise IDNAError('Unsupported error handling "{}"'.format(errors))
+
+ if not data:
+ return b"", 0
+
+ return encode(data), len(data)
+
+ def decode(self, data: bytes, errors: str = "strict") -> Tuple[str, int]:
+ if errors != "strict":
+ raise IDNAError('Unsupported error handling "{}"'.format(errors))
+
+ if not data:
+ return "", 0
+
+ return decode(data), len(data)
+
+
+class IncrementalEncoder(codecs.BufferedIncrementalEncoder):
+ def _buffer_encode(self, data: str, errors: str, final: bool) -> Tuple[bytes, int]:
+ if errors != "strict":
+ raise IDNAError('Unsupported error handling "{}"'.format(errors))
+
+ if not data:
+ return b"", 0
+
+ labels = _unicode_dots_re.split(data)
+ trailing_dot = b""
+ if labels:
+ if not labels[-1]:
+ trailing_dot = b"."
+ del labels[-1]
+ elif not final:
+ # Keep potentially unfinished label until the next call
+ del labels[-1]
+ if labels:
+ trailing_dot = b"."
+
+ result = []
+ size = 0
+ for label in labels:
+ result.append(alabel(label))
+ if size:
+ size += 1
+ size += len(label)
+
+ # Join with U+002E
+ result_bytes = b".".join(result) + trailing_dot
+ size += len(trailing_dot)
+ return result_bytes, size
+
+
+class IncrementalDecoder(codecs.BufferedIncrementalDecoder):
+ def _buffer_decode(self, data: Any, errors: str, final: bool) -> Tuple[str, int]:
+ if errors != "strict":
+ raise IDNAError('Unsupported error handling "{}"'.format(errors))
+
+ if not data:
+ return ("", 0)
+
+ if not isinstance(data, str):
+ data = str(data, "ascii")
+
+ labels = _unicode_dots_re.split(data)
+ trailing_dot = ""
+ if labels:
+ if not labels[-1]:
+ trailing_dot = "."
+ del labels[-1]
+ elif not final:
+ # Keep potentially unfinished label until the next call
+ del labels[-1]
+ if labels:
+ trailing_dot = "."
+
+ result = []
+ size = 0
+ for label in labels:
+ result.append(ulabel(label))
+ if size:
+ size += 1
+ size += len(label)
+
+ result_str = ".".join(result) + trailing_dot
+ size += len(trailing_dot)
+ return (result_str, size)
+
+
+class StreamWriter(Codec, codecs.StreamWriter):
+ pass
+
+
+class StreamReader(Codec, codecs.StreamReader):
+ pass
+
+
+def search_function(name: str) -> Optional[codecs.CodecInfo]:
+ if name != "idna2008":
+ return None
+ return codecs.CodecInfo(
+ name=name,
+ encode=Codec().encode,
+ decode=Codec().decode, # type: ignore
+ incrementalencoder=IncrementalEncoder,
+ incrementaldecoder=IncrementalDecoder,
+ streamwriter=StreamWriter,
+ streamreader=StreamReader,
+ )
+
+
+codecs.register(search_function)
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/compat.py b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/compat.py
new file mode 100644
index 0000000000000000000000000000000000000000..1df9f2a70e6815908f2784e88897a9a359eef84c
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/compat.py
@@ -0,0 +1,15 @@
+from typing import Any, Union
+
+from .core import decode, encode
+
+
+def ToASCII(label: str) -> bytes:
+ return encode(label)
+
+
+def ToUnicode(label: Union[bytes, bytearray]) -> str:
+ return decode(label)
+
+
+def nameprep(s: Any) -> None:
+ raise NotImplementedError("IDNA 2008 does not utilise nameprep protocol")
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/core.py b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/core.py
new file mode 100644
index 0000000000000000000000000000000000000000..8177bf7a324f9f54a29e41e867f5d56f2dd0a924
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/core.py
@@ -0,0 +1,437 @@
+import bisect
+import re
+import unicodedata
+from typing import Optional, Union
+
+from . import idnadata
+from .intranges import intranges_contain
+
+_virama_combining_class = 9
+_alabel_prefix = b"xn--"
+_unicode_dots_re = re.compile("[\u002e\u3002\uff0e\uff61]")
+
+
+class IDNAError(UnicodeError):
+ """Base exception for all IDNA-encoding related problems"""
+
+ pass
+
+
+class IDNABidiError(IDNAError):
+ """Exception when bidirectional requirements are not satisfied"""
+
+ pass
+
+
+class InvalidCodepoint(IDNAError):
+ """Exception when a disallowed or unallocated codepoint is used"""
+
+ pass
+
+
+class InvalidCodepointContext(IDNAError):
+ """Exception when the codepoint is not valid in the context it is used"""
+
+ pass
+
+
+def _combining_class(cp: int) -> int:
+ v = unicodedata.combining(chr(cp))
+ if v == 0:
+ if not unicodedata.name(chr(cp)):
+ raise ValueError("Unknown character in unicodedata")
+ return v
+
+
+def _is_script(cp: str, script: str) -> bool:
+ return intranges_contain(ord(cp), idnadata.scripts[script])
+
+
+def _punycode(s: str) -> bytes:
+ return s.encode("punycode")
+
+
+def _unot(s: int) -> str:
+ return "U+{:04X}".format(s)
+
+
+def valid_label_length(label: Union[bytes, str]) -> bool:
+ if len(label) > 63:
+ return False
+ return True
+
+
+def valid_string_length(label: Union[bytes, str], trailing_dot: bool) -> bool:
+ if len(label) > (254 if trailing_dot else 253):
+ return False
+ return True
+
+
+def check_bidi(label: str, check_ltr: bool = False) -> bool:
+ # Bidi rules should only be applied if string contains RTL characters
+ bidi_label = False
+ for idx, cp in enumerate(label, 1):
+ direction = unicodedata.bidirectional(cp)
+ if direction == "":
+ # String likely comes from a newer version of Unicode
+ raise IDNABidiError("Unknown directionality in label {} at position {}".format(repr(label), idx))
+ if direction in ["R", "AL", "AN"]:
+ bidi_label = True
+ if not bidi_label and not check_ltr:
+ return True
+
+ # Bidi rule 1
+ direction = unicodedata.bidirectional(label[0])
+ if direction in ["R", "AL"]:
+ rtl = True
+ elif direction == "L":
+ rtl = False
+ else:
+ raise IDNABidiError("First codepoint in label {} must be directionality L, R or AL".format(repr(label)))
+
+ valid_ending = False
+ number_type: Optional[str] = None
+ for idx, cp in enumerate(label, 1):
+ direction = unicodedata.bidirectional(cp)
+
+ if rtl:
+ # Bidi rule 2
+ if direction not in [
+ "R",
+ "AL",
+ "AN",
+ "EN",
+ "ES",
+ "CS",
+ "ET",
+ "ON",
+ "BN",
+ "NSM",
+ ]:
+ raise IDNABidiError("Invalid direction for codepoint at position {} in a right-to-left label".format(idx))
+ # Bidi rule 3
+ if direction in ["R", "AL", "EN", "AN"]:
+ valid_ending = True
+ elif direction != "NSM":
+ valid_ending = False
+ # Bidi rule 4
+ if direction in ["AN", "EN"]:
+ if not number_type:
+ number_type = direction
+ else:
+ if number_type != direction:
+ raise IDNABidiError("Can not mix numeral types in a right-to-left label")
+ else:
+ # Bidi rule 5
+ if direction not in ["L", "EN", "ES", "CS", "ET", "ON", "BN", "NSM"]:
+ raise IDNABidiError("Invalid direction for codepoint at position {} in a left-to-right label".format(idx))
+ # Bidi rule 6
+ if direction in ["L", "EN"]:
+ valid_ending = True
+ elif direction != "NSM":
+ valid_ending = False
+
+ if not valid_ending:
+ raise IDNABidiError("Label ends with illegal codepoint directionality")
+
+ return True
+
+
+def check_initial_combiner(label: str) -> bool:
+ if unicodedata.category(label[0])[0] == "M":
+ raise IDNAError("Label begins with an illegal combining character")
+ return True
+
+
+def check_hyphen_ok(label: str) -> bool:
+ if label[2:4] == "--":
+ raise IDNAError("Label has disallowed hyphens in 3rd and 4th position")
+ if label[0] == "-" or label[-1] == "-":
+ raise IDNAError("Label must not start or end with a hyphen")
+ return True
+
+
+def check_nfc(label: str) -> None:
+ if unicodedata.normalize("NFC", label) != label:
+ raise IDNAError("Label must be in Normalization Form C")
+
+
+def valid_contextj(label: str, pos: int) -> bool:
+ cp_value = ord(label[pos])
+
+ if cp_value == 0x200C:
+ if pos > 0:
+ if _combining_class(ord(label[pos - 1])) == _virama_combining_class:
+ return True
+
+ ok = False
+ for i in range(pos - 1, -1, -1):
+ joining_type = idnadata.joining_types.get(ord(label[i]))
+ if joining_type == ord("T"):
+ continue
+ elif joining_type in [ord("L"), ord("D")]:
+ ok = True
+ break
+ else:
+ break
+
+ if not ok:
+ return False
+
+ ok = False
+ for i in range(pos + 1, len(label)):
+ joining_type = idnadata.joining_types.get(ord(label[i]))
+ if joining_type == ord("T"):
+ continue
+ elif joining_type in [ord("R"), ord("D")]:
+ ok = True
+ break
+ else:
+ break
+ return ok
+
+ if cp_value == 0x200D:
+ if pos > 0:
+ if _combining_class(ord(label[pos - 1])) == _virama_combining_class:
+ return True
+ return False
+
+ else:
+ return False
+
+
+def valid_contexto(label: str, pos: int, exception: bool = False) -> bool:
+ cp_value = ord(label[pos])
+
+ if cp_value == 0x00B7:
+ if 0 < pos < len(label) - 1:
+ if ord(label[pos - 1]) == 0x006C and ord(label[pos + 1]) == 0x006C:
+ return True
+ return False
+
+ elif cp_value == 0x0375:
+ if pos < len(label) - 1 and len(label) > 1:
+ return _is_script(label[pos + 1], "Greek")
+ return False
+
+ elif cp_value == 0x05F3 or cp_value == 0x05F4:
+ if pos > 0:
+ return _is_script(label[pos - 1], "Hebrew")
+ return False
+
+ elif cp_value == 0x30FB:
+ for cp in label:
+ if cp == "\u30fb":
+ continue
+ if _is_script(cp, "Hiragana") or _is_script(cp, "Katakana") or _is_script(cp, "Han"):
+ return True
+ return False
+
+ elif 0x660 <= cp_value <= 0x669:
+ for cp in label:
+ if 0x6F0 <= ord(cp) <= 0x06F9:
+ return False
+ return True
+
+ elif 0x6F0 <= cp_value <= 0x6F9:
+ for cp in label:
+ if 0x660 <= ord(cp) <= 0x0669:
+ return False
+ return True
+
+ return False
+
+
+def check_label(label: Union[str, bytes, bytearray]) -> None:
+ if isinstance(label, (bytes, bytearray)):
+ label = label.decode("utf-8")
+ if len(label) == 0:
+ raise IDNAError("Empty Label")
+
+ check_nfc(label)
+ check_hyphen_ok(label)
+ check_initial_combiner(label)
+
+ for pos, cp in enumerate(label):
+ cp_value = ord(cp)
+ if intranges_contain(cp_value, idnadata.codepoint_classes["PVALID"]):
+ continue
+ elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTJ"]):
+ try:
+ if not valid_contextj(label, pos):
+ raise InvalidCodepointContext(
+ "Joiner {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label))
+ )
+ except ValueError:
+ raise IDNAError(
+ "Unknown codepoint adjacent to joiner {} at position {} in {}".format(
+ _unot(cp_value), pos + 1, repr(label)
+ )
+ )
+ elif intranges_contain(cp_value, idnadata.codepoint_classes["CONTEXTO"]):
+ if not valid_contexto(label, pos):
+ raise InvalidCodepointContext(
+ "Codepoint {} not allowed at position {} in {}".format(_unot(cp_value), pos + 1, repr(label))
+ )
+ else:
+ raise InvalidCodepoint(
+ "Codepoint {} at position {} of {} not allowed".format(_unot(cp_value), pos + 1, repr(label))
+ )
+
+ check_bidi(label)
+
+
+def alabel(label: str) -> bytes:
+ try:
+ label_bytes = label.encode("ascii")
+ ulabel(label_bytes)
+ if not valid_label_length(label_bytes):
+ raise IDNAError("Label too long")
+ return label_bytes
+ except UnicodeEncodeError:
+ pass
+
+ check_label(label)
+ label_bytes = _alabel_prefix + _punycode(label)
+
+ if not valid_label_length(label_bytes):
+ raise IDNAError("Label too long")
+
+ return label_bytes
+
+
+def ulabel(label: Union[str, bytes, bytearray]) -> str:
+ if not isinstance(label, (bytes, bytearray)):
+ try:
+ label_bytes = label.encode("ascii")
+ except UnicodeEncodeError:
+ check_label(label)
+ return label
+ else:
+ label_bytes = bytes(label)
+
+ label_bytes = label_bytes.lower()
+ if label_bytes.startswith(_alabel_prefix):
+ label_bytes = label_bytes[len(_alabel_prefix) :]
+ if not label_bytes:
+ raise IDNAError("Malformed A-label, no Punycode eligible content found")
+ if label_bytes.decode("ascii")[-1] == "-":
+ raise IDNAError("A-label must not end with a hyphen")
+ else:
+ check_label(label_bytes)
+ return label_bytes.decode("ascii")
+
+ try:
+ label = label_bytes.decode("punycode")
+ except UnicodeError:
+ raise IDNAError("Invalid A-label")
+ check_label(label)
+ return label
+
+
+def uts46_remap(domain: str, std3_rules: bool = True, transitional: bool = False) -> str:
+ """Re-map the characters in the string according to UTS46 processing."""
+ from .uts46data import uts46data
+
+ output = ""
+
+ for pos, char in enumerate(domain):
+ code_point = ord(char)
+ try:
+ uts46row = uts46data[code_point if code_point < 256 else bisect.bisect_left(uts46data, (code_point, "Z")) - 1]
+ status = uts46row[1]
+ replacement: Optional[str] = None
+ if len(uts46row) == 3:
+ replacement = uts46row[2]
+ if (
+ status == "V"
+ or (status == "D" and not transitional)
+ or (status == "3" and not std3_rules and replacement is None)
+ ):
+ output += char
+ elif replacement is not None and (
+ status == "M" or (status == "3" and not std3_rules) or (status == "D" and transitional)
+ ):
+ output += replacement
+ elif status != "I":
+ raise IndexError()
+ except IndexError:
+ raise InvalidCodepoint(
+ "Codepoint {} not allowed at position {} in {}".format(_unot(code_point), pos + 1, repr(domain))
+ )
+
+ return unicodedata.normalize("NFC", output)
+
+
+def encode(
+ s: Union[str, bytes, bytearray],
+ strict: bool = False,
+ uts46: bool = False,
+ std3_rules: bool = False,
+ transitional: bool = False,
+) -> bytes:
+ if not isinstance(s, str):
+ try:
+ s = str(s, "ascii")
+ except UnicodeDecodeError:
+ raise IDNAError("should pass a unicode string to the function rather than a byte string.")
+ if uts46:
+ s = uts46_remap(s, std3_rules, transitional)
+ trailing_dot = False
+ result = []
+ if strict:
+ labels = s.split(".")
+ else:
+ labels = _unicode_dots_re.split(s)
+ if not labels or labels == [""]:
+ raise IDNAError("Empty domain")
+ if labels[-1] == "":
+ del labels[-1]
+ trailing_dot = True
+ for label in labels:
+ s = alabel(label)
+ if s:
+ result.append(s)
+ else:
+ raise IDNAError("Empty label")
+ if trailing_dot:
+ result.append(b"")
+ s = b".".join(result)
+ if not valid_string_length(s, trailing_dot):
+ raise IDNAError("Domain too long")
+ return s
+
+
+def decode(
+ s: Union[str, bytes, bytearray],
+ strict: bool = False,
+ uts46: bool = False,
+ std3_rules: bool = False,
+) -> str:
+ try:
+ if not isinstance(s, str):
+ s = str(s, "ascii")
+ except UnicodeDecodeError:
+ raise IDNAError("Invalid ASCII in A-label")
+ if uts46:
+ s = uts46_remap(s, std3_rules, False)
+ trailing_dot = False
+ result = []
+ if not strict:
+ labels = _unicode_dots_re.split(s)
+ else:
+ labels = s.split(".")
+ if not labels or labels == [""]:
+ raise IDNAError("Empty domain")
+ if not labels[-1]:
+ del labels[-1]
+ trailing_dot = True
+ for label in labels:
+ s = ulabel(label)
+ if s:
+ result.append(s)
+ else:
+ raise IDNAError("Empty label")
+ if trailing_dot:
+ result.append("")
+ return ".".join(result)
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/idnadata.py b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/idnadata.py
new file mode 100644
index 0000000000000000000000000000000000000000..ded47cae0b16977aae69f3895ecfe8b8980f58d0
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/idnadata.py
@@ -0,0 +1,4309 @@
+# This file is automatically generated by tools/idna-data
+
+__version__ = "16.0.0"
+
+scripts = {
+ "Greek": (
+ 0x37000000374,
+ 0x37500000378,
+ 0x37A0000037E,
+ 0x37F00000380,
+ 0x38400000385,
+ 0x38600000387,
+ 0x3880000038B,
+ 0x38C0000038D,
+ 0x38E000003A2,
+ 0x3A3000003E2,
+ 0x3F000000400,
+ 0x1D2600001D2B,
+ 0x1D5D00001D62,
+ 0x1D6600001D6B,
+ 0x1DBF00001DC0,
+ 0x1F0000001F16,
+ 0x1F1800001F1E,
+ 0x1F2000001F46,
+ 0x1F4800001F4E,
+ 0x1F5000001F58,
+ 0x1F5900001F5A,
+ 0x1F5B00001F5C,
+ 0x1F5D00001F5E,
+ 0x1F5F00001F7E,
+ 0x1F8000001FB5,
+ 0x1FB600001FC5,
+ 0x1FC600001FD4,
+ 0x1FD600001FDC,
+ 0x1FDD00001FF0,
+ 0x1FF200001FF5,
+ 0x1FF600001FFF,
+ 0x212600002127,
+ 0xAB650000AB66,
+ 0x101400001018F,
+ 0x101A0000101A1,
+ 0x1D2000001D246,
+ ),
+ "Han": (
+ 0x2E8000002E9A,
+ 0x2E9B00002EF4,
+ 0x2F0000002FD6,
+ 0x300500003006,
+ 0x300700003008,
+ 0x30210000302A,
+ 0x30380000303C,
+ 0x340000004DC0,
+ 0x4E000000A000,
+ 0xF9000000FA6E,
+ 0xFA700000FADA,
+ 0x16FE200016FE4,
+ 0x16FF000016FF2,
+ 0x200000002A6E0,
+ 0x2A7000002B73A,
+ 0x2B7400002B81E,
+ 0x2B8200002CEA2,
+ 0x2CEB00002EBE1,
+ 0x2EBF00002EE5E,
+ 0x2F8000002FA1E,
+ 0x300000003134B,
+ 0x31350000323B0,
+ ),
+ "Hebrew": (
+ 0x591000005C8,
+ 0x5D0000005EB,
+ 0x5EF000005F5,
+ 0xFB1D0000FB37,
+ 0xFB380000FB3D,
+ 0xFB3E0000FB3F,
+ 0xFB400000FB42,
+ 0xFB430000FB45,
+ 0xFB460000FB50,
+ ),
+ "Hiragana": (
+ 0x304100003097,
+ 0x309D000030A0,
+ 0x1B0010001B120,
+ 0x1B1320001B133,
+ 0x1B1500001B153,
+ 0x1F2000001F201,
+ ),
+ "Katakana": (
+ 0x30A1000030FB,
+ 0x30FD00003100,
+ 0x31F000003200,
+ 0x32D0000032FF,
+ 0x330000003358,
+ 0xFF660000FF70,
+ 0xFF710000FF9E,
+ 0x1AFF00001AFF4,
+ 0x1AFF50001AFFC,
+ 0x1AFFD0001AFFF,
+ 0x1B0000001B001,
+ 0x1B1200001B123,
+ 0x1B1550001B156,
+ 0x1B1640001B168,
+ ),
+}
+joining_types = {
+ 0xAD: 84,
+ 0x300: 84,
+ 0x301: 84,
+ 0x302: 84,
+ 0x303: 84,
+ 0x304: 84,
+ 0x305: 84,
+ 0x306: 84,
+ 0x307: 84,
+ 0x308: 84,
+ 0x309: 84,
+ 0x30A: 84,
+ 0x30B: 84,
+ 0x30C: 84,
+ 0x30D: 84,
+ 0x30E: 84,
+ 0x30F: 84,
+ 0x310: 84,
+ 0x311: 84,
+ 0x312: 84,
+ 0x313: 84,
+ 0x314: 84,
+ 0x315: 84,
+ 0x316: 84,
+ 0x317: 84,
+ 0x318: 84,
+ 0x319: 84,
+ 0x31A: 84,
+ 0x31B: 84,
+ 0x31C: 84,
+ 0x31D: 84,
+ 0x31E: 84,
+ 0x31F: 84,
+ 0x320: 84,
+ 0x321: 84,
+ 0x322: 84,
+ 0x323: 84,
+ 0x324: 84,
+ 0x325: 84,
+ 0x326: 84,
+ 0x327: 84,
+ 0x328: 84,
+ 0x329: 84,
+ 0x32A: 84,
+ 0x32B: 84,
+ 0x32C: 84,
+ 0x32D: 84,
+ 0x32E: 84,
+ 0x32F: 84,
+ 0x330: 84,
+ 0x331: 84,
+ 0x332: 84,
+ 0x333: 84,
+ 0x334: 84,
+ 0x335: 84,
+ 0x336: 84,
+ 0x337: 84,
+ 0x338: 84,
+ 0x339: 84,
+ 0x33A: 84,
+ 0x33B: 84,
+ 0x33C: 84,
+ 0x33D: 84,
+ 0x33E: 84,
+ 0x33F: 84,
+ 0x340: 84,
+ 0x341: 84,
+ 0x342: 84,
+ 0x343: 84,
+ 0x344: 84,
+ 0x345: 84,
+ 0x346: 84,
+ 0x347: 84,
+ 0x348: 84,
+ 0x349: 84,
+ 0x34A: 84,
+ 0x34B: 84,
+ 0x34C: 84,
+ 0x34D: 84,
+ 0x34E: 84,
+ 0x34F: 84,
+ 0x350: 84,
+ 0x351: 84,
+ 0x352: 84,
+ 0x353: 84,
+ 0x354: 84,
+ 0x355: 84,
+ 0x356: 84,
+ 0x357: 84,
+ 0x358: 84,
+ 0x359: 84,
+ 0x35A: 84,
+ 0x35B: 84,
+ 0x35C: 84,
+ 0x35D: 84,
+ 0x35E: 84,
+ 0x35F: 84,
+ 0x360: 84,
+ 0x361: 84,
+ 0x362: 84,
+ 0x363: 84,
+ 0x364: 84,
+ 0x365: 84,
+ 0x366: 84,
+ 0x367: 84,
+ 0x368: 84,
+ 0x369: 84,
+ 0x36A: 84,
+ 0x36B: 84,
+ 0x36C: 84,
+ 0x36D: 84,
+ 0x36E: 84,
+ 0x36F: 84,
+ 0x483: 84,
+ 0x484: 84,
+ 0x485: 84,
+ 0x486: 84,
+ 0x487: 84,
+ 0x488: 84,
+ 0x489: 84,
+ 0x591: 84,
+ 0x592: 84,
+ 0x593: 84,
+ 0x594: 84,
+ 0x595: 84,
+ 0x596: 84,
+ 0x597: 84,
+ 0x598: 84,
+ 0x599: 84,
+ 0x59A: 84,
+ 0x59B: 84,
+ 0x59C: 84,
+ 0x59D: 84,
+ 0x59E: 84,
+ 0x59F: 84,
+ 0x5A0: 84,
+ 0x5A1: 84,
+ 0x5A2: 84,
+ 0x5A3: 84,
+ 0x5A4: 84,
+ 0x5A5: 84,
+ 0x5A6: 84,
+ 0x5A7: 84,
+ 0x5A8: 84,
+ 0x5A9: 84,
+ 0x5AA: 84,
+ 0x5AB: 84,
+ 0x5AC: 84,
+ 0x5AD: 84,
+ 0x5AE: 84,
+ 0x5AF: 84,
+ 0x5B0: 84,
+ 0x5B1: 84,
+ 0x5B2: 84,
+ 0x5B3: 84,
+ 0x5B4: 84,
+ 0x5B5: 84,
+ 0x5B6: 84,
+ 0x5B7: 84,
+ 0x5B8: 84,
+ 0x5B9: 84,
+ 0x5BA: 84,
+ 0x5BB: 84,
+ 0x5BC: 84,
+ 0x5BD: 84,
+ 0x5BF: 84,
+ 0x5C1: 84,
+ 0x5C2: 84,
+ 0x5C4: 84,
+ 0x5C5: 84,
+ 0x5C7: 84,
+ 0x610: 84,
+ 0x611: 84,
+ 0x612: 84,
+ 0x613: 84,
+ 0x614: 84,
+ 0x615: 84,
+ 0x616: 84,
+ 0x617: 84,
+ 0x618: 84,
+ 0x619: 84,
+ 0x61A: 84,
+ 0x61C: 84,
+ 0x620: 68,
+ 0x622: 82,
+ 0x623: 82,
+ 0x624: 82,
+ 0x625: 82,
+ 0x626: 68,
+ 0x627: 82,
+ 0x628: 68,
+ 0x629: 82,
+ 0x62A: 68,
+ 0x62B: 68,
+ 0x62C: 68,
+ 0x62D: 68,
+ 0x62E: 68,
+ 0x62F: 82,
+ 0x630: 82,
+ 0x631: 82,
+ 0x632: 82,
+ 0x633: 68,
+ 0x634: 68,
+ 0x635: 68,
+ 0x636: 68,
+ 0x637: 68,
+ 0x638: 68,
+ 0x639: 68,
+ 0x63A: 68,
+ 0x63B: 68,
+ 0x63C: 68,
+ 0x63D: 68,
+ 0x63E: 68,
+ 0x63F: 68,
+ 0x640: 67,
+ 0x641: 68,
+ 0x642: 68,
+ 0x643: 68,
+ 0x644: 68,
+ 0x645: 68,
+ 0x646: 68,
+ 0x647: 68,
+ 0x648: 82,
+ 0x649: 68,
+ 0x64A: 68,
+ 0x64B: 84,
+ 0x64C: 84,
+ 0x64D: 84,
+ 0x64E: 84,
+ 0x64F: 84,
+ 0x650: 84,
+ 0x651: 84,
+ 0x652: 84,
+ 0x653: 84,
+ 0x654: 84,
+ 0x655: 84,
+ 0x656: 84,
+ 0x657: 84,
+ 0x658: 84,
+ 0x659: 84,
+ 0x65A: 84,
+ 0x65B: 84,
+ 0x65C: 84,
+ 0x65D: 84,
+ 0x65E: 84,
+ 0x65F: 84,
+ 0x66E: 68,
+ 0x66F: 68,
+ 0x670: 84,
+ 0x671: 82,
+ 0x672: 82,
+ 0x673: 82,
+ 0x675: 82,
+ 0x676: 82,
+ 0x677: 82,
+ 0x678: 68,
+ 0x679: 68,
+ 0x67A: 68,
+ 0x67B: 68,
+ 0x67C: 68,
+ 0x67D: 68,
+ 0x67E: 68,
+ 0x67F: 68,
+ 0x680: 68,
+ 0x681: 68,
+ 0x682: 68,
+ 0x683: 68,
+ 0x684: 68,
+ 0x685: 68,
+ 0x686: 68,
+ 0x687: 68,
+ 0x688: 82,
+ 0x689: 82,
+ 0x68A: 82,
+ 0x68B: 82,
+ 0x68C: 82,
+ 0x68D: 82,
+ 0x68E: 82,
+ 0x68F: 82,
+ 0x690: 82,
+ 0x691: 82,
+ 0x692: 82,
+ 0x693: 82,
+ 0x694: 82,
+ 0x695: 82,
+ 0x696: 82,
+ 0x697: 82,
+ 0x698: 82,
+ 0x699: 82,
+ 0x69A: 68,
+ 0x69B: 68,
+ 0x69C: 68,
+ 0x69D: 68,
+ 0x69E: 68,
+ 0x69F: 68,
+ 0x6A0: 68,
+ 0x6A1: 68,
+ 0x6A2: 68,
+ 0x6A3: 68,
+ 0x6A4: 68,
+ 0x6A5: 68,
+ 0x6A6: 68,
+ 0x6A7: 68,
+ 0x6A8: 68,
+ 0x6A9: 68,
+ 0x6AA: 68,
+ 0x6AB: 68,
+ 0x6AC: 68,
+ 0x6AD: 68,
+ 0x6AE: 68,
+ 0x6AF: 68,
+ 0x6B0: 68,
+ 0x6B1: 68,
+ 0x6B2: 68,
+ 0x6B3: 68,
+ 0x6B4: 68,
+ 0x6B5: 68,
+ 0x6B6: 68,
+ 0x6B7: 68,
+ 0x6B8: 68,
+ 0x6B9: 68,
+ 0x6BA: 68,
+ 0x6BB: 68,
+ 0x6BC: 68,
+ 0x6BD: 68,
+ 0x6BE: 68,
+ 0x6BF: 68,
+ 0x6C0: 82,
+ 0x6C1: 68,
+ 0x6C2: 68,
+ 0x6C3: 82,
+ 0x6C4: 82,
+ 0x6C5: 82,
+ 0x6C6: 82,
+ 0x6C7: 82,
+ 0x6C8: 82,
+ 0x6C9: 82,
+ 0x6CA: 82,
+ 0x6CB: 82,
+ 0x6CC: 68,
+ 0x6CD: 82,
+ 0x6CE: 68,
+ 0x6CF: 82,
+ 0x6D0: 68,
+ 0x6D1: 68,
+ 0x6D2: 82,
+ 0x6D3: 82,
+ 0x6D5: 82,
+ 0x6D6: 84,
+ 0x6D7: 84,
+ 0x6D8: 84,
+ 0x6D9: 84,
+ 0x6DA: 84,
+ 0x6DB: 84,
+ 0x6DC: 84,
+ 0x6DF: 84,
+ 0x6E0: 84,
+ 0x6E1: 84,
+ 0x6E2: 84,
+ 0x6E3: 84,
+ 0x6E4: 84,
+ 0x6E7: 84,
+ 0x6E8: 84,
+ 0x6EA: 84,
+ 0x6EB: 84,
+ 0x6EC: 84,
+ 0x6ED: 84,
+ 0x6EE: 82,
+ 0x6EF: 82,
+ 0x6FA: 68,
+ 0x6FB: 68,
+ 0x6FC: 68,
+ 0x6FF: 68,
+ 0x70F: 84,
+ 0x710: 82,
+ 0x711: 84,
+ 0x712: 68,
+ 0x713: 68,
+ 0x714: 68,
+ 0x715: 82,
+ 0x716: 82,
+ 0x717: 82,
+ 0x718: 82,
+ 0x719: 82,
+ 0x71A: 68,
+ 0x71B: 68,
+ 0x71C: 68,
+ 0x71D: 68,
+ 0x71E: 82,
+ 0x71F: 68,
+ 0x720: 68,
+ 0x721: 68,
+ 0x722: 68,
+ 0x723: 68,
+ 0x724: 68,
+ 0x725: 68,
+ 0x726: 68,
+ 0x727: 68,
+ 0x728: 82,
+ 0x729: 68,
+ 0x72A: 82,
+ 0x72B: 68,
+ 0x72C: 82,
+ 0x72D: 68,
+ 0x72E: 68,
+ 0x72F: 82,
+ 0x730: 84,
+ 0x731: 84,
+ 0x732: 84,
+ 0x733: 84,
+ 0x734: 84,
+ 0x735: 84,
+ 0x736: 84,
+ 0x737: 84,
+ 0x738: 84,
+ 0x739: 84,
+ 0x73A: 84,
+ 0x73B: 84,
+ 0x73C: 84,
+ 0x73D: 84,
+ 0x73E: 84,
+ 0x73F: 84,
+ 0x740: 84,
+ 0x741: 84,
+ 0x742: 84,
+ 0x743: 84,
+ 0x744: 84,
+ 0x745: 84,
+ 0x746: 84,
+ 0x747: 84,
+ 0x748: 84,
+ 0x749: 84,
+ 0x74A: 84,
+ 0x74D: 82,
+ 0x74E: 68,
+ 0x74F: 68,
+ 0x750: 68,
+ 0x751: 68,
+ 0x752: 68,
+ 0x753: 68,
+ 0x754: 68,
+ 0x755: 68,
+ 0x756: 68,
+ 0x757: 68,
+ 0x758: 68,
+ 0x759: 82,
+ 0x75A: 82,
+ 0x75B: 82,
+ 0x75C: 68,
+ 0x75D: 68,
+ 0x75E: 68,
+ 0x75F: 68,
+ 0x760: 68,
+ 0x761: 68,
+ 0x762: 68,
+ 0x763: 68,
+ 0x764: 68,
+ 0x765: 68,
+ 0x766: 68,
+ 0x767: 68,
+ 0x768: 68,
+ 0x769: 68,
+ 0x76A: 68,
+ 0x76B: 82,
+ 0x76C: 82,
+ 0x76D: 68,
+ 0x76E: 68,
+ 0x76F: 68,
+ 0x770: 68,
+ 0x771: 82,
+ 0x772: 68,
+ 0x773: 82,
+ 0x774: 82,
+ 0x775: 68,
+ 0x776: 68,
+ 0x777: 68,
+ 0x778: 82,
+ 0x779: 82,
+ 0x77A: 68,
+ 0x77B: 68,
+ 0x77C: 68,
+ 0x77D: 68,
+ 0x77E: 68,
+ 0x77F: 68,
+ 0x7A6: 84,
+ 0x7A7: 84,
+ 0x7A8: 84,
+ 0x7A9: 84,
+ 0x7AA: 84,
+ 0x7AB: 84,
+ 0x7AC: 84,
+ 0x7AD: 84,
+ 0x7AE: 84,
+ 0x7AF: 84,
+ 0x7B0: 84,
+ 0x7CA: 68,
+ 0x7CB: 68,
+ 0x7CC: 68,
+ 0x7CD: 68,
+ 0x7CE: 68,
+ 0x7CF: 68,
+ 0x7D0: 68,
+ 0x7D1: 68,
+ 0x7D2: 68,
+ 0x7D3: 68,
+ 0x7D4: 68,
+ 0x7D5: 68,
+ 0x7D6: 68,
+ 0x7D7: 68,
+ 0x7D8: 68,
+ 0x7D9: 68,
+ 0x7DA: 68,
+ 0x7DB: 68,
+ 0x7DC: 68,
+ 0x7DD: 68,
+ 0x7DE: 68,
+ 0x7DF: 68,
+ 0x7E0: 68,
+ 0x7E1: 68,
+ 0x7E2: 68,
+ 0x7E3: 68,
+ 0x7E4: 68,
+ 0x7E5: 68,
+ 0x7E6: 68,
+ 0x7E7: 68,
+ 0x7E8: 68,
+ 0x7E9: 68,
+ 0x7EA: 68,
+ 0x7EB: 84,
+ 0x7EC: 84,
+ 0x7ED: 84,
+ 0x7EE: 84,
+ 0x7EF: 84,
+ 0x7F0: 84,
+ 0x7F1: 84,
+ 0x7F2: 84,
+ 0x7F3: 84,
+ 0x7FA: 67,
+ 0x7FD: 84,
+ 0x816: 84,
+ 0x817: 84,
+ 0x818: 84,
+ 0x819: 84,
+ 0x81B: 84,
+ 0x81C: 84,
+ 0x81D: 84,
+ 0x81E: 84,
+ 0x81F: 84,
+ 0x820: 84,
+ 0x821: 84,
+ 0x822: 84,
+ 0x823: 84,
+ 0x825: 84,
+ 0x826: 84,
+ 0x827: 84,
+ 0x829: 84,
+ 0x82A: 84,
+ 0x82B: 84,
+ 0x82C: 84,
+ 0x82D: 84,
+ 0x840: 82,
+ 0x841: 68,
+ 0x842: 68,
+ 0x843: 68,
+ 0x844: 68,
+ 0x845: 68,
+ 0x846: 82,
+ 0x847: 82,
+ 0x848: 68,
+ 0x849: 82,
+ 0x84A: 68,
+ 0x84B: 68,
+ 0x84C: 68,
+ 0x84D: 68,
+ 0x84E: 68,
+ 0x84F: 68,
+ 0x850: 68,
+ 0x851: 68,
+ 0x852: 68,
+ 0x853: 68,
+ 0x854: 82,
+ 0x855: 68,
+ 0x856: 82,
+ 0x857: 82,
+ 0x858: 82,
+ 0x859: 84,
+ 0x85A: 84,
+ 0x85B: 84,
+ 0x860: 68,
+ 0x862: 68,
+ 0x863: 68,
+ 0x864: 68,
+ 0x865: 68,
+ 0x867: 82,
+ 0x868: 68,
+ 0x869: 82,
+ 0x86A: 82,
+ 0x870: 82,
+ 0x871: 82,
+ 0x872: 82,
+ 0x873: 82,
+ 0x874: 82,
+ 0x875: 82,
+ 0x876: 82,
+ 0x877: 82,
+ 0x878: 82,
+ 0x879: 82,
+ 0x87A: 82,
+ 0x87B: 82,
+ 0x87C: 82,
+ 0x87D: 82,
+ 0x87E: 82,
+ 0x87F: 82,
+ 0x880: 82,
+ 0x881: 82,
+ 0x882: 82,
+ 0x883: 67,
+ 0x884: 67,
+ 0x885: 67,
+ 0x886: 68,
+ 0x889: 68,
+ 0x88A: 68,
+ 0x88B: 68,
+ 0x88C: 68,
+ 0x88D: 68,
+ 0x88E: 82,
+ 0x897: 84,
+ 0x898: 84,
+ 0x899: 84,
+ 0x89A: 84,
+ 0x89B: 84,
+ 0x89C: 84,
+ 0x89D: 84,
+ 0x89E: 84,
+ 0x89F: 84,
+ 0x8A0: 68,
+ 0x8A1: 68,
+ 0x8A2: 68,
+ 0x8A3: 68,
+ 0x8A4: 68,
+ 0x8A5: 68,
+ 0x8A6: 68,
+ 0x8A7: 68,
+ 0x8A8: 68,
+ 0x8A9: 68,
+ 0x8AA: 82,
+ 0x8AB: 82,
+ 0x8AC: 82,
+ 0x8AE: 82,
+ 0x8AF: 68,
+ 0x8B0: 68,
+ 0x8B1: 82,
+ 0x8B2: 82,
+ 0x8B3: 68,
+ 0x8B4: 68,
+ 0x8B5: 68,
+ 0x8B6: 68,
+ 0x8B7: 68,
+ 0x8B8: 68,
+ 0x8B9: 82,
+ 0x8BA: 68,
+ 0x8BB: 68,
+ 0x8BC: 68,
+ 0x8BD: 68,
+ 0x8BE: 68,
+ 0x8BF: 68,
+ 0x8C0: 68,
+ 0x8C1: 68,
+ 0x8C2: 68,
+ 0x8C3: 68,
+ 0x8C4: 68,
+ 0x8C5: 68,
+ 0x8C6: 68,
+ 0x8C7: 68,
+ 0x8C8: 68,
+ 0x8CA: 84,
+ 0x8CB: 84,
+ 0x8CC: 84,
+ 0x8CD: 84,
+ 0x8CE: 84,
+ 0x8CF: 84,
+ 0x8D0: 84,
+ 0x8D1: 84,
+ 0x8D2: 84,
+ 0x8D3: 84,
+ 0x8D4: 84,
+ 0x8D5: 84,
+ 0x8D6: 84,
+ 0x8D7: 84,
+ 0x8D8: 84,
+ 0x8D9: 84,
+ 0x8DA: 84,
+ 0x8DB: 84,
+ 0x8DC: 84,
+ 0x8DD: 84,
+ 0x8DE: 84,
+ 0x8DF: 84,
+ 0x8E0: 84,
+ 0x8E1: 84,
+ 0x8E3: 84,
+ 0x8E4: 84,
+ 0x8E5: 84,
+ 0x8E6: 84,
+ 0x8E7: 84,
+ 0x8E8: 84,
+ 0x8E9: 84,
+ 0x8EA: 84,
+ 0x8EB: 84,
+ 0x8EC: 84,
+ 0x8ED: 84,
+ 0x8EE: 84,
+ 0x8EF: 84,
+ 0x8F0: 84,
+ 0x8F1: 84,
+ 0x8F2: 84,
+ 0x8F3: 84,
+ 0x8F4: 84,
+ 0x8F5: 84,
+ 0x8F6: 84,
+ 0x8F7: 84,
+ 0x8F8: 84,
+ 0x8F9: 84,
+ 0x8FA: 84,
+ 0x8FB: 84,
+ 0x8FC: 84,
+ 0x8FD: 84,
+ 0x8FE: 84,
+ 0x8FF: 84,
+ 0x900: 84,
+ 0x901: 84,
+ 0x902: 84,
+ 0x93A: 84,
+ 0x93C: 84,
+ 0x941: 84,
+ 0x942: 84,
+ 0x943: 84,
+ 0x944: 84,
+ 0x945: 84,
+ 0x946: 84,
+ 0x947: 84,
+ 0x948: 84,
+ 0x94D: 84,
+ 0x951: 84,
+ 0x952: 84,
+ 0x953: 84,
+ 0x954: 84,
+ 0x955: 84,
+ 0x956: 84,
+ 0x957: 84,
+ 0x962: 84,
+ 0x963: 84,
+ 0x981: 84,
+ 0x9BC: 84,
+ 0x9C1: 84,
+ 0x9C2: 84,
+ 0x9C3: 84,
+ 0x9C4: 84,
+ 0x9CD: 84,
+ 0x9E2: 84,
+ 0x9E3: 84,
+ 0x9FE: 84,
+ 0xA01: 84,
+ 0xA02: 84,
+ 0xA3C: 84,
+ 0xA41: 84,
+ 0xA42: 84,
+ 0xA47: 84,
+ 0xA48: 84,
+ 0xA4B: 84,
+ 0xA4C: 84,
+ 0xA4D: 84,
+ 0xA51: 84,
+ 0xA70: 84,
+ 0xA71: 84,
+ 0xA75: 84,
+ 0xA81: 84,
+ 0xA82: 84,
+ 0xABC: 84,
+ 0xAC1: 84,
+ 0xAC2: 84,
+ 0xAC3: 84,
+ 0xAC4: 84,
+ 0xAC5: 84,
+ 0xAC7: 84,
+ 0xAC8: 84,
+ 0xACD: 84,
+ 0xAE2: 84,
+ 0xAE3: 84,
+ 0xAFA: 84,
+ 0xAFB: 84,
+ 0xAFC: 84,
+ 0xAFD: 84,
+ 0xAFE: 84,
+ 0xAFF: 84,
+ 0xB01: 84,
+ 0xB3C: 84,
+ 0xB3F: 84,
+ 0xB41: 84,
+ 0xB42: 84,
+ 0xB43: 84,
+ 0xB44: 84,
+ 0xB4D: 84,
+ 0xB55: 84,
+ 0xB56: 84,
+ 0xB62: 84,
+ 0xB63: 84,
+ 0xB82: 84,
+ 0xBC0: 84,
+ 0xBCD: 84,
+ 0xC00: 84,
+ 0xC04: 84,
+ 0xC3C: 84,
+ 0xC3E: 84,
+ 0xC3F: 84,
+ 0xC40: 84,
+ 0xC46: 84,
+ 0xC47: 84,
+ 0xC48: 84,
+ 0xC4A: 84,
+ 0xC4B: 84,
+ 0xC4C: 84,
+ 0xC4D: 84,
+ 0xC55: 84,
+ 0xC56: 84,
+ 0xC62: 84,
+ 0xC63: 84,
+ 0xC81: 84,
+ 0xCBC: 84,
+ 0xCBF: 84,
+ 0xCC6: 84,
+ 0xCCC: 84,
+ 0xCCD: 84,
+ 0xCE2: 84,
+ 0xCE3: 84,
+ 0xD00: 84,
+ 0xD01: 84,
+ 0xD3B: 84,
+ 0xD3C: 84,
+ 0xD41: 84,
+ 0xD42: 84,
+ 0xD43: 84,
+ 0xD44: 84,
+ 0xD4D: 84,
+ 0xD62: 84,
+ 0xD63: 84,
+ 0xD81: 84,
+ 0xDCA: 84,
+ 0xDD2: 84,
+ 0xDD3: 84,
+ 0xDD4: 84,
+ 0xDD6: 84,
+ 0xE31: 84,
+ 0xE34: 84,
+ 0xE35: 84,
+ 0xE36: 84,
+ 0xE37: 84,
+ 0xE38: 84,
+ 0xE39: 84,
+ 0xE3A: 84,
+ 0xE47: 84,
+ 0xE48: 84,
+ 0xE49: 84,
+ 0xE4A: 84,
+ 0xE4B: 84,
+ 0xE4C: 84,
+ 0xE4D: 84,
+ 0xE4E: 84,
+ 0xEB1: 84,
+ 0xEB4: 84,
+ 0xEB5: 84,
+ 0xEB6: 84,
+ 0xEB7: 84,
+ 0xEB8: 84,
+ 0xEB9: 84,
+ 0xEBA: 84,
+ 0xEBB: 84,
+ 0xEBC: 84,
+ 0xEC8: 84,
+ 0xEC9: 84,
+ 0xECA: 84,
+ 0xECB: 84,
+ 0xECC: 84,
+ 0xECD: 84,
+ 0xECE: 84,
+ 0xF18: 84,
+ 0xF19: 84,
+ 0xF35: 84,
+ 0xF37: 84,
+ 0xF39: 84,
+ 0xF71: 84,
+ 0xF72: 84,
+ 0xF73: 84,
+ 0xF74: 84,
+ 0xF75: 84,
+ 0xF76: 84,
+ 0xF77: 84,
+ 0xF78: 84,
+ 0xF79: 84,
+ 0xF7A: 84,
+ 0xF7B: 84,
+ 0xF7C: 84,
+ 0xF7D: 84,
+ 0xF7E: 84,
+ 0xF80: 84,
+ 0xF81: 84,
+ 0xF82: 84,
+ 0xF83: 84,
+ 0xF84: 84,
+ 0xF86: 84,
+ 0xF87: 84,
+ 0xF8D: 84,
+ 0xF8E: 84,
+ 0xF8F: 84,
+ 0xF90: 84,
+ 0xF91: 84,
+ 0xF92: 84,
+ 0xF93: 84,
+ 0xF94: 84,
+ 0xF95: 84,
+ 0xF96: 84,
+ 0xF97: 84,
+ 0xF99: 84,
+ 0xF9A: 84,
+ 0xF9B: 84,
+ 0xF9C: 84,
+ 0xF9D: 84,
+ 0xF9E: 84,
+ 0xF9F: 84,
+ 0xFA0: 84,
+ 0xFA1: 84,
+ 0xFA2: 84,
+ 0xFA3: 84,
+ 0xFA4: 84,
+ 0xFA5: 84,
+ 0xFA6: 84,
+ 0xFA7: 84,
+ 0xFA8: 84,
+ 0xFA9: 84,
+ 0xFAA: 84,
+ 0xFAB: 84,
+ 0xFAC: 84,
+ 0xFAD: 84,
+ 0xFAE: 84,
+ 0xFAF: 84,
+ 0xFB0: 84,
+ 0xFB1: 84,
+ 0xFB2: 84,
+ 0xFB3: 84,
+ 0xFB4: 84,
+ 0xFB5: 84,
+ 0xFB6: 84,
+ 0xFB7: 84,
+ 0xFB8: 84,
+ 0xFB9: 84,
+ 0xFBA: 84,
+ 0xFBB: 84,
+ 0xFBC: 84,
+ 0xFC6: 84,
+ 0x102D: 84,
+ 0x102E: 84,
+ 0x102F: 84,
+ 0x1030: 84,
+ 0x1032: 84,
+ 0x1033: 84,
+ 0x1034: 84,
+ 0x1035: 84,
+ 0x1036: 84,
+ 0x1037: 84,
+ 0x1039: 84,
+ 0x103A: 84,
+ 0x103D: 84,
+ 0x103E: 84,
+ 0x1058: 84,
+ 0x1059: 84,
+ 0x105E: 84,
+ 0x105F: 84,
+ 0x1060: 84,
+ 0x1071: 84,
+ 0x1072: 84,
+ 0x1073: 84,
+ 0x1074: 84,
+ 0x1082: 84,
+ 0x1085: 84,
+ 0x1086: 84,
+ 0x108D: 84,
+ 0x109D: 84,
+ 0x135D: 84,
+ 0x135E: 84,
+ 0x135F: 84,
+ 0x1712: 84,
+ 0x1713: 84,
+ 0x1714: 84,
+ 0x1732: 84,
+ 0x1733: 84,
+ 0x1752: 84,
+ 0x1753: 84,
+ 0x1772: 84,
+ 0x1773: 84,
+ 0x17B4: 84,
+ 0x17B5: 84,
+ 0x17B7: 84,
+ 0x17B8: 84,
+ 0x17B9: 84,
+ 0x17BA: 84,
+ 0x17BB: 84,
+ 0x17BC: 84,
+ 0x17BD: 84,
+ 0x17C6: 84,
+ 0x17C9: 84,
+ 0x17CA: 84,
+ 0x17CB: 84,
+ 0x17CC: 84,
+ 0x17CD: 84,
+ 0x17CE: 84,
+ 0x17CF: 84,
+ 0x17D0: 84,
+ 0x17D1: 84,
+ 0x17D2: 84,
+ 0x17D3: 84,
+ 0x17DD: 84,
+ 0x1807: 68,
+ 0x180A: 67,
+ 0x180B: 84,
+ 0x180C: 84,
+ 0x180D: 84,
+ 0x180F: 84,
+ 0x1820: 68,
+ 0x1821: 68,
+ 0x1822: 68,
+ 0x1823: 68,
+ 0x1824: 68,
+ 0x1825: 68,
+ 0x1826: 68,
+ 0x1827: 68,
+ 0x1828: 68,
+ 0x1829: 68,
+ 0x182A: 68,
+ 0x182B: 68,
+ 0x182C: 68,
+ 0x182D: 68,
+ 0x182E: 68,
+ 0x182F: 68,
+ 0x1830: 68,
+ 0x1831: 68,
+ 0x1832: 68,
+ 0x1833: 68,
+ 0x1834: 68,
+ 0x1835: 68,
+ 0x1836: 68,
+ 0x1837: 68,
+ 0x1838: 68,
+ 0x1839: 68,
+ 0x183A: 68,
+ 0x183B: 68,
+ 0x183C: 68,
+ 0x183D: 68,
+ 0x183E: 68,
+ 0x183F: 68,
+ 0x1840: 68,
+ 0x1841: 68,
+ 0x1842: 68,
+ 0x1843: 68,
+ 0x1844: 68,
+ 0x1845: 68,
+ 0x1846: 68,
+ 0x1847: 68,
+ 0x1848: 68,
+ 0x1849: 68,
+ 0x184A: 68,
+ 0x184B: 68,
+ 0x184C: 68,
+ 0x184D: 68,
+ 0x184E: 68,
+ 0x184F: 68,
+ 0x1850: 68,
+ 0x1851: 68,
+ 0x1852: 68,
+ 0x1853: 68,
+ 0x1854: 68,
+ 0x1855: 68,
+ 0x1856: 68,
+ 0x1857: 68,
+ 0x1858: 68,
+ 0x1859: 68,
+ 0x185A: 68,
+ 0x185B: 68,
+ 0x185C: 68,
+ 0x185D: 68,
+ 0x185E: 68,
+ 0x185F: 68,
+ 0x1860: 68,
+ 0x1861: 68,
+ 0x1862: 68,
+ 0x1863: 68,
+ 0x1864: 68,
+ 0x1865: 68,
+ 0x1866: 68,
+ 0x1867: 68,
+ 0x1868: 68,
+ 0x1869: 68,
+ 0x186A: 68,
+ 0x186B: 68,
+ 0x186C: 68,
+ 0x186D: 68,
+ 0x186E: 68,
+ 0x186F: 68,
+ 0x1870: 68,
+ 0x1871: 68,
+ 0x1872: 68,
+ 0x1873: 68,
+ 0x1874: 68,
+ 0x1875: 68,
+ 0x1876: 68,
+ 0x1877: 68,
+ 0x1878: 68,
+ 0x1885: 84,
+ 0x1886: 84,
+ 0x1887: 68,
+ 0x1888: 68,
+ 0x1889: 68,
+ 0x188A: 68,
+ 0x188B: 68,
+ 0x188C: 68,
+ 0x188D: 68,
+ 0x188E: 68,
+ 0x188F: 68,
+ 0x1890: 68,
+ 0x1891: 68,
+ 0x1892: 68,
+ 0x1893: 68,
+ 0x1894: 68,
+ 0x1895: 68,
+ 0x1896: 68,
+ 0x1897: 68,
+ 0x1898: 68,
+ 0x1899: 68,
+ 0x189A: 68,
+ 0x189B: 68,
+ 0x189C: 68,
+ 0x189D: 68,
+ 0x189E: 68,
+ 0x189F: 68,
+ 0x18A0: 68,
+ 0x18A1: 68,
+ 0x18A2: 68,
+ 0x18A3: 68,
+ 0x18A4: 68,
+ 0x18A5: 68,
+ 0x18A6: 68,
+ 0x18A7: 68,
+ 0x18A8: 68,
+ 0x18A9: 84,
+ 0x18AA: 68,
+ 0x1920: 84,
+ 0x1921: 84,
+ 0x1922: 84,
+ 0x1927: 84,
+ 0x1928: 84,
+ 0x1932: 84,
+ 0x1939: 84,
+ 0x193A: 84,
+ 0x193B: 84,
+ 0x1A17: 84,
+ 0x1A18: 84,
+ 0x1A1B: 84,
+ 0x1A56: 84,
+ 0x1A58: 84,
+ 0x1A59: 84,
+ 0x1A5A: 84,
+ 0x1A5B: 84,
+ 0x1A5C: 84,
+ 0x1A5D: 84,
+ 0x1A5E: 84,
+ 0x1A60: 84,
+ 0x1A62: 84,
+ 0x1A65: 84,
+ 0x1A66: 84,
+ 0x1A67: 84,
+ 0x1A68: 84,
+ 0x1A69: 84,
+ 0x1A6A: 84,
+ 0x1A6B: 84,
+ 0x1A6C: 84,
+ 0x1A73: 84,
+ 0x1A74: 84,
+ 0x1A75: 84,
+ 0x1A76: 84,
+ 0x1A77: 84,
+ 0x1A78: 84,
+ 0x1A79: 84,
+ 0x1A7A: 84,
+ 0x1A7B: 84,
+ 0x1A7C: 84,
+ 0x1A7F: 84,
+ 0x1AB0: 84,
+ 0x1AB1: 84,
+ 0x1AB2: 84,
+ 0x1AB3: 84,
+ 0x1AB4: 84,
+ 0x1AB5: 84,
+ 0x1AB6: 84,
+ 0x1AB7: 84,
+ 0x1AB8: 84,
+ 0x1AB9: 84,
+ 0x1ABA: 84,
+ 0x1ABB: 84,
+ 0x1ABC: 84,
+ 0x1ABD: 84,
+ 0x1ABE: 84,
+ 0x1ABF: 84,
+ 0x1AC0: 84,
+ 0x1AC1: 84,
+ 0x1AC2: 84,
+ 0x1AC3: 84,
+ 0x1AC4: 84,
+ 0x1AC5: 84,
+ 0x1AC6: 84,
+ 0x1AC7: 84,
+ 0x1AC8: 84,
+ 0x1AC9: 84,
+ 0x1ACA: 84,
+ 0x1ACB: 84,
+ 0x1ACC: 84,
+ 0x1ACD: 84,
+ 0x1ACE: 84,
+ 0x1B00: 84,
+ 0x1B01: 84,
+ 0x1B02: 84,
+ 0x1B03: 84,
+ 0x1B34: 84,
+ 0x1B36: 84,
+ 0x1B37: 84,
+ 0x1B38: 84,
+ 0x1B39: 84,
+ 0x1B3A: 84,
+ 0x1B3C: 84,
+ 0x1B42: 84,
+ 0x1B6B: 84,
+ 0x1B6C: 84,
+ 0x1B6D: 84,
+ 0x1B6E: 84,
+ 0x1B6F: 84,
+ 0x1B70: 84,
+ 0x1B71: 84,
+ 0x1B72: 84,
+ 0x1B73: 84,
+ 0x1B80: 84,
+ 0x1B81: 84,
+ 0x1BA2: 84,
+ 0x1BA3: 84,
+ 0x1BA4: 84,
+ 0x1BA5: 84,
+ 0x1BA8: 84,
+ 0x1BA9: 84,
+ 0x1BAB: 84,
+ 0x1BAC: 84,
+ 0x1BAD: 84,
+ 0x1BE6: 84,
+ 0x1BE8: 84,
+ 0x1BE9: 84,
+ 0x1BED: 84,
+ 0x1BEF: 84,
+ 0x1BF0: 84,
+ 0x1BF1: 84,
+ 0x1C2C: 84,
+ 0x1C2D: 84,
+ 0x1C2E: 84,
+ 0x1C2F: 84,
+ 0x1C30: 84,
+ 0x1C31: 84,
+ 0x1C32: 84,
+ 0x1C33: 84,
+ 0x1C36: 84,
+ 0x1C37: 84,
+ 0x1CD0: 84,
+ 0x1CD1: 84,
+ 0x1CD2: 84,
+ 0x1CD4: 84,
+ 0x1CD5: 84,
+ 0x1CD6: 84,
+ 0x1CD7: 84,
+ 0x1CD8: 84,
+ 0x1CD9: 84,
+ 0x1CDA: 84,
+ 0x1CDB: 84,
+ 0x1CDC: 84,
+ 0x1CDD: 84,
+ 0x1CDE: 84,
+ 0x1CDF: 84,
+ 0x1CE0: 84,
+ 0x1CE2: 84,
+ 0x1CE3: 84,
+ 0x1CE4: 84,
+ 0x1CE5: 84,
+ 0x1CE6: 84,
+ 0x1CE7: 84,
+ 0x1CE8: 84,
+ 0x1CED: 84,
+ 0x1CF4: 84,
+ 0x1CF8: 84,
+ 0x1CF9: 84,
+ 0x1DC0: 84,
+ 0x1DC1: 84,
+ 0x1DC2: 84,
+ 0x1DC3: 84,
+ 0x1DC4: 84,
+ 0x1DC5: 84,
+ 0x1DC6: 84,
+ 0x1DC7: 84,
+ 0x1DC8: 84,
+ 0x1DC9: 84,
+ 0x1DCA: 84,
+ 0x1DCB: 84,
+ 0x1DCC: 84,
+ 0x1DCD: 84,
+ 0x1DCE: 84,
+ 0x1DCF: 84,
+ 0x1DD0: 84,
+ 0x1DD1: 84,
+ 0x1DD2: 84,
+ 0x1DD3: 84,
+ 0x1DD4: 84,
+ 0x1DD5: 84,
+ 0x1DD6: 84,
+ 0x1DD7: 84,
+ 0x1DD8: 84,
+ 0x1DD9: 84,
+ 0x1DDA: 84,
+ 0x1DDB: 84,
+ 0x1DDC: 84,
+ 0x1DDD: 84,
+ 0x1DDE: 84,
+ 0x1DDF: 84,
+ 0x1DE0: 84,
+ 0x1DE1: 84,
+ 0x1DE2: 84,
+ 0x1DE3: 84,
+ 0x1DE4: 84,
+ 0x1DE5: 84,
+ 0x1DE6: 84,
+ 0x1DE7: 84,
+ 0x1DE8: 84,
+ 0x1DE9: 84,
+ 0x1DEA: 84,
+ 0x1DEB: 84,
+ 0x1DEC: 84,
+ 0x1DED: 84,
+ 0x1DEE: 84,
+ 0x1DEF: 84,
+ 0x1DF0: 84,
+ 0x1DF1: 84,
+ 0x1DF2: 84,
+ 0x1DF3: 84,
+ 0x1DF4: 84,
+ 0x1DF5: 84,
+ 0x1DF6: 84,
+ 0x1DF7: 84,
+ 0x1DF8: 84,
+ 0x1DF9: 84,
+ 0x1DFA: 84,
+ 0x1DFB: 84,
+ 0x1DFC: 84,
+ 0x1DFD: 84,
+ 0x1DFE: 84,
+ 0x1DFF: 84,
+ 0x200B: 84,
+ 0x200D: 67,
+ 0x200E: 84,
+ 0x200F: 84,
+ 0x202A: 84,
+ 0x202B: 84,
+ 0x202C: 84,
+ 0x202D: 84,
+ 0x202E: 84,
+ 0x2060: 84,
+ 0x2061: 84,
+ 0x2062: 84,
+ 0x2063: 84,
+ 0x2064: 84,
+ 0x206A: 84,
+ 0x206B: 84,
+ 0x206C: 84,
+ 0x206D: 84,
+ 0x206E: 84,
+ 0x206F: 84,
+ 0x20D0: 84,
+ 0x20D1: 84,
+ 0x20D2: 84,
+ 0x20D3: 84,
+ 0x20D4: 84,
+ 0x20D5: 84,
+ 0x20D6: 84,
+ 0x20D7: 84,
+ 0x20D8: 84,
+ 0x20D9: 84,
+ 0x20DA: 84,
+ 0x20DB: 84,
+ 0x20DC: 84,
+ 0x20DD: 84,
+ 0x20DE: 84,
+ 0x20DF: 84,
+ 0x20E0: 84,
+ 0x20E1: 84,
+ 0x20E2: 84,
+ 0x20E3: 84,
+ 0x20E4: 84,
+ 0x20E5: 84,
+ 0x20E6: 84,
+ 0x20E7: 84,
+ 0x20E8: 84,
+ 0x20E9: 84,
+ 0x20EA: 84,
+ 0x20EB: 84,
+ 0x20EC: 84,
+ 0x20ED: 84,
+ 0x20EE: 84,
+ 0x20EF: 84,
+ 0x20F0: 84,
+ 0x2CEF: 84,
+ 0x2CF0: 84,
+ 0x2CF1: 84,
+ 0x2D7F: 84,
+ 0x2DE0: 84,
+ 0x2DE1: 84,
+ 0x2DE2: 84,
+ 0x2DE3: 84,
+ 0x2DE4: 84,
+ 0x2DE5: 84,
+ 0x2DE6: 84,
+ 0x2DE7: 84,
+ 0x2DE8: 84,
+ 0x2DE9: 84,
+ 0x2DEA: 84,
+ 0x2DEB: 84,
+ 0x2DEC: 84,
+ 0x2DED: 84,
+ 0x2DEE: 84,
+ 0x2DEF: 84,
+ 0x2DF0: 84,
+ 0x2DF1: 84,
+ 0x2DF2: 84,
+ 0x2DF3: 84,
+ 0x2DF4: 84,
+ 0x2DF5: 84,
+ 0x2DF6: 84,
+ 0x2DF7: 84,
+ 0x2DF8: 84,
+ 0x2DF9: 84,
+ 0x2DFA: 84,
+ 0x2DFB: 84,
+ 0x2DFC: 84,
+ 0x2DFD: 84,
+ 0x2DFE: 84,
+ 0x2DFF: 84,
+ 0x302A: 84,
+ 0x302B: 84,
+ 0x302C: 84,
+ 0x302D: 84,
+ 0x3099: 84,
+ 0x309A: 84,
+ 0xA66F: 84,
+ 0xA670: 84,
+ 0xA671: 84,
+ 0xA672: 84,
+ 0xA674: 84,
+ 0xA675: 84,
+ 0xA676: 84,
+ 0xA677: 84,
+ 0xA678: 84,
+ 0xA679: 84,
+ 0xA67A: 84,
+ 0xA67B: 84,
+ 0xA67C: 84,
+ 0xA67D: 84,
+ 0xA69E: 84,
+ 0xA69F: 84,
+ 0xA6F0: 84,
+ 0xA6F1: 84,
+ 0xA802: 84,
+ 0xA806: 84,
+ 0xA80B: 84,
+ 0xA825: 84,
+ 0xA826: 84,
+ 0xA82C: 84,
+ 0xA840: 68,
+ 0xA841: 68,
+ 0xA842: 68,
+ 0xA843: 68,
+ 0xA844: 68,
+ 0xA845: 68,
+ 0xA846: 68,
+ 0xA847: 68,
+ 0xA848: 68,
+ 0xA849: 68,
+ 0xA84A: 68,
+ 0xA84B: 68,
+ 0xA84C: 68,
+ 0xA84D: 68,
+ 0xA84E: 68,
+ 0xA84F: 68,
+ 0xA850: 68,
+ 0xA851: 68,
+ 0xA852: 68,
+ 0xA853: 68,
+ 0xA854: 68,
+ 0xA855: 68,
+ 0xA856: 68,
+ 0xA857: 68,
+ 0xA858: 68,
+ 0xA859: 68,
+ 0xA85A: 68,
+ 0xA85B: 68,
+ 0xA85C: 68,
+ 0xA85D: 68,
+ 0xA85E: 68,
+ 0xA85F: 68,
+ 0xA860: 68,
+ 0xA861: 68,
+ 0xA862: 68,
+ 0xA863: 68,
+ 0xA864: 68,
+ 0xA865: 68,
+ 0xA866: 68,
+ 0xA867: 68,
+ 0xA868: 68,
+ 0xA869: 68,
+ 0xA86A: 68,
+ 0xA86B: 68,
+ 0xA86C: 68,
+ 0xA86D: 68,
+ 0xA86E: 68,
+ 0xA86F: 68,
+ 0xA870: 68,
+ 0xA871: 68,
+ 0xA872: 76,
+ 0xA8C4: 84,
+ 0xA8C5: 84,
+ 0xA8E0: 84,
+ 0xA8E1: 84,
+ 0xA8E2: 84,
+ 0xA8E3: 84,
+ 0xA8E4: 84,
+ 0xA8E5: 84,
+ 0xA8E6: 84,
+ 0xA8E7: 84,
+ 0xA8E8: 84,
+ 0xA8E9: 84,
+ 0xA8EA: 84,
+ 0xA8EB: 84,
+ 0xA8EC: 84,
+ 0xA8ED: 84,
+ 0xA8EE: 84,
+ 0xA8EF: 84,
+ 0xA8F0: 84,
+ 0xA8F1: 84,
+ 0xA8FF: 84,
+ 0xA926: 84,
+ 0xA927: 84,
+ 0xA928: 84,
+ 0xA929: 84,
+ 0xA92A: 84,
+ 0xA92B: 84,
+ 0xA92C: 84,
+ 0xA92D: 84,
+ 0xA947: 84,
+ 0xA948: 84,
+ 0xA949: 84,
+ 0xA94A: 84,
+ 0xA94B: 84,
+ 0xA94C: 84,
+ 0xA94D: 84,
+ 0xA94E: 84,
+ 0xA94F: 84,
+ 0xA950: 84,
+ 0xA951: 84,
+ 0xA980: 84,
+ 0xA981: 84,
+ 0xA982: 84,
+ 0xA9B3: 84,
+ 0xA9B6: 84,
+ 0xA9B7: 84,
+ 0xA9B8: 84,
+ 0xA9B9: 84,
+ 0xA9BC: 84,
+ 0xA9BD: 84,
+ 0xA9E5: 84,
+ 0xAA29: 84,
+ 0xAA2A: 84,
+ 0xAA2B: 84,
+ 0xAA2C: 84,
+ 0xAA2D: 84,
+ 0xAA2E: 84,
+ 0xAA31: 84,
+ 0xAA32: 84,
+ 0xAA35: 84,
+ 0xAA36: 84,
+ 0xAA43: 84,
+ 0xAA4C: 84,
+ 0xAA7C: 84,
+ 0xAAB0: 84,
+ 0xAAB2: 84,
+ 0xAAB3: 84,
+ 0xAAB4: 84,
+ 0xAAB7: 84,
+ 0xAAB8: 84,
+ 0xAABE: 84,
+ 0xAABF: 84,
+ 0xAAC1: 84,
+ 0xAAEC: 84,
+ 0xAAED: 84,
+ 0xAAF6: 84,
+ 0xABE5: 84,
+ 0xABE8: 84,
+ 0xABED: 84,
+ 0xFB1E: 84,
+ 0xFE00: 84,
+ 0xFE01: 84,
+ 0xFE02: 84,
+ 0xFE03: 84,
+ 0xFE04: 84,
+ 0xFE05: 84,
+ 0xFE06: 84,
+ 0xFE07: 84,
+ 0xFE08: 84,
+ 0xFE09: 84,
+ 0xFE0A: 84,
+ 0xFE0B: 84,
+ 0xFE0C: 84,
+ 0xFE0D: 84,
+ 0xFE0E: 84,
+ 0xFE0F: 84,
+ 0xFE20: 84,
+ 0xFE21: 84,
+ 0xFE22: 84,
+ 0xFE23: 84,
+ 0xFE24: 84,
+ 0xFE25: 84,
+ 0xFE26: 84,
+ 0xFE27: 84,
+ 0xFE28: 84,
+ 0xFE29: 84,
+ 0xFE2A: 84,
+ 0xFE2B: 84,
+ 0xFE2C: 84,
+ 0xFE2D: 84,
+ 0xFE2E: 84,
+ 0xFE2F: 84,
+ 0xFEFF: 84,
+ 0xFFF9: 84,
+ 0xFFFA: 84,
+ 0xFFFB: 84,
+ 0x101FD: 84,
+ 0x102E0: 84,
+ 0x10376: 84,
+ 0x10377: 84,
+ 0x10378: 84,
+ 0x10379: 84,
+ 0x1037A: 84,
+ 0x10A01: 84,
+ 0x10A02: 84,
+ 0x10A03: 84,
+ 0x10A05: 84,
+ 0x10A06: 84,
+ 0x10A0C: 84,
+ 0x10A0D: 84,
+ 0x10A0E: 84,
+ 0x10A0F: 84,
+ 0x10A38: 84,
+ 0x10A39: 84,
+ 0x10A3A: 84,
+ 0x10A3F: 84,
+ 0x10AC0: 68,
+ 0x10AC1: 68,
+ 0x10AC2: 68,
+ 0x10AC3: 68,
+ 0x10AC4: 68,
+ 0x10AC5: 82,
+ 0x10AC7: 82,
+ 0x10AC9: 82,
+ 0x10ACA: 82,
+ 0x10ACD: 76,
+ 0x10ACE: 82,
+ 0x10ACF: 82,
+ 0x10AD0: 82,
+ 0x10AD1: 82,
+ 0x10AD2: 82,
+ 0x10AD3: 68,
+ 0x10AD4: 68,
+ 0x10AD5: 68,
+ 0x10AD6: 68,
+ 0x10AD7: 76,
+ 0x10AD8: 68,
+ 0x10AD9: 68,
+ 0x10ADA: 68,
+ 0x10ADB: 68,
+ 0x10ADC: 68,
+ 0x10ADD: 82,
+ 0x10ADE: 68,
+ 0x10ADF: 68,
+ 0x10AE0: 68,
+ 0x10AE1: 82,
+ 0x10AE4: 82,
+ 0x10AE5: 84,
+ 0x10AE6: 84,
+ 0x10AEB: 68,
+ 0x10AEC: 68,
+ 0x10AED: 68,
+ 0x10AEE: 68,
+ 0x10AEF: 82,
+ 0x10B80: 68,
+ 0x10B81: 82,
+ 0x10B82: 68,
+ 0x10B83: 82,
+ 0x10B84: 82,
+ 0x10B85: 82,
+ 0x10B86: 68,
+ 0x10B87: 68,
+ 0x10B88: 68,
+ 0x10B89: 82,
+ 0x10B8A: 68,
+ 0x10B8B: 68,
+ 0x10B8C: 82,
+ 0x10B8D: 68,
+ 0x10B8E: 82,
+ 0x10B8F: 82,
+ 0x10B90: 68,
+ 0x10B91: 82,
+ 0x10BA9: 82,
+ 0x10BAA: 82,
+ 0x10BAB: 82,
+ 0x10BAC: 82,
+ 0x10BAD: 68,
+ 0x10BAE: 68,
+ 0x10D00: 76,
+ 0x10D01: 68,
+ 0x10D02: 68,
+ 0x10D03: 68,
+ 0x10D04: 68,
+ 0x10D05: 68,
+ 0x10D06: 68,
+ 0x10D07: 68,
+ 0x10D08: 68,
+ 0x10D09: 68,
+ 0x10D0A: 68,
+ 0x10D0B: 68,
+ 0x10D0C: 68,
+ 0x10D0D: 68,
+ 0x10D0E: 68,
+ 0x10D0F: 68,
+ 0x10D10: 68,
+ 0x10D11: 68,
+ 0x10D12: 68,
+ 0x10D13: 68,
+ 0x10D14: 68,
+ 0x10D15: 68,
+ 0x10D16: 68,
+ 0x10D17: 68,
+ 0x10D18: 68,
+ 0x10D19: 68,
+ 0x10D1A: 68,
+ 0x10D1B: 68,
+ 0x10D1C: 68,
+ 0x10D1D: 68,
+ 0x10D1E: 68,
+ 0x10D1F: 68,
+ 0x10D20: 68,
+ 0x10D21: 68,
+ 0x10D22: 82,
+ 0x10D23: 68,
+ 0x10D24: 84,
+ 0x10D25: 84,
+ 0x10D26: 84,
+ 0x10D27: 84,
+ 0x10D69: 84,
+ 0x10D6A: 84,
+ 0x10D6B: 84,
+ 0x10D6C: 84,
+ 0x10D6D: 84,
+ 0x10EAB: 84,
+ 0x10EAC: 84,
+ 0x10EC2: 82,
+ 0x10EC3: 68,
+ 0x10EC4: 68,
+ 0x10EFC: 84,
+ 0x10EFD: 84,
+ 0x10EFE: 84,
+ 0x10EFF: 84,
+ 0x10F30: 68,
+ 0x10F31: 68,
+ 0x10F32: 68,
+ 0x10F33: 82,
+ 0x10F34: 68,
+ 0x10F35: 68,
+ 0x10F36: 68,
+ 0x10F37: 68,
+ 0x10F38: 68,
+ 0x10F39: 68,
+ 0x10F3A: 68,
+ 0x10F3B: 68,
+ 0x10F3C: 68,
+ 0x10F3D: 68,
+ 0x10F3E: 68,
+ 0x10F3F: 68,
+ 0x10F40: 68,
+ 0x10F41: 68,
+ 0x10F42: 68,
+ 0x10F43: 68,
+ 0x10F44: 68,
+ 0x10F46: 84,
+ 0x10F47: 84,
+ 0x10F48: 84,
+ 0x10F49: 84,
+ 0x10F4A: 84,
+ 0x10F4B: 84,
+ 0x10F4C: 84,
+ 0x10F4D: 84,
+ 0x10F4E: 84,
+ 0x10F4F: 84,
+ 0x10F50: 84,
+ 0x10F51: 68,
+ 0x10F52: 68,
+ 0x10F53: 68,
+ 0x10F54: 82,
+ 0x10F70: 68,
+ 0x10F71: 68,
+ 0x10F72: 68,
+ 0x10F73: 68,
+ 0x10F74: 82,
+ 0x10F75: 82,
+ 0x10F76: 68,
+ 0x10F77: 68,
+ 0x10F78: 68,
+ 0x10F79: 68,
+ 0x10F7A: 68,
+ 0x10F7B: 68,
+ 0x10F7C: 68,
+ 0x10F7D: 68,
+ 0x10F7E: 68,
+ 0x10F7F: 68,
+ 0x10F80: 68,
+ 0x10F81: 68,
+ 0x10F82: 84,
+ 0x10F83: 84,
+ 0x10F84: 84,
+ 0x10F85: 84,
+ 0x10FB0: 68,
+ 0x10FB2: 68,
+ 0x10FB3: 68,
+ 0x10FB4: 82,
+ 0x10FB5: 82,
+ 0x10FB6: 82,
+ 0x10FB8: 68,
+ 0x10FB9: 82,
+ 0x10FBA: 82,
+ 0x10FBB: 68,
+ 0x10FBC: 68,
+ 0x10FBD: 82,
+ 0x10FBE: 68,
+ 0x10FBF: 68,
+ 0x10FC1: 68,
+ 0x10FC2: 82,
+ 0x10FC3: 82,
+ 0x10FC4: 68,
+ 0x10FC9: 82,
+ 0x10FCA: 68,
+ 0x10FCB: 76,
+ 0x11001: 84,
+ 0x11038: 84,
+ 0x11039: 84,
+ 0x1103A: 84,
+ 0x1103B: 84,
+ 0x1103C: 84,
+ 0x1103D: 84,
+ 0x1103E: 84,
+ 0x1103F: 84,
+ 0x11040: 84,
+ 0x11041: 84,
+ 0x11042: 84,
+ 0x11043: 84,
+ 0x11044: 84,
+ 0x11045: 84,
+ 0x11046: 84,
+ 0x11070: 84,
+ 0x11073: 84,
+ 0x11074: 84,
+ 0x1107F: 84,
+ 0x11080: 84,
+ 0x11081: 84,
+ 0x110B3: 84,
+ 0x110B4: 84,
+ 0x110B5: 84,
+ 0x110B6: 84,
+ 0x110B9: 84,
+ 0x110BA: 84,
+ 0x110C2: 84,
+ 0x11100: 84,
+ 0x11101: 84,
+ 0x11102: 84,
+ 0x11127: 84,
+ 0x11128: 84,
+ 0x11129: 84,
+ 0x1112A: 84,
+ 0x1112B: 84,
+ 0x1112D: 84,
+ 0x1112E: 84,
+ 0x1112F: 84,
+ 0x11130: 84,
+ 0x11131: 84,
+ 0x11132: 84,
+ 0x11133: 84,
+ 0x11134: 84,
+ 0x11173: 84,
+ 0x11180: 84,
+ 0x11181: 84,
+ 0x111B6: 84,
+ 0x111B7: 84,
+ 0x111B8: 84,
+ 0x111B9: 84,
+ 0x111BA: 84,
+ 0x111BB: 84,
+ 0x111BC: 84,
+ 0x111BD: 84,
+ 0x111BE: 84,
+ 0x111C9: 84,
+ 0x111CA: 84,
+ 0x111CB: 84,
+ 0x111CC: 84,
+ 0x111CF: 84,
+ 0x1122F: 84,
+ 0x11230: 84,
+ 0x11231: 84,
+ 0x11234: 84,
+ 0x11236: 84,
+ 0x11237: 84,
+ 0x1123E: 84,
+ 0x11241: 84,
+ 0x112DF: 84,
+ 0x112E3: 84,
+ 0x112E4: 84,
+ 0x112E5: 84,
+ 0x112E6: 84,
+ 0x112E7: 84,
+ 0x112E8: 84,
+ 0x112E9: 84,
+ 0x112EA: 84,
+ 0x11300: 84,
+ 0x11301: 84,
+ 0x1133B: 84,
+ 0x1133C: 84,
+ 0x11340: 84,
+ 0x11366: 84,
+ 0x11367: 84,
+ 0x11368: 84,
+ 0x11369: 84,
+ 0x1136A: 84,
+ 0x1136B: 84,
+ 0x1136C: 84,
+ 0x11370: 84,
+ 0x11371: 84,
+ 0x11372: 84,
+ 0x11373: 84,
+ 0x11374: 84,
+ 0x113BB: 84,
+ 0x113BC: 84,
+ 0x113BD: 84,
+ 0x113BE: 84,
+ 0x113BF: 84,
+ 0x113C0: 84,
+ 0x113CE: 84,
+ 0x113D0: 84,
+ 0x113D2: 84,
+ 0x113E1: 84,
+ 0x113E2: 84,
+ 0x11438: 84,
+ 0x11439: 84,
+ 0x1143A: 84,
+ 0x1143B: 84,
+ 0x1143C: 84,
+ 0x1143D: 84,
+ 0x1143E: 84,
+ 0x1143F: 84,
+ 0x11442: 84,
+ 0x11443: 84,
+ 0x11444: 84,
+ 0x11446: 84,
+ 0x1145E: 84,
+ 0x114B3: 84,
+ 0x114B4: 84,
+ 0x114B5: 84,
+ 0x114B6: 84,
+ 0x114B7: 84,
+ 0x114B8: 84,
+ 0x114BA: 84,
+ 0x114BF: 84,
+ 0x114C0: 84,
+ 0x114C2: 84,
+ 0x114C3: 84,
+ 0x115B2: 84,
+ 0x115B3: 84,
+ 0x115B4: 84,
+ 0x115B5: 84,
+ 0x115BC: 84,
+ 0x115BD: 84,
+ 0x115BF: 84,
+ 0x115C0: 84,
+ 0x115DC: 84,
+ 0x115DD: 84,
+ 0x11633: 84,
+ 0x11634: 84,
+ 0x11635: 84,
+ 0x11636: 84,
+ 0x11637: 84,
+ 0x11638: 84,
+ 0x11639: 84,
+ 0x1163A: 84,
+ 0x1163D: 84,
+ 0x1163F: 84,
+ 0x11640: 84,
+ 0x116AB: 84,
+ 0x116AD: 84,
+ 0x116B0: 84,
+ 0x116B1: 84,
+ 0x116B2: 84,
+ 0x116B3: 84,
+ 0x116B4: 84,
+ 0x116B5: 84,
+ 0x116B7: 84,
+ 0x1171D: 84,
+ 0x1171F: 84,
+ 0x11722: 84,
+ 0x11723: 84,
+ 0x11724: 84,
+ 0x11725: 84,
+ 0x11727: 84,
+ 0x11728: 84,
+ 0x11729: 84,
+ 0x1172A: 84,
+ 0x1172B: 84,
+ 0x1182F: 84,
+ 0x11830: 84,
+ 0x11831: 84,
+ 0x11832: 84,
+ 0x11833: 84,
+ 0x11834: 84,
+ 0x11835: 84,
+ 0x11836: 84,
+ 0x11837: 84,
+ 0x11839: 84,
+ 0x1183A: 84,
+ 0x1193B: 84,
+ 0x1193C: 84,
+ 0x1193E: 84,
+ 0x11943: 84,
+ 0x119D4: 84,
+ 0x119D5: 84,
+ 0x119D6: 84,
+ 0x119D7: 84,
+ 0x119DA: 84,
+ 0x119DB: 84,
+ 0x119E0: 84,
+ 0x11A01: 84,
+ 0x11A02: 84,
+ 0x11A03: 84,
+ 0x11A04: 84,
+ 0x11A05: 84,
+ 0x11A06: 84,
+ 0x11A07: 84,
+ 0x11A08: 84,
+ 0x11A09: 84,
+ 0x11A0A: 84,
+ 0x11A33: 84,
+ 0x11A34: 84,
+ 0x11A35: 84,
+ 0x11A36: 84,
+ 0x11A37: 84,
+ 0x11A38: 84,
+ 0x11A3B: 84,
+ 0x11A3C: 84,
+ 0x11A3D: 84,
+ 0x11A3E: 84,
+ 0x11A47: 84,
+ 0x11A51: 84,
+ 0x11A52: 84,
+ 0x11A53: 84,
+ 0x11A54: 84,
+ 0x11A55: 84,
+ 0x11A56: 84,
+ 0x11A59: 84,
+ 0x11A5A: 84,
+ 0x11A5B: 84,
+ 0x11A8A: 84,
+ 0x11A8B: 84,
+ 0x11A8C: 84,
+ 0x11A8D: 84,
+ 0x11A8E: 84,
+ 0x11A8F: 84,
+ 0x11A90: 84,
+ 0x11A91: 84,
+ 0x11A92: 84,
+ 0x11A93: 84,
+ 0x11A94: 84,
+ 0x11A95: 84,
+ 0x11A96: 84,
+ 0x11A98: 84,
+ 0x11A99: 84,
+ 0x11C30: 84,
+ 0x11C31: 84,
+ 0x11C32: 84,
+ 0x11C33: 84,
+ 0x11C34: 84,
+ 0x11C35: 84,
+ 0x11C36: 84,
+ 0x11C38: 84,
+ 0x11C39: 84,
+ 0x11C3A: 84,
+ 0x11C3B: 84,
+ 0x11C3C: 84,
+ 0x11C3D: 84,
+ 0x11C3F: 84,
+ 0x11C92: 84,
+ 0x11C93: 84,
+ 0x11C94: 84,
+ 0x11C95: 84,
+ 0x11C96: 84,
+ 0x11C97: 84,
+ 0x11C98: 84,
+ 0x11C99: 84,
+ 0x11C9A: 84,
+ 0x11C9B: 84,
+ 0x11C9C: 84,
+ 0x11C9D: 84,
+ 0x11C9E: 84,
+ 0x11C9F: 84,
+ 0x11CA0: 84,
+ 0x11CA1: 84,
+ 0x11CA2: 84,
+ 0x11CA3: 84,
+ 0x11CA4: 84,
+ 0x11CA5: 84,
+ 0x11CA6: 84,
+ 0x11CA7: 84,
+ 0x11CAA: 84,
+ 0x11CAB: 84,
+ 0x11CAC: 84,
+ 0x11CAD: 84,
+ 0x11CAE: 84,
+ 0x11CAF: 84,
+ 0x11CB0: 84,
+ 0x11CB2: 84,
+ 0x11CB3: 84,
+ 0x11CB5: 84,
+ 0x11CB6: 84,
+ 0x11D31: 84,
+ 0x11D32: 84,
+ 0x11D33: 84,
+ 0x11D34: 84,
+ 0x11D35: 84,
+ 0x11D36: 84,
+ 0x11D3A: 84,
+ 0x11D3C: 84,
+ 0x11D3D: 84,
+ 0x11D3F: 84,
+ 0x11D40: 84,
+ 0x11D41: 84,
+ 0x11D42: 84,
+ 0x11D43: 84,
+ 0x11D44: 84,
+ 0x11D45: 84,
+ 0x11D47: 84,
+ 0x11D90: 84,
+ 0x11D91: 84,
+ 0x11D95: 84,
+ 0x11D97: 84,
+ 0x11EF3: 84,
+ 0x11EF4: 84,
+ 0x11F00: 84,
+ 0x11F01: 84,
+ 0x11F36: 84,
+ 0x11F37: 84,
+ 0x11F38: 84,
+ 0x11F39: 84,
+ 0x11F3A: 84,
+ 0x11F40: 84,
+ 0x11F42: 84,
+ 0x11F5A: 84,
+ 0x13430: 84,
+ 0x13431: 84,
+ 0x13432: 84,
+ 0x13433: 84,
+ 0x13434: 84,
+ 0x13435: 84,
+ 0x13436: 84,
+ 0x13437: 84,
+ 0x13438: 84,
+ 0x13439: 84,
+ 0x1343A: 84,
+ 0x1343B: 84,
+ 0x1343C: 84,
+ 0x1343D: 84,
+ 0x1343E: 84,
+ 0x1343F: 84,
+ 0x13440: 84,
+ 0x13447: 84,
+ 0x13448: 84,
+ 0x13449: 84,
+ 0x1344A: 84,
+ 0x1344B: 84,
+ 0x1344C: 84,
+ 0x1344D: 84,
+ 0x1344E: 84,
+ 0x1344F: 84,
+ 0x13450: 84,
+ 0x13451: 84,
+ 0x13452: 84,
+ 0x13453: 84,
+ 0x13454: 84,
+ 0x13455: 84,
+ 0x1611E: 84,
+ 0x1611F: 84,
+ 0x16120: 84,
+ 0x16121: 84,
+ 0x16122: 84,
+ 0x16123: 84,
+ 0x16124: 84,
+ 0x16125: 84,
+ 0x16126: 84,
+ 0x16127: 84,
+ 0x16128: 84,
+ 0x16129: 84,
+ 0x1612D: 84,
+ 0x1612E: 84,
+ 0x1612F: 84,
+ 0x16AF0: 84,
+ 0x16AF1: 84,
+ 0x16AF2: 84,
+ 0x16AF3: 84,
+ 0x16AF4: 84,
+ 0x16B30: 84,
+ 0x16B31: 84,
+ 0x16B32: 84,
+ 0x16B33: 84,
+ 0x16B34: 84,
+ 0x16B35: 84,
+ 0x16B36: 84,
+ 0x16F4F: 84,
+ 0x16F8F: 84,
+ 0x16F90: 84,
+ 0x16F91: 84,
+ 0x16F92: 84,
+ 0x16FE4: 84,
+ 0x1BC9D: 84,
+ 0x1BC9E: 84,
+ 0x1BCA0: 84,
+ 0x1BCA1: 84,
+ 0x1BCA2: 84,
+ 0x1BCA3: 84,
+ 0x1CF00: 84,
+ 0x1CF01: 84,
+ 0x1CF02: 84,
+ 0x1CF03: 84,
+ 0x1CF04: 84,
+ 0x1CF05: 84,
+ 0x1CF06: 84,
+ 0x1CF07: 84,
+ 0x1CF08: 84,
+ 0x1CF09: 84,
+ 0x1CF0A: 84,
+ 0x1CF0B: 84,
+ 0x1CF0C: 84,
+ 0x1CF0D: 84,
+ 0x1CF0E: 84,
+ 0x1CF0F: 84,
+ 0x1CF10: 84,
+ 0x1CF11: 84,
+ 0x1CF12: 84,
+ 0x1CF13: 84,
+ 0x1CF14: 84,
+ 0x1CF15: 84,
+ 0x1CF16: 84,
+ 0x1CF17: 84,
+ 0x1CF18: 84,
+ 0x1CF19: 84,
+ 0x1CF1A: 84,
+ 0x1CF1B: 84,
+ 0x1CF1C: 84,
+ 0x1CF1D: 84,
+ 0x1CF1E: 84,
+ 0x1CF1F: 84,
+ 0x1CF20: 84,
+ 0x1CF21: 84,
+ 0x1CF22: 84,
+ 0x1CF23: 84,
+ 0x1CF24: 84,
+ 0x1CF25: 84,
+ 0x1CF26: 84,
+ 0x1CF27: 84,
+ 0x1CF28: 84,
+ 0x1CF29: 84,
+ 0x1CF2A: 84,
+ 0x1CF2B: 84,
+ 0x1CF2C: 84,
+ 0x1CF2D: 84,
+ 0x1CF30: 84,
+ 0x1CF31: 84,
+ 0x1CF32: 84,
+ 0x1CF33: 84,
+ 0x1CF34: 84,
+ 0x1CF35: 84,
+ 0x1CF36: 84,
+ 0x1CF37: 84,
+ 0x1CF38: 84,
+ 0x1CF39: 84,
+ 0x1CF3A: 84,
+ 0x1CF3B: 84,
+ 0x1CF3C: 84,
+ 0x1CF3D: 84,
+ 0x1CF3E: 84,
+ 0x1CF3F: 84,
+ 0x1CF40: 84,
+ 0x1CF41: 84,
+ 0x1CF42: 84,
+ 0x1CF43: 84,
+ 0x1CF44: 84,
+ 0x1CF45: 84,
+ 0x1CF46: 84,
+ 0x1D167: 84,
+ 0x1D168: 84,
+ 0x1D169: 84,
+ 0x1D173: 84,
+ 0x1D174: 84,
+ 0x1D175: 84,
+ 0x1D176: 84,
+ 0x1D177: 84,
+ 0x1D178: 84,
+ 0x1D179: 84,
+ 0x1D17A: 84,
+ 0x1D17B: 84,
+ 0x1D17C: 84,
+ 0x1D17D: 84,
+ 0x1D17E: 84,
+ 0x1D17F: 84,
+ 0x1D180: 84,
+ 0x1D181: 84,
+ 0x1D182: 84,
+ 0x1D185: 84,
+ 0x1D186: 84,
+ 0x1D187: 84,
+ 0x1D188: 84,
+ 0x1D189: 84,
+ 0x1D18A: 84,
+ 0x1D18B: 84,
+ 0x1D1AA: 84,
+ 0x1D1AB: 84,
+ 0x1D1AC: 84,
+ 0x1D1AD: 84,
+ 0x1D242: 84,
+ 0x1D243: 84,
+ 0x1D244: 84,
+ 0x1DA00: 84,
+ 0x1DA01: 84,
+ 0x1DA02: 84,
+ 0x1DA03: 84,
+ 0x1DA04: 84,
+ 0x1DA05: 84,
+ 0x1DA06: 84,
+ 0x1DA07: 84,
+ 0x1DA08: 84,
+ 0x1DA09: 84,
+ 0x1DA0A: 84,
+ 0x1DA0B: 84,
+ 0x1DA0C: 84,
+ 0x1DA0D: 84,
+ 0x1DA0E: 84,
+ 0x1DA0F: 84,
+ 0x1DA10: 84,
+ 0x1DA11: 84,
+ 0x1DA12: 84,
+ 0x1DA13: 84,
+ 0x1DA14: 84,
+ 0x1DA15: 84,
+ 0x1DA16: 84,
+ 0x1DA17: 84,
+ 0x1DA18: 84,
+ 0x1DA19: 84,
+ 0x1DA1A: 84,
+ 0x1DA1B: 84,
+ 0x1DA1C: 84,
+ 0x1DA1D: 84,
+ 0x1DA1E: 84,
+ 0x1DA1F: 84,
+ 0x1DA20: 84,
+ 0x1DA21: 84,
+ 0x1DA22: 84,
+ 0x1DA23: 84,
+ 0x1DA24: 84,
+ 0x1DA25: 84,
+ 0x1DA26: 84,
+ 0x1DA27: 84,
+ 0x1DA28: 84,
+ 0x1DA29: 84,
+ 0x1DA2A: 84,
+ 0x1DA2B: 84,
+ 0x1DA2C: 84,
+ 0x1DA2D: 84,
+ 0x1DA2E: 84,
+ 0x1DA2F: 84,
+ 0x1DA30: 84,
+ 0x1DA31: 84,
+ 0x1DA32: 84,
+ 0x1DA33: 84,
+ 0x1DA34: 84,
+ 0x1DA35: 84,
+ 0x1DA36: 84,
+ 0x1DA3B: 84,
+ 0x1DA3C: 84,
+ 0x1DA3D: 84,
+ 0x1DA3E: 84,
+ 0x1DA3F: 84,
+ 0x1DA40: 84,
+ 0x1DA41: 84,
+ 0x1DA42: 84,
+ 0x1DA43: 84,
+ 0x1DA44: 84,
+ 0x1DA45: 84,
+ 0x1DA46: 84,
+ 0x1DA47: 84,
+ 0x1DA48: 84,
+ 0x1DA49: 84,
+ 0x1DA4A: 84,
+ 0x1DA4B: 84,
+ 0x1DA4C: 84,
+ 0x1DA4D: 84,
+ 0x1DA4E: 84,
+ 0x1DA4F: 84,
+ 0x1DA50: 84,
+ 0x1DA51: 84,
+ 0x1DA52: 84,
+ 0x1DA53: 84,
+ 0x1DA54: 84,
+ 0x1DA55: 84,
+ 0x1DA56: 84,
+ 0x1DA57: 84,
+ 0x1DA58: 84,
+ 0x1DA59: 84,
+ 0x1DA5A: 84,
+ 0x1DA5B: 84,
+ 0x1DA5C: 84,
+ 0x1DA5D: 84,
+ 0x1DA5E: 84,
+ 0x1DA5F: 84,
+ 0x1DA60: 84,
+ 0x1DA61: 84,
+ 0x1DA62: 84,
+ 0x1DA63: 84,
+ 0x1DA64: 84,
+ 0x1DA65: 84,
+ 0x1DA66: 84,
+ 0x1DA67: 84,
+ 0x1DA68: 84,
+ 0x1DA69: 84,
+ 0x1DA6A: 84,
+ 0x1DA6B: 84,
+ 0x1DA6C: 84,
+ 0x1DA75: 84,
+ 0x1DA84: 84,
+ 0x1DA9B: 84,
+ 0x1DA9C: 84,
+ 0x1DA9D: 84,
+ 0x1DA9E: 84,
+ 0x1DA9F: 84,
+ 0x1DAA1: 84,
+ 0x1DAA2: 84,
+ 0x1DAA3: 84,
+ 0x1DAA4: 84,
+ 0x1DAA5: 84,
+ 0x1DAA6: 84,
+ 0x1DAA7: 84,
+ 0x1DAA8: 84,
+ 0x1DAA9: 84,
+ 0x1DAAA: 84,
+ 0x1DAAB: 84,
+ 0x1DAAC: 84,
+ 0x1DAAD: 84,
+ 0x1DAAE: 84,
+ 0x1DAAF: 84,
+ 0x1E000: 84,
+ 0x1E001: 84,
+ 0x1E002: 84,
+ 0x1E003: 84,
+ 0x1E004: 84,
+ 0x1E005: 84,
+ 0x1E006: 84,
+ 0x1E008: 84,
+ 0x1E009: 84,
+ 0x1E00A: 84,
+ 0x1E00B: 84,
+ 0x1E00C: 84,
+ 0x1E00D: 84,
+ 0x1E00E: 84,
+ 0x1E00F: 84,
+ 0x1E010: 84,
+ 0x1E011: 84,
+ 0x1E012: 84,
+ 0x1E013: 84,
+ 0x1E014: 84,
+ 0x1E015: 84,
+ 0x1E016: 84,
+ 0x1E017: 84,
+ 0x1E018: 84,
+ 0x1E01B: 84,
+ 0x1E01C: 84,
+ 0x1E01D: 84,
+ 0x1E01E: 84,
+ 0x1E01F: 84,
+ 0x1E020: 84,
+ 0x1E021: 84,
+ 0x1E023: 84,
+ 0x1E024: 84,
+ 0x1E026: 84,
+ 0x1E027: 84,
+ 0x1E028: 84,
+ 0x1E029: 84,
+ 0x1E02A: 84,
+ 0x1E08F: 84,
+ 0x1E130: 84,
+ 0x1E131: 84,
+ 0x1E132: 84,
+ 0x1E133: 84,
+ 0x1E134: 84,
+ 0x1E135: 84,
+ 0x1E136: 84,
+ 0x1E2AE: 84,
+ 0x1E2EC: 84,
+ 0x1E2ED: 84,
+ 0x1E2EE: 84,
+ 0x1E2EF: 84,
+ 0x1E4EC: 84,
+ 0x1E4ED: 84,
+ 0x1E4EE: 84,
+ 0x1E4EF: 84,
+ 0x1E5EE: 84,
+ 0x1E5EF: 84,
+ 0x1E8D0: 84,
+ 0x1E8D1: 84,
+ 0x1E8D2: 84,
+ 0x1E8D3: 84,
+ 0x1E8D4: 84,
+ 0x1E8D5: 84,
+ 0x1E8D6: 84,
+ 0x1E900: 68,
+ 0x1E901: 68,
+ 0x1E902: 68,
+ 0x1E903: 68,
+ 0x1E904: 68,
+ 0x1E905: 68,
+ 0x1E906: 68,
+ 0x1E907: 68,
+ 0x1E908: 68,
+ 0x1E909: 68,
+ 0x1E90A: 68,
+ 0x1E90B: 68,
+ 0x1E90C: 68,
+ 0x1E90D: 68,
+ 0x1E90E: 68,
+ 0x1E90F: 68,
+ 0x1E910: 68,
+ 0x1E911: 68,
+ 0x1E912: 68,
+ 0x1E913: 68,
+ 0x1E914: 68,
+ 0x1E915: 68,
+ 0x1E916: 68,
+ 0x1E917: 68,
+ 0x1E918: 68,
+ 0x1E919: 68,
+ 0x1E91A: 68,
+ 0x1E91B: 68,
+ 0x1E91C: 68,
+ 0x1E91D: 68,
+ 0x1E91E: 68,
+ 0x1E91F: 68,
+ 0x1E920: 68,
+ 0x1E921: 68,
+ 0x1E922: 68,
+ 0x1E923: 68,
+ 0x1E924: 68,
+ 0x1E925: 68,
+ 0x1E926: 68,
+ 0x1E927: 68,
+ 0x1E928: 68,
+ 0x1E929: 68,
+ 0x1E92A: 68,
+ 0x1E92B: 68,
+ 0x1E92C: 68,
+ 0x1E92D: 68,
+ 0x1E92E: 68,
+ 0x1E92F: 68,
+ 0x1E930: 68,
+ 0x1E931: 68,
+ 0x1E932: 68,
+ 0x1E933: 68,
+ 0x1E934: 68,
+ 0x1E935: 68,
+ 0x1E936: 68,
+ 0x1E937: 68,
+ 0x1E938: 68,
+ 0x1E939: 68,
+ 0x1E93A: 68,
+ 0x1E93B: 68,
+ 0x1E93C: 68,
+ 0x1E93D: 68,
+ 0x1E93E: 68,
+ 0x1E93F: 68,
+ 0x1E940: 68,
+ 0x1E941: 68,
+ 0x1E942: 68,
+ 0x1E943: 68,
+ 0x1E944: 84,
+ 0x1E945: 84,
+ 0x1E946: 84,
+ 0x1E947: 84,
+ 0x1E948: 84,
+ 0x1E949: 84,
+ 0x1E94A: 84,
+ 0x1E94B: 84,
+ 0xE0001: 84,
+ 0xE0020: 84,
+ 0xE0021: 84,
+ 0xE0022: 84,
+ 0xE0023: 84,
+ 0xE0024: 84,
+ 0xE0025: 84,
+ 0xE0026: 84,
+ 0xE0027: 84,
+ 0xE0028: 84,
+ 0xE0029: 84,
+ 0xE002A: 84,
+ 0xE002B: 84,
+ 0xE002C: 84,
+ 0xE002D: 84,
+ 0xE002E: 84,
+ 0xE002F: 84,
+ 0xE0030: 84,
+ 0xE0031: 84,
+ 0xE0032: 84,
+ 0xE0033: 84,
+ 0xE0034: 84,
+ 0xE0035: 84,
+ 0xE0036: 84,
+ 0xE0037: 84,
+ 0xE0038: 84,
+ 0xE0039: 84,
+ 0xE003A: 84,
+ 0xE003B: 84,
+ 0xE003C: 84,
+ 0xE003D: 84,
+ 0xE003E: 84,
+ 0xE003F: 84,
+ 0xE0040: 84,
+ 0xE0041: 84,
+ 0xE0042: 84,
+ 0xE0043: 84,
+ 0xE0044: 84,
+ 0xE0045: 84,
+ 0xE0046: 84,
+ 0xE0047: 84,
+ 0xE0048: 84,
+ 0xE0049: 84,
+ 0xE004A: 84,
+ 0xE004B: 84,
+ 0xE004C: 84,
+ 0xE004D: 84,
+ 0xE004E: 84,
+ 0xE004F: 84,
+ 0xE0050: 84,
+ 0xE0051: 84,
+ 0xE0052: 84,
+ 0xE0053: 84,
+ 0xE0054: 84,
+ 0xE0055: 84,
+ 0xE0056: 84,
+ 0xE0057: 84,
+ 0xE0058: 84,
+ 0xE0059: 84,
+ 0xE005A: 84,
+ 0xE005B: 84,
+ 0xE005C: 84,
+ 0xE005D: 84,
+ 0xE005E: 84,
+ 0xE005F: 84,
+ 0xE0060: 84,
+ 0xE0061: 84,
+ 0xE0062: 84,
+ 0xE0063: 84,
+ 0xE0064: 84,
+ 0xE0065: 84,
+ 0xE0066: 84,
+ 0xE0067: 84,
+ 0xE0068: 84,
+ 0xE0069: 84,
+ 0xE006A: 84,
+ 0xE006B: 84,
+ 0xE006C: 84,
+ 0xE006D: 84,
+ 0xE006E: 84,
+ 0xE006F: 84,
+ 0xE0070: 84,
+ 0xE0071: 84,
+ 0xE0072: 84,
+ 0xE0073: 84,
+ 0xE0074: 84,
+ 0xE0075: 84,
+ 0xE0076: 84,
+ 0xE0077: 84,
+ 0xE0078: 84,
+ 0xE0079: 84,
+ 0xE007A: 84,
+ 0xE007B: 84,
+ 0xE007C: 84,
+ 0xE007D: 84,
+ 0xE007E: 84,
+ 0xE007F: 84,
+ 0xE0100: 84,
+ 0xE0101: 84,
+ 0xE0102: 84,
+ 0xE0103: 84,
+ 0xE0104: 84,
+ 0xE0105: 84,
+ 0xE0106: 84,
+ 0xE0107: 84,
+ 0xE0108: 84,
+ 0xE0109: 84,
+ 0xE010A: 84,
+ 0xE010B: 84,
+ 0xE010C: 84,
+ 0xE010D: 84,
+ 0xE010E: 84,
+ 0xE010F: 84,
+ 0xE0110: 84,
+ 0xE0111: 84,
+ 0xE0112: 84,
+ 0xE0113: 84,
+ 0xE0114: 84,
+ 0xE0115: 84,
+ 0xE0116: 84,
+ 0xE0117: 84,
+ 0xE0118: 84,
+ 0xE0119: 84,
+ 0xE011A: 84,
+ 0xE011B: 84,
+ 0xE011C: 84,
+ 0xE011D: 84,
+ 0xE011E: 84,
+ 0xE011F: 84,
+ 0xE0120: 84,
+ 0xE0121: 84,
+ 0xE0122: 84,
+ 0xE0123: 84,
+ 0xE0124: 84,
+ 0xE0125: 84,
+ 0xE0126: 84,
+ 0xE0127: 84,
+ 0xE0128: 84,
+ 0xE0129: 84,
+ 0xE012A: 84,
+ 0xE012B: 84,
+ 0xE012C: 84,
+ 0xE012D: 84,
+ 0xE012E: 84,
+ 0xE012F: 84,
+ 0xE0130: 84,
+ 0xE0131: 84,
+ 0xE0132: 84,
+ 0xE0133: 84,
+ 0xE0134: 84,
+ 0xE0135: 84,
+ 0xE0136: 84,
+ 0xE0137: 84,
+ 0xE0138: 84,
+ 0xE0139: 84,
+ 0xE013A: 84,
+ 0xE013B: 84,
+ 0xE013C: 84,
+ 0xE013D: 84,
+ 0xE013E: 84,
+ 0xE013F: 84,
+ 0xE0140: 84,
+ 0xE0141: 84,
+ 0xE0142: 84,
+ 0xE0143: 84,
+ 0xE0144: 84,
+ 0xE0145: 84,
+ 0xE0146: 84,
+ 0xE0147: 84,
+ 0xE0148: 84,
+ 0xE0149: 84,
+ 0xE014A: 84,
+ 0xE014B: 84,
+ 0xE014C: 84,
+ 0xE014D: 84,
+ 0xE014E: 84,
+ 0xE014F: 84,
+ 0xE0150: 84,
+ 0xE0151: 84,
+ 0xE0152: 84,
+ 0xE0153: 84,
+ 0xE0154: 84,
+ 0xE0155: 84,
+ 0xE0156: 84,
+ 0xE0157: 84,
+ 0xE0158: 84,
+ 0xE0159: 84,
+ 0xE015A: 84,
+ 0xE015B: 84,
+ 0xE015C: 84,
+ 0xE015D: 84,
+ 0xE015E: 84,
+ 0xE015F: 84,
+ 0xE0160: 84,
+ 0xE0161: 84,
+ 0xE0162: 84,
+ 0xE0163: 84,
+ 0xE0164: 84,
+ 0xE0165: 84,
+ 0xE0166: 84,
+ 0xE0167: 84,
+ 0xE0168: 84,
+ 0xE0169: 84,
+ 0xE016A: 84,
+ 0xE016B: 84,
+ 0xE016C: 84,
+ 0xE016D: 84,
+ 0xE016E: 84,
+ 0xE016F: 84,
+ 0xE0170: 84,
+ 0xE0171: 84,
+ 0xE0172: 84,
+ 0xE0173: 84,
+ 0xE0174: 84,
+ 0xE0175: 84,
+ 0xE0176: 84,
+ 0xE0177: 84,
+ 0xE0178: 84,
+ 0xE0179: 84,
+ 0xE017A: 84,
+ 0xE017B: 84,
+ 0xE017C: 84,
+ 0xE017D: 84,
+ 0xE017E: 84,
+ 0xE017F: 84,
+ 0xE0180: 84,
+ 0xE0181: 84,
+ 0xE0182: 84,
+ 0xE0183: 84,
+ 0xE0184: 84,
+ 0xE0185: 84,
+ 0xE0186: 84,
+ 0xE0187: 84,
+ 0xE0188: 84,
+ 0xE0189: 84,
+ 0xE018A: 84,
+ 0xE018B: 84,
+ 0xE018C: 84,
+ 0xE018D: 84,
+ 0xE018E: 84,
+ 0xE018F: 84,
+ 0xE0190: 84,
+ 0xE0191: 84,
+ 0xE0192: 84,
+ 0xE0193: 84,
+ 0xE0194: 84,
+ 0xE0195: 84,
+ 0xE0196: 84,
+ 0xE0197: 84,
+ 0xE0198: 84,
+ 0xE0199: 84,
+ 0xE019A: 84,
+ 0xE019B: 84,
+ 0xE019C: 84,
+ 0xE019D: 84,
+ 0xE019E: 84,
+ 0xE019F: 84,
+ 0xE01A0: 84,
+ 0xE01A1: 84,
+ 0xE01A2: 84,
+ 0xE01A3: 84,
+ 0xE01A4: 84,
+ 0xE01A5: 84,
+ 0xE01A6: 84,
+ 0xE01A7: 84,
+ 0xE01A8: 84,
+ 0xE01A9: 84,
+ 0xE01AA: 84,
+ 0xE01AB: 84,
+ 0xE01AC: 84,
+ 0xE01AD: 84,
+ 0xE01AE: 84,
+ 0xE01AF: 84,
+ 0xE01B0: 84,
+ 0xE01B1: 84,
+ 0xE01B2: 84,
+ 0xE01B3: 84,
+ 0xE01B4: 84,
+ 0xE01B5: 84,
+ 0xE01B6: 84,
+ 0xE01B7: 84,
+ 0xE01B8: 84,
+ 0xE01B9: 84,
+ 0xE01BA: 84,
+ 0xE01BB: 84,
+ 0xE01BC: 84,
+ 0xE01BD: 84,
+ 0xE01BE: 84,
+ 0xE01BF: 84,
+ 0xE01C0: 84,
+ 0xE01C1: 84,
+ 0xE01C2: 84,
+ 0xE01C3: 84,
+ 0xE01C4: 84,
+ 0xE01C5: 84,
+ 0xE01C6: 84,
+ 0xE01C7: 84,
+ 0xE01C8: 84,
+ 0xE01C9: 84,
+ 0xE01CA: 84,
+ 0xE01CB: 84,
+ 0xE01CC: 84,
+ 0xE01CD: 84,
+ 0xE01CE: 84,
+ 0xE01CF: 84,
+ 0xE01D0: 84,
+ 0xE01D1: 84,
+ 0xE01D2: 84,
+ 0xE01D3: 84,
+ 0xE01D4: 84,
+ 0xE01D5: 84,
+ 0xE01D6: 84,
+ 0xE01D7: 84,
+ 0xE01D8: 84,
+ 0xE01D9: 84,
+ 0xE01DA: 84,
+ 0xE01DB: 84,
+ 0xE01DC: 84,
+ 0xE01DD: 84,
+ 0xE01DE: 84,
+ 0xE01DF: 84,
+ 0xE01E0: 84,
+ 0xE01E1: 84,
+ 0xE01E2: 84,
+ 0xE01E3: 84,
+ 0xE01E4: 84,
+ 0xE01E5: 84,
+ 0xE01E6: 84,
+ 0xE01E7: 84,
+ 0xE01E8: 84,
+ 0xE01E9: 84,
+ 0xE01EA: 84,
+ 0xE01EB: 84,
+ 0xE01EC: 84,
+ 0xE01ED: 84,
+ 0xE01EE: 84,
+ 0xE01EF: 84,
+}
+codepoint_classes = {
+ "PVALID": (
+ 0x2D0000002E,
+ 0x300000003A,
+ 0x610000007B,
+ 0xDF000000F7,
+ 0xF800000100,
+ 0x10100000102,
+ 0x10300000104,
+ 0x10500000106,
+ 0x10700000108,
+ 0x1090000010A,
+ 0x10B0000010C,
+ 0x10D0000010E,
+ 0x10F00000110,
+ 0x11100000112,
+ 0x11300000114,
+ 0x11500000116,
+ 0x11700000118,
+ 0x1190000011A,
+ 0x11B0000011C,
+ 0x11D0000011E,
+ 0x11F00000120,
+ 0x12100000122,
+ 0x12300000124,
+ 0x12500000126,
+ 0x12700000128,
+ 0x1290000012A,
+ 0x12B0000012C,
+ 0x12D0000012E,
+ 0x12F00000130,
+ 0x13100000132,
+ 0x13500000136,
+ 0x13700000139,
+ 0x13A0000013B,
+ 0x13C0000013D,
+ 0x13E0000013F,
+ 0x14200000143,
+ 0x14400000145,
+ 0x14600000147,
+ 0x14800000149,
+ 0x14B0000014C,
+ 0x14D0000014E,
+ 0x14F00000150,
+ 0x15100000152,
+ 0x15300000154,
+ 0x15500000156,
+ 0x15700000158,
+ 0x1590000015A,
+ 0x15B0000015C,
+ 0x15D0000015E,
+ 0x15F00000160,
+ 0x16100000162,
+ 0x16300000164,
+ 0x16500000166,
+ 0x16700000168,
+ 0x1690000016A,
+ 0x16B0000016C,
+ 0x16D0000016E,
+ 0x16F00000170,
+ 0x17100000172,
+ 0x17300000174,
+ 0x17500000176,
+ 0x17700000178,
+ 0x17A0000017B,
+ 0x17C0000017D,
+ 0x17E0000017F,
+ 0x18000000181,
+ 0x18300000184,
+ 0x18500000186,
+ 0x18800000189,
+ 0x18C0000018E,
+ 0x19200000193,
+ 0x19500000196,
+ 0x1990000019C,
+ 0x19E0000019F,
+ 0x1A1000001A2,
+ 0x1A3000001A4,
+ 0x1A5000001A6,
+ 0x1A8000001A9,
+ 0x1AA000001AC,
+ 0x1AD000001AE,
+ 0x1B0000001B1,
+ 0x1B4000001B5,
+ 0x1B6000001B7,
+ 0x1B9000001BC,
+ 0x1BD000001C4,
+ 0x1CE000001CF,
+ 0x1D0000001D1,
+ 0x1D2000001D3,
+ 0x1D4000001D5,
+ 0x1D6000001D7,
+ 0x1D8000001D9,
+ 0x1DA000001DB,
+ 0x1DC000001DE,
+ 0x1DF000001E0,
+ 0x1E1000001E2,
+ 0x1E3000001E4,
+ 0x1E5000001E6,
+ 0x1E7000001E8,
+ 0x1E9000001EA,
+ 0x1EB000001EC,
+ 0x1ED000001EE,
+ 0x1EF000001F1,
+ 0x1F5000001F6,
+ 0x1F9000001FA,
+ 0x1FB000001FC,
+ 0x1FD000001FE,
+ 0x1FF00000200,
+ 0x20100000202,
+ 0x20300000204,
+ 0x20500000206,
+ 0x20700000208,
+ 0x2090000020A,
+ 0x20B0000020C,
+ 0x20D0000020E,
+ 0x20F00000210,
+ 0x21100000212,
+ 0x21300000214,
+ 0x21500000216,
+ 0x21700000218,
+ 0x2190000021A,
+ 0x21B0000021C,
+ 0x21D0000021E,
+ 0x21F00000220,
+ 0x22100000222,
+ 0x22300000224,
+ 0x22500000226,
+ 0x22700000228,
+ 0x2290000022A,
+ 0x22B0000022C,
+ 0x22D0000022E,
+ 0x22F00000230,
+ 0x23100000232,
+ 0x2330000023A,
+ 0x23C0000023D,
+ 0x23F00000241,
+ 0x24200000243,
+ 0x24700000248,
+ 0x2490000024A,
+ 0x24B0000024C,
+ 0x24D0000024E,
+ 0x24F000002B0,
+ 0x2B9000002C2,
+ 0x2C6000002D2,
+ 0x2EC000002ED,
+ 0x2EE000002EF,
+ 0x30000000340,
+ 0x34200000343,
+ 0x3460000034F,
+ 0x35000000370,
+ 0x37100000372,
+ 0x37300000374,
+ 0x37700000378,
+ 0x37B0000037E,
+ 0x39000000391,
+ 0x3AC000003CF,
+ 0x3D7000003D8,
+ 0x3D9000003DA,
+ 0x3DB000003DC,
+ 0x3DD000003DE,
+ 0x3DF000003E0,
+ 0x3E1000003E2,
+ 0x3E3000003E4,
+ 0x3E5000003E6,
+ 0x3E7000003E8,
+ 0x3E9000003EA,
+ 0x3EB000003EC,
+ 0x3ED000003EE,
+ 0x3EF000003F0,
+ 0x3F3000003F4,
+ 0x3F8000003F9,
+ 0x3FB000003FD,
+ 0x43000000460,
+ 0x46100000462,
+ 0x46300000464,
+ 0x46500000466,
+ 0x46700000468,
+ 0x4690000046A,
+ 0x46B0000046C,
+ 0x46D0000046E,
+ 0x46F00000470,
+ 0x47100000472,
+ 0x47300000474,
+ 0x47500000476,
+ 0x47700000478,
+ 0x4790000047A,
+ 0x47B0000047C,
+ 0x47D0000047E,
+ 0x47F00000480,
+ 0x48100000482,
+ 0x48300000488,
+ 0x48B0000048C,
+ 0x48D0000048E,
+ 0x48F00000490,
+ 0x49100000492,
+ 0x49300000494,
+ 0x49500000496,
+ 0x49700000498,
+ 0x4990000049A,
+ 0x49B0000049C,
+ 0x49D0000049E,
+ 0x49F000004A0,
+ 0x4A1000004A2,
+ 0x4A3000004A4,
+ 0x4A5000004A6,
+ 0x4A7000004A8,
+ 0x4A9000004AA,
+ 0x4AB000004AC,
+ 0x4AD000004AE,
+ 0x4AF000004B0,
+ 0x4B1000004B2,
+ 0x4B3000004B4,
+ 0x4B5000004B6,
+ 0x4B7000004B8,
+ 0x4B9000004BA,
+ 0x4BB000004BC,
+ 0x4BD000004BE,
+ 0x4BF000004C0,
+ 0x4C2000004C3,
+ 0x4C4000004C5,
+ 0x4C6000004C7,
+ 0x4C8000004C9,
+ 0x4CA000004CB,
+ 0x4CC000004CD,
+ 0x4CE000004D0,
+ 0x4D1000004D2,
+ 0x4D3000004D4,
+ 0x4D5000004D6,
+ 0x4D7000004D8,
+ 0x4D9000004DA,
+ 0x4DB000004DC,
+ 0x4DD000004DE,
+ 0x4DF000004E0,
+ 0x4E1000004E2,
+ 0x4E3000004E4,
+ 0x4E5000004E6,
+ 0x4E7000004E8,
+ 0x4E9000004EA,
+ 0x4EB000004EC,
+ 0x4ED000004EE,
+ 0x4EF000004F0,
+ 0x4F1000004F2,
+ 0x4F3000004F4,
+ 0x4F5000004F6,
+ 0x4F7000004F8,
+ 0x4F9000004FA,
+ 0x4FB000004FC,
+ 0x4FD000004FE,
+ 0x4FF00000500,
+ 0x50100000502,
+ 0x50300000504,
+ 0x50500000506,
+ 0x50700000508,
+ 0x5090000050A,
+ 0x50B0000050C,
+ 0x50D0000050E,
+ 0x50F00000510,
+ 0x51100000512,
+ 0x51300000514,
+ 0x51500000516,
+ 0x51700000518,
+ 0x5190000051A,
+ 0x51B0000051C,
+ 0x51D0000051E,
+ 0x51F00000520,
+ 0x52100000522,
+ 0x52300000524,
+ 0x52500000526,
+ 0x52700000528,
+ 0x5290000052A,
+ 0x52B0000052C,
+ 0x52D0000052E,
+ 0x52F00000530,
+ 0x5590000055A,
+ 0x56000000587,
+ 0x58800000589,
+ 0x591000005BE,
+ 0x5BF000005C0,
+ 0x5C1000005C3,
+ 0x5C4000005C6,
+ 0x5C7000005C8,
+ 0x5D0000005EB,
+ 0x5EF000005F3,
+ 0x6100000061B,
+ 0x62000000640,
+ 0x64100000660,
+ 0x66E00000675,
+ 0x679000006D4,
+ 0x6D5000006DD,
+ 0x6DF000006E9,
+ 0x6EA000006F0,
+ 0x6FA00000700,
+ 0x7100000074B,
+ 0x74D000007B2,
+ 0x7C0000007F6,
+ 0x7FD000007FE,
+ 0x8000000082E,
+ 0x8400000085C,
+ 0x8600000086B,
+ 0x87000000888,
+ 0x8890000088F,
+ 0x897000008E2,
+ 0x8E300000958,
+ 0x96000000964,
+ 0x96600000970,
+ 0x97100000984,
+ 0x9850000098D,
+ 0x98F00000991,
+ 0x993000009A9,
+ 0x9AA000009B1,
+ 0x9B2000009B3,
+ 0x9B6000009BA,
+ 0x9BC000009C5,
+ 0x9C7000009C9,
+ 0x9CB000009CF,
+ 0x9D7000009D8,
+ 0x9E0000009E4,
+ 0x9E6000009F2,
+ 0x9FC000009FD,
+ 0x9FE000009FF,
+ 0xA0100000A04,
+ 0xA0500000A0B,
+ 0xA0F00000A11,
+ 0xA1300000A29,
+ 0xA2A00000A31,
+ 0xA3200000A33,
+ 0xA3500000A36,
+ 0xA3800000A3A,
+ 0xA3C00000A3D,
+ 0xA3E00000A43,
+ 0xA4700000A49,
+ 0xA4B00000A4E,
+ 0xA5100000A52,
+ 0xA5C00000A5D,
+ 0xA6600000A76,
+ 0xA8100000A84,
+ 0xA8500000A8E,
+ 0xA8F00000A92,
+ 0xA9300000AA9,
+ 0xAAA00000AB1,
+ 0xAB200000AB4,
+ 0xAB500000ABA,
+ 0xABC00000AC6,
+ 0xAC700000ACA,
+ 0xACB00000ACE,
+ 0xAD000000AD1,
+ 0xAE000000AE4,
+ 0xAE600000AF0,
+ 0xAF900000B00,
+ 0xB0100000B04,
+ 0xB0500000B0D,
+ 0xB0F00000B11,
+ 0xB1300000B29,
+ 0xB2A00000B31,
+ 0xB3200000B34,
+ 0xB3500000B3A,
+ 0xB3C00000B45,
+ 0xB4700000B49,
+ 0xB4B00000B4E,
+ 0xB5500000B58,
+ 0xB5F00000B64,
+ 0xB6600000B70,
+ 0xB7100000B72,
+ 0xB8200000B84,
+ 0xB8500000B8B,
+ 0xB8E00000B91,
+ 0xB9200000B96,
+ 0xB9900000B9B,
+ 0xB9C00000B9D,
+ 0xB9E00000BA0,
+ 0xBA300000BA5,
+ 0xBA800000BAB,
+ 0xBAE00000BBA,
+ 0xBBE00000BC3,
+ 0xBC600000BC9,
+ 0xBCA00000BCE,
+ 0xBD000000BD1,
+ 0xBD700000BD8,
+ 0xBE600000BF0,
+ 0xC0000000C0D,
+ 0xC0E00000C11,
+ 0xC1200000C29,
+ 0xC2A00000C3A,
+ 0xC3C00000C45,
+ 0xC4600000C49,
+ 0xC4A00000C4E,
+ 0xC5500000C57,
+ 0xC5800000C5B,
+ 0xC5D00000C5E,
+ 0xC6000000C64,
+ 0xC6600000C70,
+ 0xC8000000C84,
+ 0xC8500000C8D,
+ 0xC8E00000C91,
+ 0xC9200000CA9,
+ 0xCAA00000CB4,
+ 0xCB500000CBA,
+ 0xCBC00000CC5,
+ 0xCC600000CC9,
+ 0xCCA00000CCE,
+ 0xCD500000CD7,
+ 0xCDD00000CDF,
+ 0xCE000000CE4,
+ 0xCE600000CF0,
+ 0xCF100000CF4,
+ 0xD0000000D0D,
+ 0xD0E00000D11,
+ 0xD1200000D45,
+ 0xD4600000D49,
+ 0xD4A00000D4F,
+ 0xD5400000D58,
+ 0xD5F00000D64,
+ 0xD6600000D70,
+ 0xD7A00000D80,
+ 0xD8100000D84,
+ 0xD8500000D97,
+ 0xD9A00000DB2,
+ 0xDB300000DBC,
+ 0xDBD00000DBE,
+ 0xDC000000DC7,
+ 0xDCA00000DCB,
+ 0xDCF00000DD5,
+ 0xDD600000DD7,
+ 0xDD800000DE0,
+ 0xDE600000DF0,
+ 0xDF200000DF4,
+ 0xE0100000E33,
+ 0xE3400000E3B,
+ 0xE4000000E4F,
+ 0xE5000000E5A,
+ 0xE8100000E83,
+ 0xE8400000E85,
+ 0xE8600000E8B,
+ 0xE8C00000EA4,
+ 0xEA500000EA6,
+ 0xEA700000EB3,
+ 0xEB400000EBE,
+ 0xEC000000EC5,
+ 0xEC600000EC7,
+ 0xEC800000ECF,
+ 0xED000000EDA,
+ 0xEDE00000EE0,
+ 0xF0000000F01,
+ 0xF0B00000F0C,
+ 0xF1800000F1A,
+ 0xF2000000F2A,
+ 0xF3500000F36,
+ 0xF3700000F38,
+ 0xF3900000F3A,
+ 0xF3E00000F43,
+ 0xF4400000F48,
+ 0xF4900000F4D,
+ 0xF4E00000F52,
+ 0xF5300000F57,
+ 0xF5800000F5C,
+ 0xF5D00000F69,
+ 0xF6A00000F6D,
+ 0xF7100000F73,
+ 0xF7400000F75,
+ 0xF7A00000F81,
+ 0xF8200000F85,
+ 0xF8600000F93,
+ 0xF9400000F98,
+ 0xF9900000F9D,
+ 0xF9E00000FA2,
+ 0xFA300000FA7,
+ 0xFA800000FAC,
+ 0xFAD00000FB9,
+ 0xFBA00000FBD,
+ 0xFC600000FC7,
+ 0x10000000104A,
+ 0x10500000109E,
+ 0x10D0000010FB,
+ 0x10FD00001100,
+ 0x120000001249,
+ 0x124A0000124E,
+ 0x125000001257,
+ 0x125800001259,
+ 0x125A0000125E,
+ 0x126000001289,
+ 0x128A0000128E,
+ 0x1290000012B1,
+ 0x12B2000012B6,
+ 0x12B8000012BF,
+ 0x12C0000012C1,
+ 0x12C2000012C6,
+ 0x12C8000012D7,
+ 0x12D800001311,
+ 0x131200001316,
+ 0x13180000135B,
+ 0x135D00001360,
+ 0x138000001390,
+ 0x13A0000013F6,
+ 0x14010000166D,
+ 0x166F00001680,
+ 0x16810000169B,
+ 0x16A0000016EB,
+ 0x16F1000016F9,
+ 0x170000001716,
+ 0x171F00001735,
+ 0x174000001754,
+ 0x17600000176D,
+ 0x176E00001771,
+ 0x177200001774,
+ 0x1780000017B4,
+ 0x17B6000017D4,
+ 0x17D7000017D8,
+ 0x17DC000017DE,
+ 0x17E0000017EA,
+ 0x18100000181A,
+ 0x182000001879,
+ 0x1880000018AB,
+ 0x18B0000018F6,
+ 0x19000000191F,
+ 0x19200000192C,
+ 0x19300000193C,
+ 0x19460000196E,
+ 0x197000001975,
+ 0x1980000019AC,
+ 0x19B0000019CA,
+ 0x19D0000019DA,
+ 0x1A0000001A1C,
+ 0x1A2000001A5F,
+ 0x1A6000001A7D,
+ 0x1A7F00001A8A,
+ 0x1A9000001A9A,
+ 0x1AA700001AA8,
+ 0x1AB000001ABE,
+ 0x1ABF00001ACF,
+ 0x1B0000001B4D,
+ 0x1B5000001B5A,
+ 0x1B6B00001B74,
+ 0x1B8000001BF4,
+ 0x1C0000001C38,
+ 0x1C4000001C4A,
+ 0x1C4D00001C7E,
+ 0x1C8A00001C8B,
+ 0x1CD000001CD3,
+ 0x1CD400001CFB,
+ 0x1D0000001D2C,
+ 0x1D2F00001D30,
+ 0x1D3B00001D3C,
+ 0x1D4E00001D4F,
+ 0x1D6B00001D78,
+ 0x1D7900001D9B,
+ 0x1DC000001E00,
+ 0x1E0100001E02,
+ 0x1E0300001E04,
+ 0x1E0500001E06,
+ 0x1E0700001E08,
+ 0x1E0900001E0A,
+ 0x1E0B00001E0C,
+ 0x1E0D00001E0E,
+ 0x1E0F00001E10,
+ 0x1E1100001E12,
+ 0x1E1300001E14,
+ 0x1E1500001E16,
+ 0x1E1700001E18,
+ 0x1E1900001E1A,
+ 0x1E1B00001E1C,
+ 0x1E1D00001E1E,
+ 0x1E1F00001E20,
+ 0x1E2100001E22,
+ 0x1E2300001E24,
+ 0x1E2500001E26,
+ 0x1E2700001E28,
+ 0x1E2900001E2A,
+ 0x1E2B00001E2C,
+ 0x1E2D00001E2E,
+ 0x1E2F00001E30,
+ 0x1E3100001E32,
+ 0x1E3300001E34,
+ 0x1E3500001E36,
+ 0x1E3700001E38,
+ 0x1E3900001E3A,
+ 0x1E3B00001E3C,
+ 0x1E3D00001E3E,
+ 0x1E3F00001E40,
+ 0x1E4100001E42,
+ 0x1E4300001E44,
+ 0x1E4500001E46,
+ 0x1E4700001E48,
+ 0x1E4900001E4A,
+ 0x1E4B00001E4C,
+ 0x1E4D00001E4E,
+ 0x1E4F00001E50,
+ 0x1E5100001E52,
+ 0x1E5300001E54,
+ 0x1E5500001E56,
+ 0x1E5700001E58,
+ 0x1E5900001E5A,
+ 0x1E5B00001E5C,
+ 0x1E5D00001E5E,
+ 0x1E5F00001E60,
+ 0x1E6100001E62,
+ 0x1E6300001E64,
+ 0x1E6500001E66,
+ 0x1E6700001E68,
+ 0x1E6900001E6A,
+ 0x1E6B00001E6C,
+ 0x1E6D00001E6E,
+ 0x1E6F00001E70,
+ 0x1E7100001E72,
+ 0x1E7300001E74,
+ 0x1E7500001E76,
+ 0x1E7700001E78,
+ 0x1E7900001E7A,
+ 0x1E7B00001E7C,
+ 0x1E7D00001E7E,
+ 0x1E7F00001E80,
+ 0x1E8100001E82,
+ 0x1E8300001E84,
+ 0x1E8500001E86,
+ 0x1E8700001E88,
+ 0x1E8900001E8A,
+ 0x1E8B00001E8C,
+ 0x1E8D00001E8E,
+ 0x1E8F00001E90,
+ 0x1E9100001E92,
+ 0x1E9300001E94,
+ 0x1E9500001E9A,
+ 0x1E9C00001E9E,
+ 0x1E9F00001EA0,
+ 0x1EA100001EA2,
+ 0x1EA300001EA4,
+ 0x1EA500001EA6,
+ 0x1EA700001EA8,
+ 0x1EA900001EAA,
+ 0x1EAB00001EAC,
+ 0x1EAD00001EAE,
+ 0x1EAF00001EB0,
+ 0x1EB100001EB2,
+ 0x1EB300001EB4,
+ 0x1EB500001EB6,
+ 0x1EB700001EB8,
+ 0x1EB900001EBA,
+ 0x1EBB00001EBC,
+ 0x1EBD00001EBE,
+ 0x1EBF00001EC0,
+ 0x1EC100001EC2,
+ 0x1EC300001EC4,
+ 0x1EC500001EC6,
+ 0x1EC700001EC8,
+ 0x1EC900001ECA,
+ 0x1ECB00001ECC,
+ 0x1ECD00001ECE,
+ 0x1ECF00001ED0,
+ 0x1ED100001ED2,
+ 0x1ED300001ED4,
+ 0x1ED500001ED6,
+ 0x1ED700001ED8,
+ 0x1ED900001EDA,
+ 0x1EDB00001EDC,
+ 0x1EDD00001EDE,
+ 0x1EDF00001EE0,
+ 0x1EE100001EE2,
+ 0x1EE300001EE4,
+ 0x1EE500001EE6,
+ 0x1EE700001EE8,
+ 0x1EE900001EEA,
+ 0x1EEB00001EEC,
+ 0x1EED00001EEE,
+ 0x1EEF00001EF0,
+ 0x1EF100001EF2,
+ 0x1EF300001EF4,
+ 0x1EF500001EF6,
+ 0x1EF700001EF8,
+ 0x1EF900001EFA,
+ 0x1EFB00001EFC,
+ 0x1EFD00001EFE,
+ 0x1EFF00001F08,
+ 0x1F1000001F16,
+ 0x1F2000001F28,
+ 0x1F3000001F38,
+ 0x1F4000001F46,
+ 0x1F5000001F58,
+ 0x1F6000001F68,
+ 0x1F7000001F71,
+ 0x1F7200001F73,
+ 0x1F7400001F75,
+ 0x1F7600001F77,
+ 0x1F7800001F79,
+ 0x1F7A00001F7B,
+ 0x1F7C00001F7D,
+ 0x1FB000001FB2,
+ 0x1FB600001FB7,
+ 0x1FC600001FC7,
+ 0x1FD000001FD3,
+ 0x1FD600001FD8,
+ 0x1FE000001FE3,
+ 0x1FE400001FE8,
+ 0x1FF600001FF7,
+ 0x214E0000214F,
+ 0x218400002185,
+ 0x2C3000002C60,
+ 0x2C6100002C62,
+ 0x2C6500002C67,
+ 0x2C6800002C69,
+ 0x2C6A00002C6B,
+ 0x2C6C00002C6D,
+ 0x2C7100002C72,
+ 0x2C7300002C75,
+ 0x2C7600002C7C,
+ 0x2C8100002C82,
+ 0x2C8300002C84,
+ 0x2C8500002C86,
+ 0x2C8700002C88,
+ 0x2C8900002C8A,
+ 0x2C8B00002C8C,
+ 0x2C8D00002C8E,
+ 0x2C8F00002C90,
+ 0x2C9100002C92,
+ 0x2C9300002C94,
+ 0x2C9500002C96,
+ 0x2C9700002C98,
+ 0x2C9900002C9A,
+ 0x2C9B00002C9C,
+ 0x2C9D00002C9E,
+ 0x2C9F00002CA0,
+ 0x2CA100002CA2,
+ 0x2CA300002CA4,
+ 0x2CA500002CA6,
+ 0x2CA700002CA8,
+ 0x2CA900002CAA,
+ 0x2CAB00002CAC,
+ 0x2CAD00002CAE,
+ 0x2CAF00002CB0,
+ 0x2CB100002CB2,
+ 0x2CB300002CB4,
+ 0x2CB500002CB6,
+ 0x2CB700002CB8,
+ 0x2CB900002CBA,
+ 0x2CBB00002CBC,
+ 0x2CBD00002CBE,
+ 0x2CBF00002CC0,
+ 0x2CC100002CC2,
+ 0x2CC300002CC4,
+ 0x2CC500002CC6,
+ 0x2CC700002CC8,
+ 0x2CC900002CCA,
+ 0x2CCB00002CCC,
+ 0x2CCD00002CCE,
+ 0x2CCF00002CD0,
+ 0x2CD100002CD2,
+ 0x2CD300002CD4,
+ 0x2CD500002CD6,
+ 0x2CD700002CD8,
+ 0x2CD900002CDA,
+ 0x2CDB00002CDC,
+ 0x2CDD00002CDE,
+ 0x2CDF00002CE0,
+ 0x2CE100002CE2,
+ 0x2CE300002CE5,
+ 0x2CEC00002CED,
+ 0x2CEE00002CF2,
+ 0x2CF300002CF4,
+ 0x2D0000002D26,
+ 0x2D2700002D28,
+ 0x2D2D00002D2E,
+ 0x2D3000002D68,
+ 0x2D7F00002D97,
+ 0x2DA000002DA7,
+ 0x2DA800002DAF,
+ 0x2DB000002DB7,
+ 0x2DB800002DBF,
+ 0x2DC000002DC7,
+ 0x2DC800002DCF,
+ 0x2DD000002DD7,
+ 0x2DD800002DDF,
+ 0x2DE000002E00,
+ 0x2E2F00002E30,
+ 0x300500003008,
+ 0x302A0000302E,
+ 0x303C0000303D,
+ 0x304100003097,
+ 0x30990000309B,
+ 0x309D0000309F,
+ 0x30A1000030FB,
+ 0x30FC000030FF,
+ 0x310500003130,
+ 0x31A0000031C0,
+ 0x31F000003200,
+ 0x340000004DC0,
+ 0x4E000000A48D,
+ 0xA4D00000A4FE,
+ 0xA5000000A60D,
+ 0xA6100000A62C,
+ 0xA6410000A642,
+ 0xA6430000A644,
+ 0xA6450000A646,
+ 0xA6470000A648,
+ 0xA6490000A64A,
+ 0xA64B0000A64C,
+ 0xA64D0000A64E,
+ 0xA64F0000A650,
+ 0xA6510000A652,
+ 0xA6530000A654,
+ 0xA6550000A656,
+ 0xA6570000A658,
+ 0xA6590000A65A,
+ 0xA65B0000A65C,
+ 0xA65D0000A65E,
+ 0xA65F0000A660,
+ 0xA6610000A662,
+ 0xA6630000A664,
+ 0xA6650000A666,
+ 0xA6670000A668,
+ 0xA6690000A66A,
+ 0xA66B0000A66C,
+ 0xA66D0000A670,
+ 0xA6740000A67E,
+ 0xA67F0000A680,
+ 0xA6810000A682,
+ 0xA6830000A684,
+ 0xA6850000A686,
+ 0xA6870000A688,
+ 0xA6890000A68A,
+ 0xA68B0000A68C,
+ 0xA68D0000A68E,
+ 0xA68F0000A690,
+ 0xA6910000A692,
+ 0xA6930000A694,
+ 0xA6950000A696,
+ 0xA6970000A698,
+ 0xA6990000A69A,
+ 0xA69B0000A69C,
+ 0xA69E0000A6E6,
+ 0xA6F00000A6F2,
+ 0xA7170000A720,
+ 0xA7230000A724,
+ 0xA7250000A726,
+ 0xA7270000A728,
+ 0xA7290000A72A,
+ 0xA72B0000A72C,
+ 0xA72D0000A72E,
+ 0xA72F0000A732,
+ 0xA7330000A734,
+ 0xA7350000A736,
+ 0xA7370000A738,
+ 0xA7390000A73A,
+ 0xA73B0000A73C,
+ 0xA73D0000A73E,
+ 0xA73F0000A740,
+ 0xA7410000A742,
+ 0xA7430000A744,
+ 0xA7450000A746,
+ 0xA7470000A748,
+ 0xA7490000A74A,
+ 0xA74B0000A74C,
+ 0xA74D0000A74E,
+ 0xA74F0000A750,
+ 0xA7510000A752,
+ 0xA7530000A754,
+ 0xA7550000A756,
+ 0xA7570000A758,
+ 0xA7590000A75A,
+ 0xA75B0000A75C,
+ 0xA75D0000A75E,
+ 0xA75F0000A760,
+ 0xA7610000A762,
+ 0xA7630000A764,
+ 0xA7650000A766,
+ 0xA7670000A768,
+ 0xA7690000A76A,
+ 0xA76B0000A76C,
+ 0xA76D0000A76E,
+ 0xA76F0000A770,
+ 0xA7710000A779,
+ 0xA77A0000A77B,
+ 0xA77C0000A77D,
+ 0xA77F0000A780,
+ 0xA7810000A782,
+ 0xA7830000A784,
+ 0xA7850000A786,
+ 0xA7870000A789,
+ 0xA78C0000A78D,
+ 0xA78E0000A790,
+ 0xA7910000A792,
+ 0xA7930000A796,
+ 0xA7970000A798,
+ 0xA7990000A79A,
+ 0xA79B0000A79C,
+ 0xA79D0000A79E,
+ 0xA79F0000A7A0,
+ 0xA7A10000A7A2,
+ 0xA7A30000A7A4,
+ 0xA7A50000A7A6,
+ 0xA7A70000A7A8,
+ 0xA7A90000A7AA,
+ 0xA7AF0000A7B0,
+ 0xA7B50000A7B6,
+ 0xA7B70000A7B8,
+ 0xA7B90000A7BA,
+ 0xA7BB0000A7BC,
+ 0xA7BD0000A7BE,
+ 0xA7BF0000A7C0,
+ 0xA7C10000A7C2,
+ 0xA7C30000A7C4,
+ 0xA7C80000A7C9,
+ 0xA7CA0000A7CB,
+ 0xA7CD0000A7CE,
+ 0xA7D10000A7D2,
+ 0xA7D30000A7D4,
+ 0xA7D50000A7D6,
+ 0xA7D70000A7D8,
+ 0xA7D90000A7DA,
+ 0xA7DB0000A7DC,
+ 0xA7F60000A7F8,
+ 0xA7FA0000A828,
+ 0xA82C0000A82D,
+ 0xA8400000A874,
+ 0xA8800000A8C6,
+ 0xA8D00000A8DA,
+ 0xA8E00000A8F8,
+ 0xA8FB0000A8FC,
+ 0xA8FD0000A92E,
+ 0xA9300000A954,
+ 0xA9800000A9C1,
+ 0xA9CF0000A9DA,
+ 0xA9E00000A9FF,
+ 0xAA000000AA37,
+ 0xAA400000AA4E,
+ 0xAA500000AA5A,
+ 0xAA600000AA77,
+ 0xAA7A0000AAC3,
+ 0xAADB0000AADE,
+ 0xAAE00000AAF0,
+ 0xAAF20000AAF7,
+ 0xAB010000AB07,
+ 0xAB090000AB0F,
+ 0xAB110000AB17,
+ 0xAB200000AB27,
+ 0xAB280000AB2F,
+ 0xAB300000AB5B,
+ 0xAB600000AB69,
+ 0xABC00000ABEB,
+ 0xABEC0000ABEE,
+ 0xABF00000ABFA,
+ 0xAC000000D7A4,
+ 0xFA0E0000FA10,
+ 0xFA110000FA12,
+ 0xFA130000FA15,
+ 0xFA1F0000FA20,
+ 0xFA210000FA22,
+ 0xFA230000FA25,
+ 0xFA270000FA2A,
+ 0xFB1E0000FB1F,
+ 0xFE200000FE30,
+ 0xFE730000FE74,
+ 0x100000001000C,
+ 0x1000D00010027,
+ 0x100280001003B,
+ 0x1003C0001003E,
+ 0x1003F0001004E,
+ 0x100500001005E,
+ 0x10080000100FB,
+ 0x101FD000101FE,
+ 0x102800001029D,
+ 0x102A0000102D1,
+ 0x102E0000102E1,
+ 0x1030000010320,
+ 0x1032D00010341,
+ 0x103420001034A,
+ 0x103500001037B,
+ 0x103800001039E,
+ 0x103A0000103C4,
+ 0x103C8000103D0,
+ 0x104280001049E,
+ 0x104A0000104AA,
+ 0x104D8000104FC,
+ 0x1050000010528,
+ 0x1053000010564,
+ 0x10597000105A2,
+ 0x105A3000105B2,
+ 0x105B3000105BA,
+ 0x105BB000105BD,
+ 0x105C0000105F4,
+ 0x1060000010737,
+ 0x1074000010756,
+ 0x1076000010768,
+ 0x1078000010781,
+ 0x1080000010806,
+ 0x1080800010809,
+ 0x1080A00010836,
+ 0x1083700010839,
+ 0x1083C0001083D,
+ 0x1083F00010856,
+ 0x1086000010877,
+ 0x108800001089F,
+ 0x108E0000108F3,
+ 0x108F4000108F6,
+ 0x1090000010916,
+ 0x109200001093A,
+ 0x10980000109B8,
+ 0x109BE000109C0,
+ 0x10A0000010A04,
+ 0x10A0500010A07,
+ 0x10A0C00010A14,
+ 0x10A1500010A18,
+ 0x10A1900010A36,
+ 0x10A3800010A3B,
+ 0x10A3F00010A40,
+ 0x10A6000010A7D,
+ 0x10A8000010A9D,
+ 0x10AC000010AC8,
+ 0x10AC900010AE7,
+ 0x10B0000010B36,
+ 0x10B4000010B56,
+ 0x10B6000010B73,
+ 0x10B8000010B92,
+ 0x10C0000010C49,
+ 0x10CC000010CF3,
+ 0x10D0000010D28,
+ 0x10D3000010D3A,
+ 0x10D4000010D50,
+ 0x10D6900010D6E,
+ 0x10D6F00010D86,
+ 0x10E8000010EAA,
+ 0x10EAB00010EAD,
+ 0x10EB000010EB2,
+ 0x10EC200010EC5,
+ 0x10EFC00010F1D,
+ 0x10F2700010F28,
+ 0x10F3000010F51,
+ 0x10F7000010F86,
+ 0x10FB000010FC5,
+ 0x10FE000010FF7,
+ 0x1100000011047,
+ 0x1106600011076,
+ 0x1107F000110BB,
+ 0x110C2000110C3,
+ 0x110D0000110E9,
+ 0x110F0000110FA,
+ 0x1110000011135,
+ 0x1113600011140,
+ 0x1114400011148,
+ 0x1115000011174,
+ 0x1117600011177,
+ 0x11180000111C5,
+ 0x111C9000111CD,
+ 0x111CE000111DB,
+ 0x111DC000111DD,
+ 0x1120000011212,
+ 0x1121300011238,
+ 0x1123E00011242,
+ 0x1128000011287,
+ 0x1128800011289,
+ 0x1128A0001128E,
+ 0x1128F0001129E,
+ 0x1129F000112A9,
+ 0x112B0000112EB,
+ 0x112F0000112FA,
+ 0x1130000011304,
+ 0x113050001130D,
+ 0x1130F00011311,
+ 0x1131300011329,
+ 0x1132A00011331,
+ 0x1133200011334,
+ 0x113350001133A,
+ 0x1133B00011345,
+ 0x1134700011349,
+ 0x1134B0001134E,
+ 0x1135000011351,
+ 0x1135700011358,
+ 0x1135D00011364,
+ 0x113660001136D,
+ 0x1137000011375,
+ 0x113800001138A,
+ 0x1138B0001138C,
+ 0x1138E0001138F,
+ 0x11390000113B6,
+ 0x113B7000113C1,
+ 0x113C2000113C3,
+ 0x113C5000113C6,
+ 0x113C7000113CB,
+ 0x113CC000113D4,
+ 0x113E1000113E3,
+ 0x114000001144B,
+ 0x114500001145A,
+ 0x1145E00011462,
+ 0x11480000114C6,
+ 0x114C7000114C8,
+ 0x114D0000114DA,
+ 0x11580000115B6,
+ 0x115B8000115C1,
+ 0x115D8000115DE,
+ 0x1160000011641,
+ 0x1164400011645,
+ 0x116500001165A,
+ 0x11680000116B9,
+ 0x116C0000116CA,
+ 0x116D0000116E4,
+ 0x117000001171B,
+ 0x1171D0001172C,
+ 0x117300001173A,
+ 0x1174000011747,
+ 0x118000001183B,
+ 0x118C0000118EA,
+ 0x118FF00011907,
+ 0x119090001190A,
+ 0x1190C00011914,
+ 0x1191500011917,
+ 0x1191800011936,
+ 0x1193700011939,
+ 0x1193B00011944,
+ 0x119500001195A,
+ 0x119A0000119A8,
+ 0x119AA000119D8,
+ 0x119DA000119E2,
+ 0x119E3000119E5,
+ 0x11A0000011A3F,
+ 0x11A4700011A48,
+ 0x11A5000011A9A,
+ 0x11A9D00011A9E,
+ 0x11AB000011AF9,
+ 0x11BC000011BE1,
+ 0x11BF000011BFA,
+ 0x11C0000011C09,
+ 0x11C0A00011C37,
+ 0x11C3800011C41,
+ 0x11C5000011C5A,
+ 0x11C7200011C90,
+ 0x11C9200011CA8,
+ 0x11CA900011CB7,
+ 0x11D0000011D07,
+ 0x11D0800011D0A,
+ 0x11D0B00011D37,
+ 0x11D3A00011D3B,
+ 0x11D3C00011D3E,
+ 0x11D3F00011D48,
+ 0x11D5000011D5A,
+ 0x11D6000011D66,
+ 0x11D6700011D69,
+ 0x11D6A00011D8F,
+ 0x11D9000011D92,
+ 0x11D9300011D99,
+ 0x11DA000011DAA,
+ 0x11EE000011EF7,
+ 0x11F0000011F11,
+ 0x11F1200011F3B,
+ 0x11F3E00011F43,
+ 0x11F5000011F5B,
+ 0x11FB000011FB1,
+ 0x120000001239A,
+ 0x1248000012544,
+ 0x12F9000012FF1,
+ 0x1300000013430,
+ 0x1344000013456,
+ 0x13460000143FB,
+ 0x1440000014647,
+ 0x161000001613A,
+ 0x1680000016A39,
+ 0x16A4000016A5F,
+ 0x16A6000016A6A,
+ 0x16A7000016ABF,
+ 0x16AC000016ACA,
+ 0x16AD000016AEE,
+ 0x16AF000016AF5,
+ 0x16B0000016B37,
+ 0x16B4000016B44,
+ 0x16B5000016B5A,
+ 0x16B6300016B78,
+ 0x16B7D00016B90,
+ 0x16D4000016D6D,
+ 0x16D7000016D7A,
+ 0x16E6000016E80,
+ 0x16F0000016F4B,
+ 0x16F4F00016F88,
+ 0x16F8F00016FA0,
+ 0x16FE000016FE2,
+ 0x16FE300016FE5,
+ 0x16FF000016FF2,
+ 0x17000000187F8,
+ 0x1880000018CD6,
+ 0x18CFF00018D09,
+ 0x1AFF00001AFF4,
+ 0x1AFF50001AFFC,
+ 0x1AFFD0001AFFF,
+ 0x1B0000001B123,
+ 0x1B1320001B133,
+ 0x1B1500001B153,
+ 0x1B1550001B156,
+ 0x1B1640001B168,
+ 0x1B1700001B2FC,
+ 0x1BC000001BC6B,
+ 0x1BC700001BC7D,
+ 0x1BC800001BC89,
+ 0x1BC900001BC9A,
+ 0x1BC9D0001BC9F,
+ 0x1CCF00001CCFA,
+ 0x1CF000001CF2E,
+ 0x1CF300001CF47,
+ 0x1DA000001DA37,
+ 0x1DA3B0001DA6D,
+ 0x1DA750001DA76,
+ 0x1DA840001DA85,
+ 0x1DA9B0001DAA0,
+ 0x1DAA10001DAB0,
+ 0x1DF000001DF1F,
+ 0x1DF250001DF2B,
+ 0x1E0000001E007,
+ 0x1E0080001E019,
+ 0x1E01B0001E022,
+ 0x1E0230001E025,
+ 0x1E0260001E02B,
+ 0x1E08F0001E090,
+ 0x1E1000001E12D,
+ 0x1E1300001E13E,
+ 0x1E1400001E14A,
+ 0x1E14E0001E14F,
+ 0x1E2900001E2AF,
+ 0x1E2C00001E2FA,
+ 0x1E4D00001E4FA,
+ 0x1E5D00001E5FB,
+ 0x1E7E00001E7E7,
+ 0x1E7E80001E7EC,
+ 0x1E7ED0001E7EF,
+ 0x1E7F00001E7FF,
+ 0x1E8000001E8C5,
+ 0x1E8D00001E8D7,
+ 0x1E9220001E94C,
+ 0x1E9500001E95A,
+ 0x200000002A6E0,
+ 0x2A7000002B73A,
+ 0x2B7400002B81E,
+ 0x2B8200002CEA2,
+ 0x2CEB00002EBE1,
+ 0x2EBF00002EE5E,
+ 0x300000003134B,
+ 0x31350000323B0,
+ ),
+ "CONTEXTJ": (0x200C0000200E,),
+ "CONTEXTO": (
+ 0xB7000000B8,
+ 0x37500000376,
+ 0x5F3000005F5,
+ 0x6600000066A,
+ 0x6F0000006FA,
+ 0x30FB000030FC,
+ ),
+}
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/intranges.py b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/intranges.py
new file mode 100644
index 0000000000000000000000000000000000000000..7bfaa8d80d7dc471d572db0f949460901126e8bd
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/intranges.py
@@ -0,0 +1,57 @@
+"""
+Given a list of integers, made up of (hopefully) a small number of long runs
+of consecutive integers, compute a representation of the form
+((start1, end1), (start2, end2) ...). Then answer the question "was x present
+in the original list?" in time O(log(# runs)).
+"""
+
+import bisect
+from typing import List, Tuple
+
+
+def intranges_from_list(list_: List[int]) -> Tuple[int, ...]:
+ """Represent a list of integers as a sequence of ranges:
+ ((start_0, end_0), (start_1, end_1), ...), such that the original
+ integers are exactly those x such that start_i <= x < end_i for some i.
+
+ Ranges are encoded as single integers (start << 32 | end), not as tuples.
+ """
+
+ sorted_list = sorted(list_)
+ ranges = []
+ last_write = -1
+ for i in range(len(sorted_list)):
+ if i + 1 < len(sorted_list):
+ if sorted_list[i] == sorted_list[i + 1] - 1:
+ continue
+ current_range = sorted_list[last_write + 1 : i + 1]
+ ranges.append(_encode_range(current_range[0], current_range[-1] + 1))
+ last_write = i
+
+ return tuple(ranges)
+
+
+def _encode_range(start: int, end: int) -> int:
+ return (start << 32) | end
+
+
+def _decode_range(r: int) -> Tuple[int, int]:
+ return (r >> 32), (r & ((1 << 32) - 1))
+
+
+def intranges_contain(int_: int, ranges: Tuple[int, ...]) -> bool:
+ """Determine if `int_` falls into one of the ranges in `ranges`."""
+ tuple_ = _encode_range(int_, 0)
+ pos = bisect.bisect_left(ranges, tuple_)
+ # we could be immediately ahead of a tuple (start, end)
+ # with start < int_ <= end
+ if pos > 0:
+ left, right = _decode_range(ranges[pos - 1])
+ if left <= int_ < right:
+ return True
+ # or we could be immediately behind a tuple (int_, end)
+ if pos < len(ranges):
+ left, _ = _decode_range(ranges[pos])
+ if left == int_:
+ return True
+ return False
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/package_data.py b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/package_data.py
new file mode 100644
index 0000000000000000000000000000000000000000..7272c8d92364886c51fefd22837ed5ceab145606
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/package_data.py
@@ -0,0 +1 @@
+__version__ = "3.11"
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/py.typed b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/py.typed
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/uts46data.py b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/uts46data.py
new file mode 100644
index 0000000000000000000000000000000000000000..4610b71dad9196838d4e1e04e76d5e7c9baf8cd9
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/idna/uts46data.py
@@ -0,0 +1,8841 @@
+# This file is automatically generated by tools/idna-data
+# vim: set fileencoding=utf-8 :
+
+from typing import List, Tuple, Union
+
+"""IDNA Mapping Table from UTS46."""
+
+
+__version__ = "16.0.0"
+
+
+def _seg_0() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x0, "V"),
+ (0x1, "V"),
+ (0x2, "V"),
+ (0x3, "V"),
+ (0x4, "V"),
+ (0x5, "V"),
+ (0x6, "V"),
+ (0x7, "V"),
+ (0x8, "V"),
+ (0x9, "V"),
+ (0xA, "V"),
+ (0xB, "V"),
+ (0xC, "V"),
+ (0xD, "V"),
+ (0xE, "V"),
+ (0xF, "V"),
+ (0x10, "V"),
+ (0x11, "V"),
+ (0x12, "V"),
+ (0x13, "V"),
+ (0x14, "V"),
+ (0x15, "V"),
+ (0x16, "V"),
+ (0x17, "V"),
+ (0x18, "V"),
+ (0x19, "V"),
+ (0x1A, "V"),
+ (0x1B, "V"),
+ (0x1C, "V"),
+ (0x1D, "V"),
+ (0x1E, "V"),
+ (0x1F, "V"),
+ (0x20, "V"),
+ (0x21, "V"),
+ (0x22, "V"),
+ (0x23, "V"),
+ (0x24, "V"),
+ (0x25, "V"),
+ (0x26, "V"),
+ (0x27, "V"),
+ (0x28, "V"),
+ (0x29, "V"),
+ (0x2A, "V"),
+ (0x2B, "V"),
+ (0x2C, "V"),
+ (0x2D, "V"),
+ (0x2E, "V"),
+ (0x2F, "V"),
+ (0x30, "V"),
+ (0x31, "V"),
+ (0x32, "V"),
+ (0x33, "V"),
+ (0x34, "V"),
+ (0x35, "V"),
+ (0x36, "V"),
+ (0x37, "V"),
+ (0x38, "V"),
+ (0x39, "V"),
+ (0x3A, "V"),
+ (0x3B, "V"),
+ (0x3C, "V"),
+ (0x3D, "V"),
+ (0x3E, "V"),
+ (0x3F, "V"),
+ (0x40, "V"),
+ (0x41, "M", "a"),
+ (0x42, "M", "b"),
+ (0x43, "M", "c"),
+ (0x44, "M", "d"),
+ (0x45, "M", "e"),
+ (0x46, "M", "f"),
+ (0x47, "M", "g"),
+ (0x48, "M", "h"),
+ (0x49, "M", "i"),
+ (0x4A, "M", "j"),
+ (0x4B, "M", "k"),
+ (0x4C, "M", "l"),
+ (0x4D, "M", "m"),
+ (0x4E, "M", "n"),
+ (0x4F, "M", "o"),
+ (0x50, "M", "p"),
+ (0x51, "M", "q"),
+ (0x52, "M", "r"),
+ (0x53, "M", "s"),
+ (0x54, "M", "t"),
+ (0x55, "M", "u"),
+ (0x56, "M", "v"),
+ (0x57, "M", "w"),
+ (0x58, "M", "x"),
+ (0x59, "M", "y"),
+ (0x5A, "M", "z"),
+ (0x5B, "V"),
+ (0x5C, "V"),
+ (0x5D, "V"),
+ (0x5E, "V"),
+ (0x5F, "V"),
+ (0x60, "V"),
+ (0x61, "V"),
+ (0x62, "V"),
+ (0x63, "V"),
+ ]
+
+
+def _seg_1() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x64, "V"),
+ (0x65, "V"),
+ (0x66, "V"),
+ (0x67, "V"),
+ (0x68, "V"),
+ (0x69, "V"),
+ (0x6A, "V"),
+ (0x6B, "V"),
+ (0x6C, "V"),
+ (0x6D, "V"),
+ (0x6E, "V"),
+ (0x6F, "V"),
+ (0x70, "V"),
+ (0x71, "V"),
+ (0x72, "V"),
+ (0x73, "V"),
+ (0x74, "V"),
+ (0x75, "V"),
+ (0x76, "V"),
+ (0x77, "V"),
+ (0x78, "V"),
+ (0x79, "V"),
+ (0x7A, "V"),
+ (0x7B, "V"),
+ (0x7C, "V"),
+ (0x7D, "V"),
+ (0x7E, "V"),
+ (0x7F, "V"),
+ (0x80, "X"),
+ (0x81, "X"),
+ (0x82, "X"),
+ (0x83, "X"),
+ (0x84, "X"),
+ (0x85, "X"),
+ (0x86, "X"),
+ (0x87, "X"),
+ (0x88, "X"),
+ (0x89, "X"),
+ (0x8A, "X"),
+ (0x8B, "X"),
+ (0x8C, "X"),
+ (0x8D, "X"),
+ (0x8E, "X"),
+ (0x8F, "X"),
+ (0x90, "X"),
+ (0x91, "X"),
+ (0x92, "X"),
+ (0x93, "X"),
+ (0x94, "X"),
+ (0x95, "X"),
+ (0x96, "X"),
+ (0x97, "X"),
+ (0x98, "X"),
+ (0x99, "X"),
+ (0x9A, "X"),
+ (0x9B, "X"),
+ (0x9C, "X"),
+ (0x9D, "X"),
+ (0x9E, "X"),
+ (0x9F, "X"),
+ (0xA0, "M", " "),
+ (0xA1, "V"),
+ (0xA2, "V"),
+ (0xA3, "V"),
+ (0xA4, "V"),
+ (0xA5, "V"),
+ (0xA6, "V"),
+ (0xA7, "V"),
+ (0xA8, "M", " ̈"),
+ (0xA9, "V"),
+ (0xAA, "M", "a"),
+ (0xAB, "V"),
+ (0xAC, "V"),
+ (0xAD, "I"),
+ (0xAE, "V"),
+ (0xAF, "M", " ̄"),
+ (0xB0, "V"),
+ (0xB1, "V"),
+ (0xB2, "M", "2"),
+ (0xB3, "M", "3"),
+ (0xB4, "M", " ́"),
+ (0xB5, "M", "μ"),
+ (0xB6, "V"),
+ (0xB7, "V"),
+ (0xB8, "M", " ̧"),
+ (0xB9, "M", "1"),
+ (0xBA, "M", "o"),
+ (0xBB, "V"),
+ (0xBC, "M", "1⁄4"),
+ (0xBD, "M", "1⁄2"),
+ (0xBE, "M", "3⁄4"),
+ (0xBF, "V"),
+ (0xC0, "M", "à"),
+ (0xC1, "M", "á"),
+ (0xC2, "M", "â"),
+ (0xC3, "M", "ã"),
+ (0xC4, "M", "ä"),
+ (0xC5, "M", "å"),
+ (0xC6, "M", "æ"),
+ (0xC7, "M", "ç"),
+ ]
+
+
+def _seg_2() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xC8, "M", "è"),
+ (0xC9, "M", "é"),
+ (0xCA, "M", "ê"),
+ (0xCB, "M", "ë"),
+ (0xCC, "M", "ì"),
+ (0xCD, "M", "í"),
+ (0xCE, "M", "î"),
+ (0xCF, "M", "ï"),
+ (0xD0, "M", "ð"),
+ (0xD1, "M", "ñ"),
+ (0xD2, "M", "ò"),
+ (0xD3, "M", "ó"),
+ (0xD4, "M", "ô"),
+ (0xD5, "M", "õ"),
+ (0xD6, "M", "ö"),
+ (0xD7, "V"),
+ (0xD8, "M", "ø"),
+ (0xD9, "M", "ù"),
+ (0xDA, "M", "ú"),
+ (0xDB, "M", "û"),
+ (0xDC, "M", "ü"),
+ (0xDD, "M", "ý"),
+ (0xDE, "M", "þ"),
+ (0xDF, "D", "ss"),
+ (0xE0, "V"),
+ (0xE1, "V"),
+ (0xE2, "V"),
+ (0xE3, "V"),
+ (0xE4, "V"),
+ (0xE5, "V"),
+ (0xE6, "V"),
+ (0xE7, "V"),
+ (0xE8, "V"),
+ (0xE9, "V"),
+ (0xEA, "V"),
+ (0xEB, "V"),
+ (0xEC, "V"),
+ (0xED, "V"),
+ (0xEE, "V"),
+ (0xEF, "V"),
+ (0xF0, "V"),
+ (0xF1, "V"),
+ (0xF2, "V"),
+ (0xF3, "V"),
+ (0xF4, "V"),
+ (0xF5, "V"),
+ (0xF6, "V"),
+ (0xF7, "V"),
+ (0xF8, "V"),
+ (0xF9, "V"),
+ (0xFA, "V"),
+ (0xFB, "V"),
+ (0xFC, "V"),
+ (0xFD, "V"),
+ (0xFE, "V"),
+ (0xFF, "V"),
+ (0x100, "M", "ā"),
+ (0x101, "V"),
+ (0x102, "M", "ă"),
+ (0x103, "V"),
+ (0x104, "M", "ą"),
+ (0x105, "V"),
+ (0x106, "M", "ć"),
+ (0x107, "V"),
+ (0x108, "M", "ĉ"),
+ (0x109, "V"),
+ (0x10A, "M", "ċ"),
+ (0x10B, "V"),
+ (0x10C, "M", "č"),
+ (0x10D, "V"),
+ (0x10E, "M", "ď"),
+ (0x10F, "V"),
+ (0x110, "M", "đ"),
+ (0x111, "V"),
+ (0x112, "M", "ē"),
+ (0x113, "V"),
+ (0x114, "M", "ĕ"),
+ (0x115, "V"),
+ (0x116, "M", "ė"),
+ (0x117, "V"),
+ (0x118, "M", "ę"),
+ (0x119, "V"),
+ (0x11A, "M", "ě"),
+ (0x11B, "V"),
+ (0x11C, "M", "ĝ"),
+ (0x11D, "V"),
+ (0x11E, "M", "ğ"),
+ (0x11F, "V"),
+ (0x120, "M", "ġ"),
+ (0x121, "V"),
+ (0x122, "M", "ģ"),
+ (0x123, "V"),
+ (0x124, "M", "ĥ"),
+ (0x125, "V"),
+ (0x126, "M", "ħ"),
+ (0x127, "V"),
+ (0x128, "M", "ĩ"),
+ (0x129, "V"),
+ (0x12A, "M", "ī"),
+ (0x12B, "V"),
+ ]
+
+
+def _seg_3() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x12C, "M", "ĭ"),
+ (0x12D, "V"),
+ (0x12E, "M", "į"),
+ (0x12F, "V"),
+ (0x130, "M", "i̇"),
+ (0x131, "V"),
+ (0x132, "M", "ij"),
+ (0x134, "M", "ĵ"),
+ (0x135, "V"),
+ (0x136, "M", "ķ"),
+ (0x137, "V"),
+ (0x139, "M", "ĺ"),
+ (0x13A, "V"),
+ (0x13B, "M", "ļ"),
+ (0x13C, "V"),
+ (0x13D, "M", "ľ"),
+ (0x13E, "V"),
+ (0x13F, "M", "l·"),
+ (0x141, "M", "ł"),
+ (0x142, "V"),
+ (0x143, "M", "ń"),
+ (0x144, "V"),
+ (0x145, "M", "ņ"),
+ (0x146, "V"),
+ (0x147, "M", "ň"),
+ (0x148, "V"),
+ (0x149, "M", "ʼn"),
+ (0x14A, "M", "ŋ"),
+ (0x14B, "V"),
+ (0x14C, "M", "ō"),
+ (0x14D, "V"),
+ (0x14E, "M", "ŏ"),
+ (0x14F, "V"),
+ (0x150, "M", "ő"),
+ (0x151, "V"),
+ (0x152, "M", "œ"),
+ (0x153, "V"),
+ (0x154, "M", "ŕ"),
+ (0x155, "V"),
+ (0x156, "M", "ŗ"),
+ (0x157, "V"),
+ (0x158, "M", "ř"),
+ (0x159, "V"),
+ (0x15A, "M", "ś"),
+ (0x15B, "V"),
+ (0x15C, "M", "ŝ"),
+ (0x15D, "V"),
+ (0x15E, "M", "ş"),
+ (0x15F, "V"),
+ (0x160, "M", "š"),
+ (0x161, "V"),
+ (0x162, "M", "ţ"),
+ (0x163, "V"),
+ (0x164, "M", "ť"),
+ (0x165, "V"),
+ (0x166, "M", "ŧ"),
+ (0x167, "V"),
+ (0x168, "M", "ũ"),
+ (0x169, "V"),
+ (0x16A, "M", "ū"),
+ (0x16B, "V"),
+ (0x16C, "M", "ŭ"),
+ (0x16D, "V"),
+ (0x16E, "M", "ů"),
+ (0x16F, "V"),
+ (0x170, "M", "ű"),
+ (0x171, "V"),
+ (0x172, "M", "ų"),
+ (0x173, "V"),
+ (0x174, "M", "ŵ"),
+ (0x175, "V"),
+ (0x176, "M", "ŷ"),
+ (0x177, "V"),
+ (0x178, "M", "ÿ"),
+ (0x179, "M", "ź"),
+ (0x17A, "V"),
+ (0x17B, "M", "ż"),
+ (0x17C, "V"),
+ (0x17D, "M", "ž"),
+ (0x17E, "V"),
+ (0x17F, "M", "s"),
+ (0x180, "V"),
+ (0x181, "M", "ɓ"),
+ (0x182, "M", "ƃ"),
+ (0x183, "V"),
+ (0x184, "M", "ƅ"),
+ (0x185, "V"),
+ (0x186, "M", "ɔ"),
+ (0x187, "M", "ƈ"),
+ (0x188, "V"),
+ (0x189, "M", "ɖ"),
+ (0x18A, "M", "ɗ"),
+ (0x18B, "M", "ƌ"),
+ (0x18C, "V"),
+ (0x18E, "M", "ǝ"),
+ (0x18F, "M", "ə"),
+ (0x190, "M", "ɛ"),
+ (0x191, "M", "ƒ"),
+ (0x192, "V"),
+ (0x193, "M", "ɠ"),
+ ]
+
+
+def _seg_4() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x194, "M", "ɣ"),
+ (0x195, "V"),
+ (0x196, "M", "ɩ"),
+ (0x197, "M", "ɨ"),
+ (0x198, "M", "ƙ"),
+ (0x199, "V"),
+ (0x19C, "M", "ɯ"),
+ (0x19D, "M", "ɲ"),
+ (0x19E, "V"),
+ (0x19F, "M", "ɵ"),
+ (0x1A0, "M", "ơ"),
+ (0x1A1, "V"),
+ (0x1A2, "M", "ƣ"),
+ (0x1A3, "V"),
+ (0x1A4, "M", "ƥ"),
+ (0x1A5, "V"),
+ (0x1A6, "M", "ʀ"),
+ (0x1A7, "M", "ƨ"),
+ (0x1A8, "V"),
+ (0x1A9, "M", "ʃ"),
+ (0x1AA, "V"),
+ (0x1AC, "M", "ƭ"),
+ (0x1AD, "V"),
+ (0x1AE, "M", "ʈ"),
+ (0x1AF, "M", "ư"),
+ (0x1B0, "V"),
+ (0x1B1, "M", "ʊ"),
+ (0x1B2, "M", "ʋ"),
+ (0x1B3, "M", "ƴ"),
+ (0x1B4, "V"),
+ (0x1B5, "M", "ƶ"),
+ (0x1B6, "V"),
+ (0x1B7, "M", "ʒ"),
+ (0x1B8, "M", "ƹ"),
+ (0x1B9, "V"),
+ (0x1BC, "M", "ƽ"),
+ (0x1BD, "V"),
+ (0x1C4, "M", "dž"),
+ (0x1C7, "M", "lj"),
+ (0x1CA, "M", "nj"),
+ (0x1CD, "M", "ǎ"),
+ (0x1CE, "V"),
+ (0x1CF, "M", "ǐ"),
+ (0x1D0, "V"),
+ (0x1D1, "M", "ǒ"),
+ (0x1D2, "V"),
+ (0x1D3, "M", "ǔ"),
+ (0x1D4, "V"),
+ (0x1D5, "M", "ǖ"),
+ (0x1D6, "V"),
+ (0x1D7, "M", "ǘ"),
+ (0x1D8, "V"),
+ (0x1D9, "M", "ǚ"),
+ (0x1DA, "V"),
+ (0x1DB, "M", "ǜ"),
+ (0x1DC, "V"),
+ (0x1DE, "M", "ǟ"),
+ (0x1DF, "V"),
+ (0x1E0, "M", "ǡ"),
+ (0x1E1, "V"),
+ (0x1E2, "M", "ǣ"),
+ (0x1E3, "V"),
+ (0x1E4, "M", "ǥ"),
+ (0x1E5, "V"),
+ (0x1E6, "M", "ǧ"),
+ (0x1E7, "V"),
+ (0x1E8, "M", "ǩ"),
+ (0x1E9, "V"),
+ (0x1EA, "M", "ǫ"),
+ (0x1EB, "V"),
+ (0x1EC, "M", "ǭ"),
+ (0x1ED, "V"),
+ (0x1EE, "M", "ǯ"),
+ (0x1EF, "V"),
+ (0x1F1, "M", "dz"),
+ (0x1F4, "M", "ǵ"),
+ (0x1F5, "V"),
+ (0x1F6, "M", "ƕ"),
+ (0x1F7, "M", "ƿ"),
+ (0x1F8, "M", "ǹ"),
+ (0x1F9, "V"),
+ (0x1FA, "M", "ǻ"),
+ (0x1FB, "V"),
+ (0x1FC, "M", "ǽ"),
+ (0x1FD, "V"),
+ (0x1FE, "M", "ǿ"),
+ (0x1FF, "V"),
+ (0x200, "M", "ȁ"),
+ (0x201, "V"),
+ (0x202, "M", "ȃ"),
+ (0x203, "V"),
+ (0x204, "M", "ȅ"),
+ (0x205, "V"),
+ (0x206, "M", "ȇ"),
+ (0x207, "V"),
+ (0x208, "M", "ȉ"),
+ (0x209, "V"),
+ (0x20A, "M", "ȋ"),
+ (0x20B, "V"),
+ (0x20C, "M", "ȍ"),
+ ]
+
+
+def _seg_5() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x20D, "V"),
+ (0x20E, "M", "ȏ"),
+ (0x20F, "V"),
+ (0x210, "M", "ȑ"),
+ (0x211, "V"),
+ (0x212, "M", "ȓ"),
+ (0x213, "V"),
+ (0x214, "M", "ȕ"),
+ (0x215, "V"),
+ (0x216, "M", "ȗ"),
+ (0x217, "V"),
+ (0x218, "M", "ș"),
+ (0x219, "V"),
+ (0x21A, "M", "ț"),
+ (0x21B, "V"),
+ (0x21C, "M", "ȝ"),
+ (0x21D, "V"),
+ (0x21E, "M", "ȟ"),
+ (0x21F, "V"),
+ (0x220, "M", "ƞ"),
+ (0x221, "V"),
+ (0x222, "M", "ȣ"),
+ (0x223, "V"),
+ (0x224, "M", "ȥ"),
+ (0x225, "V"),
+ (0x226, "M", "ȧ"),
+ (0x227, "V"),
+ (0x228, "M", "ȩ"),
+ (0x229, "V"),
+ (0x22A, "M", "ȫ"),
+ (0x22B, "V"),
+ (0x22C, "M", "ȭ"),
+ (0x22D, "V"),
+ (0x22E, "M", "ȯ"),
+ (0x22F, "V"),
+ (0x230, "M", "ȱ"),
+ (0x231, "V"),
+ (0x232, "M", "ȳ"),
+ (0x233, "V"),
+ (0x23A, "M", "ⱥ"),
+ (0x23B, "M", "ȼ"),
+ (0x23C, "V"),
+ (0x23D, "M", "ƚ"),
+ (0x23E, "M", "ⱦ"),
+ (0x23F, "V"),
+ (0x241, "M", "ɂ"),
+ (0x242, "V"),
+ (0x243, "M", "ƀ"),
+ (0x244, "M", "ʉ"),
+ (0x245, "M", "ʌ"),
+ (0x246, "M", "ɇ"),
+ (0x247, "V"),
+ (0x248, "M", "ɉ"),
+ (0x249, "V"),
+ (0x24A, "M", "ɋ"),
+ (0x24B, "V"),
+ (0x24C, "M", "ɍ"),
+ (0x24D, "V"),
+ (0x24E, "M", "ɏ"),
+ (0x24F, "V"),
+ (0x2B0, "M", "h"),
+ (0x2B1, "M", "ɦ"),
+ (0x2B2, "M", "j"),
+ (0x2B3, "M", "r"),
+ (0x2B4, "M", "ɹ"),
+ (0x2B5, "M", "ɻ"),
+ (0x2B6, "M", "ʁ"),
+ (0x2B7, "M", "w"),
+ (0x2B8, "M", "y"),
+ (0x2B9, "V"),
+ (0x2D8, "M", " ̆"),
+ (0x2D9, "M", " ̇"),
+ (0x2DA, "M", " ̊"),
+ (0x2DB, "M", " ̨"),
+ (0x2DC, "M", " ̃"),
+ (0x2DD, "M", " ̋"),
+ (0x2DE, "V"),
+ (0x2E0, "M", "ɣ"),
+ (0x2E1, "M", "l"),
+ (0x2E2, "M", "s"),
+ (0x2E3, "M", "x"),
+ (0x2E4, "M", "ʕ"),
+ (0x2E5, "V"),
+ (0x340, "M", "̀"),
+ (0x341, "M", "́"),
+ (0x342, "V"),
+ (0x343, "M", "̓"),
+ (0x344, "M", "̈́"),
+ (0x345, "M", "ι"),
+ (0x346, "V"),
+ (0x34F, "I"),
+ (0x350, "V"),
+ (0x370, "M", "ͱ"),
+ (0x371, "V"),
+ (0x372, "M", "ͳ"),
+ (0x373, "V"),
+ (0x374, "M", "ʹ"),
+ (0x375, "V"),
+ (0x376, "M", "ͷ"),
+ (0x377, "V"),
+ ]
+
+
+def _seg_6() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x378, "X"),
+ (0x37A, "M", " ι"),
+ (0x37B, "V"),
+ (0x37E, "M", ";"),
+ (0x37F, "M", "ϳ"),
+ (0x380, "X"),
+ (0x384, "M", " ́"),
+ (0x385, "M", " ̈́"),
+ (0x386, "M", "ά"),
+ (0x387, "M", "·"),
+ (0x388, "M", "έ"),
+ (0x389, "M", "ή"),
+ (0x38A, "M", "ί"),
+ (0x38B, "X"),
+ (0x38C, "M", "ό"),
+ (0x38D, "X"),
+ (0x38E, "M", "ύ"),
+ (0x38F, "M", "ώ"),
+ (0x390, "V"),
+ (0x391, "M", "α"),
+ (0x392, "M", "β"),
+ (0x393, "M", "γ"),
+ (0x394, "M", "δ"),
+ (0x395, "M", "ε"),
+ (0x396, "M", "ζ"),
+ (0x397, "M", "η"),
+ (0x398, "M", "θ"),
+ (0x399, "M", "ι"),
+ (0x39A, "M", "κ"),
+ (0x39B, "M", "λ"),
+ (0x39C, "M", "μ"),
+ (0x39D, "M", "ν"),
+ (0x39E, "M", "ξ"),
+ (0x39F, "M", "ο"),
+ (0x3A0, "M", "π"),
+ (0x3A1, "M", "ρ"),
+ (0x3A2, "X"),
+ (0x3A3, "M", "σ"),
+ (0x3A4, "M", "τ"),
+ (0x3A5, "M", "υ"),
+ (0x3A6, "M", "φ"),
+ (0x3A7, "M", "χ"),
+ (0x3A8, "M", "ψ"),
+ (0x3A9, "M", "ω"),
+ (0x3AA, "M", "ϊ"),
+ (0x3AB, "M", "ϋ"),
+ (0x3AC, "V"),
+ (0x3C2, "D", "σ"),
+ (0x3C3, "V"),
+ (0x3CF, "M", "ϗ"),
+ (0x3D0, "M", "β"),
+ (0x3D1, "M", "θ"),
+ (0x3D2, "M", "υ"),
+ (0x3D3, "M", "ύ"),
+ (0x3D4, "M", "ϋ"),
+ (0x3D5, "M", "φ"),
+ (0x3D6, "M", "π"),
+ (0x3D7, "V"),
+ (0x3D8, "M", "ϙ"),
+ (0x3D9, "V"),
+ (0x3DA, "M", "ϛ"),
+ (0x3DB, "V"),
+ (0x3DC, "M", "ϝ"),
+ (0x3DD, "V"),
+ (0x3DE, "M", "ϟ"),
+ (0x3DF, "V"),
+ (0x3E0, "M", "ϡ"),
+ (0x3E1, "V"),
+ (0x3E2, "M", "ϣ"),
+ (0x3E3, "V"),
+ (0x3E4, "M", "ϥ"),
+ (0x3E5, "V"),
+ (0x3E6, "M", "ϧ"),
+ (0x3E7, "V"),
+ (0x3E8, "M", "ϩ"),
+ (0x3E9, "V"),
+ (0x3EA, "M", "ϫ"),
+ (0x3EB, "V"),
+ (0x3EC, "M", "ϭ"),
+ (0x3ED, "V"),
+ (0x3EE, "M", "ϯ"),
+ (0x3EF, "V"),
+ (0x3F0, "M", "κ"),
+ (0x3F1, "M", "ρ"),
+ (0x3F2, "M", "σ"),
+ (0x3F3, "V"),
+ (0x3F4, "M", "θ"),
+ (0x3F5, "M", "ε"),
+ (0x3F6, "V"),
+ (0x3F7, "M", "ϸ"),
+ (0x3F8, "V"),
+ (0x3F9, "M", "σ"),
+ (0x3FA, "M", "ϻ"),
+ (0x3FB, "V"),
+ (0x3FD, "M", "ͻ"),
+ (0x3FE, "M", "ͼ"),
+ (0x3FF, "M", "ͽ"),
+ (0x400, "M", "ѐ"),
+ (0x401, "M", "ё"),
+ (0x402, "M", "ђ"),
+ ]
+
+
+def _seg_7() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x403, "M", "ѓ"),
+ (0x404, "M", "є"),
+ (0x405, "M", "ѕ"),
+ (0x406, "M", "і"),
+ (0x407, "M", "ї"),
+ (0x408, "M", "ј"),
+ (0x409, "M", "љ"),
+ (0x40A, "M", "њ"),
+ (0x40B, "M", "ћ"),
+ (0x40C, "M", "ќ"),
+ (0x40D, "M", "ѝ"),
+ (0x40E, "M", "ў"),
+ (0x40F, "M", "џ"),
+ (0x410, "M", "а"),
+ (0x411, "M", "б"),
+ (0x412, "M", "в"),
+ (0x413, "M", "г"),
+ (0x414, "M", "д"),
+ (0x415, "M", "е"),
+ (0x416, "M", "ж"),
+ (0x417, "M", "з"),
+ (0x418, "M", "и"),
+ (0x419, "M", "й"),
+ (0x41A, "M", "к"),
+ (0x41B, "M", "л"),
+ (0x41C, "M", "м"),
+ (0x41D, "M", "н"),
+ (0x41E, "M", "о"),
+ (0x41F, "M", "п"),
+ (0x420, "M", "р"),
+ (0x421, "M", "с"),
+ (0x422, "M", "т"),
+ (0x423, "M", "у"),
+ (0x424, "M", "ф"),
+ (0x425, "M", "х"),
+ (0x426, "M", "ц"),
+ (0x427, "M", "ч"),
+ (0x428, "M", "ш"),
+ (0x429, "M", "щ"),
+ (0x42A, "M", "ъ"),
+ (0x42B, "M", "ы"),
+ (0x42C, "M", "ь"),
+ (0x42D, "M", "э"),
+ (0x42E, "M", "ю"),
+ (0x42F, "M", "я"),
+ (0x430, "V"),
+ (0x460, "M", "ѡ"),
+ (0x461, "V"),
+ (0x462, "M", "ѣ"),
+ (0x463, "V"),
+ (0x464, "M", "ѥ"),
+ (0x465, "V"),
+ (0x466, "M", "ѧ"),
+ (0x467, "V"),
+ (0x468, "M", "ѩ"),
+ (0x469, "V"),
+ (0x46A, "M", "ѫ"),
+ (0x46B, "V"),
+ (0x46C, "M", "ѭ"),
+ (0x46D, "V"),
+ (0x46E, "M", "ѯ"),
+ (0x46F, "V"),
+ (0x470, "M", "ѱ"),
+ (0x471, "V"),
+ (0x472, "M", "ѳ"),
+ (0x473, "V"),
+ (0x474, "M", "ѵ"),
+ (0x475, "V"),
+ (0x476, "M", "ѷ"),
+ (0x477, "V"),
+ (0x478, "M", "ѹ"),
+ (0x479, "V"),
+ (0x47A, "M", "ѻ"),
+ (0x47B, "V"),
+ (0x47C, "M", "ѽ"),
+ (0x47D, "V"),
+ (0x47E, "M", "ѿ"),
+ (0x47F, "V"),
+ (0x480, "M", "ҁ"),
+ (0x481, "V"),
+ (0x48A, "M", "ҋ"),
+ (0x48B, "V"),
+ (0x48C, "M", "ҍ"),
+ (0x48D, "V"),
+ (0x48E, "M", "ҏ"),
+ (0x48F, "V"),
+ (0x490, "M", "ґ"),
+ (0x491, "V"),
+ (0x492, "M", "ғ"),
+ (0x493, "V"),
+ (0x494, "M", "ҕ"),
+ (0x495, "V"),
+ (0x496, "M", "җ"),
+ (0x497, "V"),
+ (0x498, "M", "ҙ"),
+ (0x499, "V"),
+ (0x49A, "M", "қ"),
+ (0x49B, "V"),
+ (0x49C, "M", "ҝ"),
+ (0x49D, "V"),
+ ]
+
+
+def _seg_8() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x49E, "M", "ҟ"),
+ (0x49F, "V"),
+ (0x4A0, "M", "ҡ"),
+ (0x4A1, "V"),
+ (0x4A2, "M", "ң"),
+ (0x4A3, "V"),
+ (0x4A4, "M", "ҥ"),
+ (0x4A5, "V"),
+ (0x4A6, "M", "ҧ"),
+ (0x4A7, "V"),
+ (0x4A8, "M", "ҩ"),
+ (0x4A9, "V"),
+ (0x4AA, "M", "ҫ"),
+ (0x4AB, "V"),
+ (0x4AC, "M", "ҭ"),
+ (0x4AD, "V"),
+ (0x4AE, "M", "ү"),
+ (0x4AF, "V"),
+ (0x4B0, "M", "ұ"),
+ (0x4B1, "V"),
+ (0x4B2, "M", "ҳ"),
+ (0x4B3, "V"),
+ (0x4B4, "M", "ҵ"),
+ (0x4B5, "V"),
+ (0x4B6, "M", "ҷ"),
+ (0x4B7, "V"),
+ (0x4B8, "M", "ҹ"),
+ (0x4B9, "V"),
+ (0x4BA, "M", "һ"),
+ (0x4BB, "V"),
+ (0x4BC, "M", "ҽ"),
+ (0x4BD, "V"),
+ (0x4BE, "M", "ҿ"),
+ (0x4BF, "V"),
+ (0x4C0, "M", "ӏ"),
+ (0x4C1, "M", "ӂ"),
+ (0x4C2, "V"),
+ (0x4C3, "M", "ӄ"),
+ (0x4C4, "V"),
+ (0x4C5, "M", "ӆ"),
+ (0x4C6, "V"),
+ (0x4C7, "M", "ӈ"),
+ (0x4C8, "V"),
+ (0x4C9, "M", "ӊ"),
+ (0x4CA, "V"),
+ (0x4CB, "M", "ӌ"),
+ (0x4CC, "V"),
+ (0x4CD, "M", "ӎ"),
+ (0x4CE, "V"),
+ (0x4D0, "M", "ӑ"),
+ (0x4D1, "V"),
+ (0x4D2, "M", "ӓ"),
+ (0x4D3, "V"),
+ (0x4D4, "M", "ӕ"),
+ (0x4D5, "V"),
+ (0x4D6, "M", "ӗ"),
+ (0x4D7, "V"),
+ (0x4D8, "M", "ә"),
+ (0x4D9, "V"),
+ (0x4DA, "M", "ӛ"),
+ (0x4DB, "V"),
+ (0x4DC, "M", "ӝ"),
+ (0x4DD, "V"),
+ (0x4DE, "M", "ӟ"),
+ (0x4DF, "V"),
+ (0x4E0, "M", "ӡ"),
+ (0x4E1, "V"),
+ (0x4E2, "M", "ӣ"),
+ (0x4E3, "V"),
+ (0x4E4, "M", "ӥ"),
+ (0x4E5, "V"),
+ (0x4E6, "M", "ӧ"),
+ (0x4E7, "V"),
+ (0x4E8, "M", "ө"),
+ (0x4E9, "V"),
+ (0x4EA, "M", "ӫ"),
+ (0x4EB, "V"),
+ (0x4EC, "M", "ӭ"),
+ (0x4ED, "V"),
+ (0x4EE, "M", "ӯ"),
+ (0x4EF, "V"),
+ (0x4F0, "M", "ӱ"),
+ (0x4F1, "V"),
+ (0x4F2, "M", "ӳ"),
+ (0x4F3, "V"),
+ (0x4F4, "M", "ӵ"),
+ (0x4F5, "V"),
+ (0x4F6, "M", "ӷ"),
+ (0x4F7, "V"),
+ (0x4F8, "M", "ӹ"),
+ (0x4F9, "V"),
+ (0x4FA, "M", "ӻ"),
+ (0x4FB, "V"),
+ (0x4FC, "M", "ӽ"),
+ (0x4FD, "V"),
+ (0x4FE, "M", "ӿ"),
+ (0x4FF, "V"),
+ (0x500, "M", "ԁ"),
+ (0x501, "V"),
+ (0x502, "M", "ԃ"),
+ ]
+
+
+def _seg_9() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x503, "V"),
+ (0x504, "M", "ԅ"),
+ (0x505, "V"),
+ (0x506, "M", "ԇ"),
+ (0x507, "V"),
+ (0x508, "M", "ԉ"),
+ (0x509, "V"),
+ (0x50A, "M", "ԋ"),
+ (0x50B, "V"),
+ (0x50C, "M", "ԍ"),
+ (0x50D, "V"),
+ (0x50E, "M", "ԏ"),
+ (0x50F, "V"),
+ (0x510, "M", "ԑ"),
+ (0x511, "V"),
+ (0x512, "M", "ԓ"),
+ (0x513, "V"),
+ (0x514, "M", "ԕ"),
+ (0x515, "V"),
+ (0x516, "M", "ԗ"),
+ (0x517, "V"),
+ (0x518, "M", "ԙ"),
+ (0x519, "V"),
+ (0x51A, "M", "ԛ"),
+ (0x51B, "V"),
+ (0x51C, "M", "ԝ"),
+ (0x51D, "V"),
+ (0x51E, "M", "ԟ"),
+ (0x51F, "V"),
+ (0x520, "M", "ԡ"),
+ (0x521, "V"),
+ (0x522, "M", "ԣ"),
+ (0x523, "V"),
+ (0x524, "M", "ԥ"),
+ (0x525, "V"),
+ (0x526, "M", "ԧ"),
+ (0x527, "V"),
+ (0x528, "M", "ԩ"),
+ (0x529, "V"),
+ (0x52A, "M", "ԫ"),
+ (0x52B, "V"),
+ (0x52C, "M", "ԭ"),
+ (0x52D, "V"),
+ (0x52E, "M", "ԯ"),
+ (0x52F, "V"),
+ (0x530, "X"),
+ (0x531, "M", "ա"),
+ (0x532, "M", "բ"),
+ (0x533, "M", "գ"),
+ (0x534, "M", "դ"),
+ (0x535, "M", "ե"),
+ (0x536, "M", "զ"),
+ (0x537, "M", "է"),
+ (0x538, "M", "ը"),
+ (0x539, "M", "թ"),
+ (0x53A, "M", "ժ"),
+ (0x53B, "M", "ի"),
+ (0x53C, "M", "լ"),
+ (0x53D, "M", "խ"),
+ (0x53E, "M", "ծ"),
+ (0x53F, "M", "կ"),
+ (0x540, "M", "հ"),
+ (0x541, "M", "ձ"),
+ (0x542, "M", "ղ"),
+ (0x543, "M", "ճ"),
+ (0x544, "M", "մ"),
+ (0x545, "M", "յ"),
+ (0x546, "M", "ն"),
+ (0x547, "M", "շ"),
+ (0x548, "M", "ո"),
+ (0x549, "M", "չ"),
+ (0x54A, "M", "պ"),
+ (0x54B, "M", "ջ"),
+ (0x54C, "M", "ռ"),
+ (0x54D, "M", "ս"),
+ (0x54E, "M", "վ"),
+ (0x54F, "M", "տ"),
+ (0x550, "M", "ր"),
+ (0x551, "M", "ց"),
+ (0x552, "M", "ւ"),
+ (0x553, "M", "փ"),
+ (0x554, "M", "ք"),
+ (0x555, "M", "օ"),
+ (0x556, "M", "ֆ"),
+ (0x557, "X"),
+ (0x559, "V"),
+ (0x587, "M", "եւ"),
+ (0x588, "V"),
+ (0x58B, "X"),
+ (0x58D, "V"),
+ (0x590, "X"),
+ (0x591, "V"),
+ (0x5C8, "X"),
+ (0x5D0, "V"),
+ (0x5EB, "X"),
+ (0x5EF, "V"),
+ (0x5F5, "X"),
+ (0x606, "V"),
+ (0x61C, "X"),
+ (0x61D, "V"),
+ ]
+
+
+def _seg_10() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x675, "M", "اٴ"),
+ (0x676, "M", "وٴ"),
+ (0x677, "M", "ۇٴ"),
+ (0x678, "M", "يٴ"),
+ (0x679, "V"),
+ (0x6DD, "X"),
+ (0x6DE, "V"),
+ (0x70E, "X"),
+ (0x710, "V"),
+ (0x74B, "X"),
+ (0x74D, "V"),
+ (0x7B2, "X"),
+ (0x7C0, "V"),
+ (0x7FB, "X"),
+ (0x7FD, "V"),
+ (0x82E, "X"),
+ (0x830, "V"),
+ (0x83F, "X"),
+ (0x840, "V"),
+ (0x85C, "X"),
+ (0x85E, "V"),
+ (0x85F, "X"),
+ (0x860, "V"),
+ (0x86B, "X"),
+ (0x870, "V"),
+ (0x88F, "X"),
+ (0x897, "V"),
+ (0x8E2, "X"),
+ (0x8E3, "V"),
+ (0x958, "M", "क़"),
+ (0x959, "M", "ख़"),
+ (0x95A, "M", "ग़"),
+ (0x95B, "M", "ज़"),
+ (0x95C, "M", "ड़"),
+ (0x95D, "M", "ढ़"),
+ (0x95E, "M", "फ़"),
+ (0x95F, "M", "य़"),
+ (0x960, "V"),
+ (0x984, "X"),
+ (0x985, "V"),
+ (0x98D, "X"),
+ (0x98F, "V"),
+ (0x991, "X"),
+ (0x993, "V"),
+ (0x9A9, "X"),
+ (0x9AA, "V"),
+ (0x9B1, "X"),
+ (0x9B2, "V"),
+ (0x9B3, "X"),
+ (0x9B6, "V"),
+ (0x9BA, "X"),
+ (0x9BC, "V"),
+ (0x9C5, "X"),
+ (0x9C7, "V"),
+ (0x9C9, "X"),
+ (0x9CB, "V"),
+ (0x9CF, "X"),
+ (0x9D7, "V"),
+ (0x9D8, "X"),
+ (0x9DC, "M", "ড়"),
+ (0x9DD, "M", "ঢ়"),
+ (0x9DE, "X"),
+ (0x9DF, "M", "য়"),
+ (0x9E0, "V"),
+ (0x9E4, "X"),
+ (0x9E6, "V"),
+ (0x9FF, "X"),
+ (0xA01, "V"),
+ (0xA04, "X"),
+ (0xA05, "V"),
+ (0xA0B, "X"),
+ (0xA0F, "V"),
+ (0xA11, "X"),
+ (0xA13, "V"),
+ (0xA29, "X"),
+ (0xA2A, "V"),
+ (0xA31, "X"),
+ (0xA32, "V"),
+ (0xA33, "M", "ਲ਼"),
+ (0xA34, "X"),
+ (0xA35, "V"),
+ (0xA36, "M", "ਸ਼"),
+ (0xA37, "X"),
+ (0xA38, "V"),
+ (0xA3A, "X"),
+ (0xA3C, "V"),
+ (0xA3D, "X"),
+ (0xA3E, "V"),
+ (0xA43, "X"),
+ (0xA47, "V"),
+ (0xA49, "X"),
+ (0xA4B, "V"),
+ (0xA4E, "X"),
+ (0xA51, "V"),
+ (0xA52, "X"),
+ (0xA59, "M", "ਖ਼"),
+ (0xA5A, "M", "ਗ਼"),
+ (0xA5B, "M", "ਜ਼"),
+ (0xA5C, "V"),
+ (0xA5D, "X"),
+ ]
+
+
+def _seg_11() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA5E, "M", "ਫ਼"),
+ (0xA5F, "X"),
+ (0xA66, "V"),
+ (0xA77, "X"),
+ (0xA81, "V"),
+ (0xA84, "X"),
+ (0xA85, "V"),
+ (0xA8E, "X"),
+ (0xA8F, "V"),
+ (0xA92, "X"),
+ (0xA93, "V"),
+ (0xAA9, "X"),
+ (0xAAA, "V"),
+ (0xAB1, "X"),
+ (0xAB2, "V"),
+ (0xAB4, "X"),
+ (0xAB5, "V"),
+ (0xABA, "X"),
+ (0xABC, "V"),
+ (0xAC6, "X"),
+ (0xAC7, "V"),
+ (0xACA, "X"),
+ (0xACB, "V"),
+ (0xACE, "X"),
+ (0xAD0, "V"),
+ (0xAD1, "X"),
+ (0xAE0, "V"),
+ (0xAE4, "X"),
+ (0xAE6, "V"),
+ (0xAF2, "X"),
+ (0xAF9, "V"),
+ (0xB00, "X"),
+ (0xB01, "V"),
+ (0xB04, "X"),
+ (0xB05, "V"),
+ (0xB0D, "X"),
+ (0xB0F, "V"),
+ (0xB11, "X"),
+ (0xB13, "V"),
+ (0xB29, "X"),
+ (0xB2A, "V"),
+ (0xB31, "X"),
+ (0xB32, "V"),
+ (0xB34, "X"),
+ (0xB35, "V"),
+ (0xB3A, "X"),
+ (0xB3C, "V"),
+ (0xB45, "X"),
+ (0xB47, "V"),
+ (0xB49, "X"),
+ (0xB4B, "V"),
+ (0xB4E, "X"),
+ (0xB55, "V"),
+ (0xB58, "X"),
+ (0xB5C, "M", "ଡ଼"),
+ (0xB5D, "M", "ଢ଼"),
+ (0xB5E, "X"),
+ (0xB5F, "V"),
+ (0xB64, "X"),
+ (0xB66, "V"),
+ (0xB78, "X"),
+ (0xB82, "V"),
+ (0xB84, "X"),
+ (0xB85, "V"),
+ (0xB8B, "X"),
+ (0xB8E, "V"),
+ (0xB91, "X"),
+ (0xB92, "V"),
+ (0xB96, "X"),
+ (0xB99, "V"),
+ (0xB9B, "X"),
+ (0xB9C, "V"),
+ (0xB9D, "X"),
+ (0xB9E, "V"),
+ (0xBA0, "X"),
+ (0xBA3, "V"),
+ (0xBA5, "X"),
+ (0xBA8, "V"),
+ (0xBAB, "X"),
+ (0xBAE, "V"),
+ (0xBBA, "X"),
+ (0xBBE, "V"),
+ (0xBC3, "X"),
+ (0xBC6, "V"),
+ (0xBC9, "X"),
+ (0xBCA, "V"),
+ (0xBCE, "X"),
+ (0xBD0, "V"),
+ (0xBD1, "X"),
+ (0xBD7, "V"),
+ (0xBD8, "X"),
+ (0xBE6, "V"),
+ (0xBFB, "X"),
+ (0xC00, "V"),
+ (0xC0D, "X"),
+ (0xC0E, "V"),
+ (0xC11, "X"),
+ (0xC12, "V"),
+ (0xC29, "X"),
+ (0xC2A, "V"),
+ ]
+
+
+def _seg_12() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xC3A, "X"),
+ (0xC3C, "V"),
+ (0xC45, "X"),
+ (0xC46, "V"),
+ (0xC49, "X"),
+ (0xC4A, "V"),
+ (0xC4E, "X"),
+ (0xC55, "V"),
+ (0xC57, "X"),
+ (0xC58, "V"),
+ (0xC5B, "X"),
+ (0xC5D, "V"),
+ (0xC5E, "X"),
+ (0xC60, "V"),
+ (0xC64, "X"),
+ (0xC66, "V"),
+ (0xC70, "X"),
+ (0xC77, "V"),
+ (0xC8D, "X"),
+ (0xC8E, "V"),
+ (0xC91, "X"),
+ (0xC92, "V"),
+ (0xCA9, "X"),
+ (0xCAA, "V"),
+ (0xCB4, "X"),
+ (0xCB5, "V"),
+ (0xCBA, "X"),
+ (0xCBC, "V"),
+ (0xCC5, "X"),
+ (0xCC6, "V"),
+ (0xCC9, "X"),
+ (0xCCA, "V"),
+ (0xCCE, "X"),
+ (0xCD5, "V"),
+ (0xCD7, "X"),
+ (0xCDD, "V"),
+ (0xCDF, "X"),
+ (0xCE0, "V"),
+ (0xCE4, "X"),
+ (0xCE6, "V"),
+ (0xCF0, "X"),
+ (0xCF1, "V"),
+ (0xCF4, "X"),
+ (0xD00, "V"),
+ (0xD0D, "X"),
+ (0xD0E, "V"),
+ (0xD11, "X"),
+ (0xD12, "V"),
+ (0xD45, "X"),
+ (0xD46, "V"),
+ (0xD49, "X"),
+ (0xD4A, "V"),
+ (0xD50, "X"),
+ (0xD54, "V"),
+ (0xD64, "X"),
+ (0xD66, "V"),
+ (0xD80, "X"),
+ (0xD81, "V"),
+ (0xD84, "X"),
+ (0xD85, "V"),
+ (0xD97, "X"),
+ (0xD9A, "V"),
+ (0xDB2, "X"),
+ (0xDB3, "V"),
+ (0xDBC, "X"),
+ (0xDBD, "V"),
+ (0xDBE, "X"),
+ (0xDC0, "V"),
+ (0xDC7, "X"),
+ (0xDCA, "V"),
+ (0xDCB, "X"),
+ (0xDCF, "V"),
+ (0xDD5, "X"),
+ (0xDD6, "V"),
+ (0xDD7, "X"),
+ (0xDD8, "V"),
+ (0xDE0, "X"),
+ (0xDE6, "V"),
+ (0xDF0, "X"),
+ (0xDF2, "V"),
+ (0xDF5, "X"),
+ (0xE01, "V"),
+ (0xE33, "M", "ํา"),
+ (0xE34, "V"),
+ (0xE3B, "X"),
+ (0xE3F, "V"),
+ (0xE5C, "X"),
+ (0xE81, "V"),
+ (0xE83, "X"),
+ (0xE84, "V"),
+ (0xE85, "X"),
+ (0xE86, "V"),
+ (0xE8B, "X"),
+ (0xE8C, "V"),
+ (0xEA4, "X"),
+ (0xEA5, "V"),
+ (0xEA6, "X"),
+ (0xEA7, "V"),
+ (0xEB3, "M", "ໍາ"),
+ (0xEB4, "V"),
+ ]
+
+
+def _seg_13() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xEBE, "X"),
+ (0xEC0, "V"),
+ (0xEC5, "X"),
+ (0xEC6, "V"),
+ (0xEC7, "X"),
+ (0xEC8, "V"),
+ (0xECF, "X"),
+ (0xED0, "V"),
+ (0xEDA, "X"),
+ (0xEDC, "M", "ຫນ"),
+ (0xEDD, "M", "ຫມ"),
+ (0xEDE, "V"),
+ (0xEE0, "X"),
+ (0xF00, "V"),
+ (0xF0C, "M", "་"),
+ (0xF0D, "V"),
+ (0xF43, "M", "གྷ"),
+ (0xF44, "V"),
+ (0xF48, "X"),
+ (0xF49, "V"),
+ (0xF4D, "M", "ཌྷ"),
+ (0xF4E, "V"),
+ (0xF52, "M", "དྷ"),
+ (0xF53, "V"),
+ (0xF57, "M", "བྷ"),
+ (0xF58, "V"),
+ (0xF5C, "M", "ཛྷ"),
+ (0xF5D, "V"),
+ (0xF69, "M", "ཀྵ"),
+ (0xF6A, "V"),
+ (0xF6D, "X"),
+ (0xF71, "V"),
+ (0xF73, "M", "ཱི"),
+ (0xF74, "V"),
+ (0xF75, "M", "ཱུ"),
+ (0xF76, "M", "ྲྀ"),
+ (0xF77, "M", "ྲཱྀ"),
+ (0xF78, "M", "ླྀ"),
+ (0xF79, "M", "ླཱྀ"),
+ (0xF7A, "V"),
+ (0xF81, "M", "ཱྀ"),
+ (0xF82, "V"),
+ (0xF93, "M", "ྒྷ"),
+ (0xF94, "V"),
+ (0xF98, "X"),
+ (0xF99, "V"),
+ (0xF9D, "M", "ྜྷ"),
+ (0xF9E, "V"),
+ (0xFA2, "M", "ྡྷ"),
+ (0xFA3, "V"),
+ (0xFA7, "M", "ྦྷ"),
+ (0xFA8, "V"),
+ (0xFAC, "M", "ྫྷ"),
+ (0xFAD, "V"),
+ (0xFB9, "M", "ྐྵ"),
+ (0xFBA, "V"),
+ (0xFBD, "X"),
+ (0xFBE, "V"),
+ (0xFCD, "X"),
+ (0xFCE, "V"),
+ (0xFDB, "X"),
+ (0x1000, "V"),
+ (0x10A0, "M", "ⴀ"),
+ (0x10A1, "M", "ⴁ"),
+ (0x10A2, "M", "ⴂ"),
+ (0x10A3, "M", "ⴃ"),
+ (0x10A4, "M", "ⴄ"),
+ (0x10A5, "M", "ⴅ"),
+ (0x10A6, "M", "ⴆ"),
+ (0x10A7, "M", "ⴇ"),
+ (0x10A8, "M", "ⴈ"),
+ (0x10A9, "M", "ⴉ"),
+ (0x10AA, "M", "ⴊ"),
+ (0x10AB, "M", "ⴋ"),
+ (0x10AC, "M", "ⴌ"),
+ (0x10AD, "M", "ⴍ"),
+ (0x10AE, "M", "ⴎ"),
+ (0x10AF, "M", "ⴏ"),
+ (0x10B0, "M", "ⴐ"),
+ (0x10B1, "M", "ⴑ"),
+ (0x10B2, "M", "ⴒ"),
+ (0x10B3, "M", "ⴓ"),
+ (0x10B4, "M", "ⴔ"),
+ (0x10B5, "M", "ⴕ"),
+ (0x10B6, "M", "ⴖ"),
+ (0x10B7, "M", "ⴗ"),
+ (0x10B8, "M", "ⴘ"),
+ (0x10B9, "M", "ⴙ"),
+ (0x10BA, "M", "ⴚ"),
+ (0x10BB, "M", "ⴛ"),
+ (0x10BC, "M", "ⴜ"),
+ (0x10BD, "M", "ⴝ"),
+ (0x10BE, "M", "ⴞ"),
+ (0x10BF, "M", "ⴟ"),
+ (0x10C0, "M", "ⴠ"),
+ (0x10C1, "M", "ⴡ"),
+ (0x10C2, "M", "ⴢ"),
+ (0x10C3, "M", "ⴣ"),
+ (0x10C4, "M", "ⴤ"),
+ (0x10C5, "M", "ⴥ"),
+ ]
+
+
+def _seg_14() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x10C6, "X"),
+ (0x10C7, "M", "ⴧ"),
+ (0x10C8, "X"),
+ (0x10CD, "M", "ⴭ"),
+ (0x10CE, "X"),
+ (0x10D0, "V"),
+ (0x10FC, "M", "ნ"),
+ (0x10FD, "V"),
+ (0x115F, "I"),
+ (0x1161, "V"),
+ (0x1249, "X"),
+ (0x124A, "V"),
+ (0x124E, "X"),
+ (0x1250, "V"),
+ (0x1257, "X"),
+ (0x1258, "V"),
+ (0x1259, "X"),
+ (0x125A, "V"),
+ (0x125E, "X"),
+ (0x1260, "V"),
+ (0x1289, "X"),
+ (0x128A, "V"),
+ (0x128E, "X"),
+ (0x1290, "V"),
+ (0x12B1, "X"),
+ (0x12B2, "V"),
+ (0x12B6, "X"),
+ (0x12B8, "V"),
+ (0x12BF, "X"),
+ (0x12C0, "V"),
+ (0x12C1, "X"),
+ (0x12C2, "V"),
+ (0x12C6, "X"),
+ (0x12C8, "V"),
+ (0x12D7, "X"),
+ (0x12D8, "V"),
+ (0x1311, "X"),
+ (0x1312, "V"),
+ (0x1316, "X"),
+ (0x1318, "V"),
+ (0x135B, "X"),
+ (0x135D, "V"),
+ (0x137D, "X"),
+ (0x1380, "V"),
+ (0x139A, "X"),
+ (0x13A0, "V"),
+ (0x13F6, "X"),
+ (0x13F8, "M", "Ᏸ"),
+ (0x13F9, "M", "Ᏹ"),
+ (0x13FA, "M", "Ᏺ"),
+ (0x13FB, "M", "Ᏻ"),
+ (0x13FC, "M", "Ᏼ"),
+ (0x13FD, "M", "Ᏽ"),
+ (0x13FE, "X"),
+ (0x1400, "V"),
+ (0x1680, "X"),
+ (0x1681, "V"),
+ (0x169D, "X"),
+ (0x16A0, "V"),
+ (0x16F9, "X"),
+ (0x1700, "V"),
+ (0x1716, "X"),
+ (0x171F, "V"),
+ (0x1737, "X"),
+ (0x1740, "V"),
+ (0x1754, "X"),
+ (0x1760, "V"),
+ (0x176D, "X"),
+ (0x176E, "V"),
+ (0x1771, "X"),
+ (0x1772, "V"),
+ (0x1774, "X"),
+ (0x1780, "V"),
+ (0x17B4, "I"),
+ (0x17B6, "V"),
+ (0x17DE, "X"),
+ (0x17E0, "V"),
+ (0x17EA, "X"),
+ (0x17F0, "V"),
+ (0x17FA, "X"),
+ (0x1800, "V"),
+ (0x180B, "I"),
+ (0x1810, "V"),
+ (0x181A, "X"),
+ (0x1820, "V"),
+ (0x1879, "X"),
+ (0x1880, "V"),
+ (0x18AB, "X"),
+ (0x18B0, "V"),
+ (0x18F6, "X"),
+ (0x1900, "V"),
+ (0x191F, "X"),
+ (0x1920, "V"),
+ (0x192C, "X"),
+ (0x1930, "V"),
+ (0x193C, "X"),
+ (0x1940, "V"),
+ (0x1941, "X"),
+ (0x1944, "V"),
+ (0x196E, "X"),
+ ]
+
+
+def _seg_15() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1970, "V"),
+ (0x1975, "X"),
+ (0x1980, "V"),
+ (0x19AC, "X"),
+ (0x19B0, "V"),
+ (0x19CA, "X"),
+ (0x19D0, "V"),
+ (0x19DB, "X"),
+ (0x19DE, "V"),
+ (0x1A1C, "X"),
+ (0x1A1E, "V"),
+ (0x1A5F, "X"),
+ (0x1A60, "V"),
+ (0x1A7D, "X"),
+ (0x1A7F, "V"),
+ (0x1A8A, "X"),
+ (0x1A90, "V"),
+ (0x1A9A, "X"),
+ (0x1AA0, "V"),
+ (0x1AAE, "X"),
+ (0x1AB0, "V"),
+ (0x1ACF, "X"),
+ (0x1B00, "V"),
+ (0x1B4D, "X"),
+ (0x1B4E, "V"),
+ (0x1BF4, "X"),
+ (0x1BFC, "V"),
+ (0x1C38, "X"),
+ (0x1C3B, "V"),
+ (0x1C4A, "X"),
+ (0x1C4D, "V"),
+ (0x1C80, "M", "в"),
+ (0x1C81, "M", "д"),
+ (0x1C82, "M", "о"),
+ (0x1C83, "M", "с"),
+ (0x1C84, "M", "т"),
+ (0x1C86, "M", "ъ"),
+ (0x1C87, "M", "ѣ"),
+ (0x1C88, "M", "ꙋ"),
+ (0x1C89, "M", ""),
+ (0x1C8A, "V"),
+ (0x1C8B, "X"),
+ (0x1C90, "M", "ა"),
+ (0x1C91, "M", "ბ"),
+ (0x1C92, "M", "გ"),
+ (0x1C93, "M", "დ"),
+ (0x1C94, "M", "ე"),
+ (0x1C95, "M", "ვ"),
+ (0x1C96, "M", "ზ"),
+ (0x1C97, "M", "თ"),
+ (0x1C98, "M", "ი"),
+ (0x1C99, "M", "კ"),
+ (0x1C9A, "M", "ლ"),
+ (0x1C9B, "M", "მ"),
+ (0x1C9C, "M", "ნ"),
+ (0x1C9D, "M", "ო"),
+ (0x1C9E, "M", "პ"),
+ (0x1C9F, "M", "ჟ"),
+ (0x1CA0, "M", "რ"),
+ (0x1CA1, "M", "ს"),
+ (0x1CA2, "M", "ტ"),
+ (0x1CA3, "M", "უ"),
+ (0x1CA4, "M", "ფ"),
+ (0x1CA5, "M", "ქ"),
+ (0x1CA6, "M", "ღ"),
+ (0x1CA7, "M", "ყ"),
+ (0x1CA8, "M", "შ"),
+ (0x1CA9, "M", "ჩ"),
+ (0x1CAA, "M", "ც"),
+ (0x1CAB, "M", "ძ"),
+ (0x1CAC, "M", "წ"),
+ (0x1CAD, "M", "ჭ"),
+ (0x1CAE, "M", "ხ"),
+ (0x1CAF, "M", "ჯ"),
+ (0x1CB0, "M", "ჰ"),
+ (0x1CB1, "M", "ჱ"),
+ (0x1CB2, "M", "ჲ"),
+ (0x1CB3, "M", "ჳ"),
+ (0x1CB4, "M", "ჴ"),
+ (0x1CB5, "M", "ჵ"),
+ (0x1CB6, "M", "ჶ"),
+ (0x1CB7, "M", "ჷ"),
+ (0x1CB8, "M", "ჸ"),
+ (0x1CB9, "M", "ჹ"),
+ (0x1CBA, "M", "ჺ"),
+ (0x1CBB, "X"),
+ (0x1CBD, "M", "ჽ"),
+ (0x1CBE, "M", "ჾ"),
+ (0x1CBF, "M", "ჿ"),
+ (0x1CC0, "V"),
+ (0x1CC8, "X"),
+ (0x1CD0, "V"),
+ (0x1CFB, "X"),
+ (0x1D00, "V"),
+ (0x1D2C, "M", "a"),
+ (0x1D2D, "M", "æ"),
+ (0x1D2E, "M", "b"),
+ (0x1D2F, "V"),
+ (0x1D30, "M", "d"),
+ (0x1D31, "M", "e"),
+ ]
+
+
+def _seg_16() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D32, "M", "ǝ"),
+ (0x1D33, "M", "g"),
+ (0x1D34, "M", "h"),
+ (0x1D35, "M", "i"),
+ (0x1D36, "M", "j"),
+ (0x1D37, "M", "k"),
+ (0x1D38, "M", "l"),
+ (0x1D39, "M", "m"),
+ (0x1D3A, "M", "n"),
+ (0x1D3B, "V"),
+ (0x1D3C, "M", "o"),
+ (0x1D3D, "M", "ȣ"),
+ (0x1D3E, "M", "p"),
+ (0x1D3F, "M", "r"),
+ (0x1D40, "M", "t"),
+ (0x1D41, "M", "u"),
+ (0x1D42, "M", "w"),
+ (0x1D43, "M", "a"),
+ (0x1D44, "M", "ɐ"),
+ (0x1D45, "M", "ɑ"),
+ (0x1D46, "M", "ᴂ"),
+ (0x1D47, "M", "b"),
+ (0x1D48, "M", "d"),
+ (0x1D49, "M", "e"),
+ (0x1D4A, "M", "ə"),
+ (0x1D4B, "M", "ɛ"),
+ (0x1D4C, "M", "ɜ"),
+ (0x1D4D, "M", "g"),
+ (0x1D4E, "V"),
+ (0x1D4F, "M", "k"),
+ (0x1D50, "M", "m"),
+ (0x1D51, "M", "ŋ"),
+ (0x1D52, "M", "o"),
+ (0x1D53, "M", "ɔ"),
+ (0x1D54, "M", "ᴖ"),
+ (0x1D55, "M", "ᴗ"),
+ (0x1D56, "M", "p"),
+ (0x1D57, "M", "t"),
+ (0x1D58, "M", "u"),
+ (0x1D59, "M", "ᴝ"),
+ (0x1D5A, "M", "ɯ"),
+ (0x1D5B, "M", "v"),
+ (0x1D5C, "M", "ᴥ"),
+ (0x1D5D, "M", "β"),
+ (0x1D5E, "M", "γ"),
+ (0x1D5F, "M", "δ"),
+ (0x1D60, "M", "φ"),
+ (0x1D61, "M", "χ"),
+ (0x1D62, "M", "i"),
+ (0x1D63, "M", "r"),
+ (0x1D64, "M", "u"),
+ (0x1D65, "M", "v"),
+ (0x1D66, "M", "β"),
+ (0x1D67, "M", "γ"),
+ (0x1D68, "M", "ρ"),
+ (0x1D69, "M", "φ"),
+ (0x1D6A, "M", "χ"),
+ (0x1D6B, "V"),
+ (0x1D78, "M", "н"),
+ (0x1D79, "V"),
+ (0x1D9B, "M", "ɒ"),
+ (0x1D9C, "M", "c"),
+ (0x1D9D, "M", "ɕ"),
+ (0x1D9E, "M", "ð"),
+ (0x1D9F, "M", "ɜ"),
+ (0x1DA0, "M", "f"),
+ (0x1DA1, "M", "ɟ"),
+ (0x1DA2, "M", "ɡ"),
+ (0x1DA3, "M", "ɥ"),
+ (0x1DA4, "M", "ɨ"),
+ (0x1DA5, "M", "ɩ"),
+ (0x1DA6, "M", "ɪ"),
+ (0x1DA7, "M", "ᵻ"),
+ (0x1DA8, "M", "ʝ"),
+ (0x1DA9, "M", "ɭ"),
+ (0x1DAA, "M", "ᶅ"),
+ (0x1DAB, "M", "ʟ"),
+ (0x1DAC, "M", "ɱ"),
+ (0x1DAD, "M", "ɰ"),
+ (0x1DAE, "M", "ɲ"),
+ (0x1DAF, "M", "ɳ"),
+ (0x1DB0, "M", "ɴ"),
+ (0x1DB1, "M", "ɵ"),
+ (0x1DB2, "M", "ɸ"),
+ (0x1DB3, "M", "ʂ"),
+ (0x1DB4, "M", "ʃ"),
+ (0x1DB5, "M", "ƫ"),
+ (0x1DB6, "M", "ʉ"),
+ (0x1DB7, "M", "ʊ"),
+ (0x1DB8, "M", "ᴜ"),
+ (0x1DB9, "M", "ʋ"),
+ (0x1DBA, "M", "ʌ"),
+ (0x1DBB, "M", "z"),
+ (0x1DBC, "M", "ʐ"),
+ (0x1DBD, "M", "ʑ"),
+ (0x1DBE, "M", "ʒ"),
+ (0x1DBF, "M", "θ"),
+ (0x1DC0, "V"),
+ (0x1E00, "M", "ḁ"),
+ (0x1E01, "V"),
+ ]
+
+
+def _seg_17() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E02, "M", "ḃ"),
+ (0x1E03, "V"),
+ (0x1E04, "M", "ḅ"),
+ (0x1E05, "V"),
+ (0x1E06, "M", "ḇ"),
+ (0x1E07, "V"),
+ (0x1E08, "M", "ḉ"),
+ (0x1E09, "V"),
+ (0x1E0A, "M", "ḋ"),
+ (0x1E0B, "V"),
+ (0x1E0C, "M", "ḍ"),
+ (0x1E0D, "V"),
+ (0x1E0E, "M", "ḏ"),
+ (0x1E0F, "V"),
+ (0x1E10, "M", "ḑ"),
+ (0x1E11, "V"),
+ (0x1E12, "M", "ḓ"),
+ (0x1E13, "V"),
+ (0x1E14, "M", "ḕ"),
+ (0x1E15, "V"),
+ (0x1E16, "M", "ḗ"),
+ (0x1E17, "V"),
+ (0x1E18, "M", "ḙ"),
+ (0x1E19, "V"),
+ (0x1E1A, "M", "ḛ"),
+ (0x1E1B, "V"),
+ (0x1E1C, "M", "ḝ"),
+ (0x1E1D, "V"),
+ (0x1E1E, "M", "ḟ"),
+ (0x1E1F, "V"),
+ (0x1E20, "M", "ḡ"),
+ (0x1E21, "V"),
+ (0x1E22, "M", "ḣ"),
+ (0x1E23, "V"),
+ (0x1E24, "M", "ḥ"),
+ (0x1E25, "V"),
+ (0x1E26, "M", "ḧ"),
+ (0x1E27, "V"),
+ (0x1E28, "M", "ḩ"),
+ (0x1E29, "V"),
+ (0x1E2A, "M", "ḫ"),
+ (0x1E2B, "V"),
+ (0x1E2C, "M", "ḭ"),
+ (0x1E2D, "V"),
+ (0x1E2E, "M", "ḯ"),
+ (0x1E2F, "V"),
+ (0x1E30, "M", "ḱ"),
+ (0x1E31, "V"),
+ (0x1E32, "M", "ḳ"),
+ (0x1E33, "V"),
+ (0x1E34, "M", "ḵ"),
+ (0x1E35, "V"),
+ (0x1E36, "M", "ḷ"),
+ (0x1E37, "V"),
+ (0x1E38, "M", "ḹ"),
+ (0x1E39, "V"),
+ (0x1E3A, "M", "ḻ"),
+ (0x1E3B, "V"),
+ (0x1E3C, "M", "ḽ"),
+ (0x1E3D, "V"),
+ (0x1E3E, "M", "ḿ"),
+ (0x1E3F, "V"),
+ (0x1E40, "M", "ṁ"),
+ (0x1E41, "V"),
+ (0x1E42, "M", "ṃ"),
+ (0x1E43, "V"),
+ (0x1E44, "M", "ṅ"),
+ (0x1E45, "V"),
+ (0x1E46, "M", "ṇ"),
+ (0x1E47, "V"),
+ (0x1E48, "M", "ṉ"),
+ (0x1E49, "V"),
+ (0x1E4A, "M", "ṋ"),
+ (0x1E4B, "V"),
+ (0x1E4C, "M", "ṍ"),
+ (0x1E4D, "V"),
+ (0x1E4E, "M", "ṏ"),
+ (0x1E4F, "V"),
+ (0x1E50, "M", "ṑ"),
+ (0x1E51, "V"),
+ (0x1E52, "M", "ṓ"),
+ (0x1E53, "V"),
+ (0x1E54, "M", "ṕ"),
+ (0x1E55, "V"),
+ (0x1E56, "M", "ṗ"),
+ (0x1E57, "V"),
+ (0x1E58, "M", "ṙ"),
+ (0x1E59, "V"),
+ (0x1E5A, "M", "ṛ"),
+ (0x1E5B, "V"),
+ (0x1E5C, "M", "ṝ"),
+ (0x1E5D, "V"),
+ (0x1E5E, "M", "ṟ"),
+ (0x1E5F, "V"),
+ (0x1E60, "M", "ṡ"),
+ (0x1E61, "V"),
+ (0x1E62, "M", "ṣ"),
+ (0x1E63, "V"),
+ (0x1E64, "M", "ṥ"),
+ (0x1E65, "V"),
+ ]
+
+
+def _seg_18() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E66, "M", "ṧ"),
+ (0x1E67, "V"),
+ (0x1E68, "M", "ṩ"),
+ (0x1E69, "V"),
+ (0x1E6A, "M", "ṫ"),
+ (0x1E6B, "V"),
+ (0x1E6C, "M", "ṭ"),
+ (0x1E6D, "V"),
+ (0x1E6E, "M", "ṯ"),
+ (0x1E6F, "V"),
+ (0x1E70, "M", "ṱ"),
+ (0x1E71, "V"),
+ (0x1E72, "M", "ṳ"),
+ (0x1E73, "V"),
+ (0x1E74, "M", "ṵ"),
+ (0x1E75, "V"),
+ (0x1E76, "M", "ṷ"),
+ (0x1E77, "V"),
+ (0x1E78, "M", "ṹ"),
+ (0x1E79, "V"),
+ (0x1E7A, "M", "ṻ"),
+ (0x1E7B, "V"),
+ (0x1E7C, "M", "ṽ"),
+ (0x1E7D, "V"),
+ (0x1E7E, "M", "ṿ"),
+ (0x1E7F, "V"),
+ (0x1E80, "M", "ẁ"),
+ (0x1E81, "V"),
+ (0x1E82, "M", "ẃ"),
+ (0x1E83, "V"),
+ (0x1E84, "M", "ẅ"),
+ (0x1E85, "V"),
+ (0x1E86, "M", "ẇ"),
+ (0x1E87, "V"),
+ (0x1E88, "M", "ẉ"),
+ (0x1E89, "V"),
+ (0x1E8A, "M", "ẋ"),
+ (0x1E8B, "V"),
+ (0x1E8C, "M", "ẍ"),
+ (0x1E8D, "V"),
+ (0x1E8E, "M", "ẏ"),
+ (0x1E8F, "V"),
+ (0x1E90, "M", "ẑ"),
+ (0x1E91, "V"),
+ (0x1E92, "M", "ẓ"),
+ (0x1E93, "V"),
+ (0x1E94, "M", "ẕ"),
+ (0x1E95, "V"),
+ (0x1E9A, "M", "aʾ"),
+ (0x1E9B, "M", "ṡ"),
+ (0x1E9C, "V"),
+ (0x1E9E, "M", "ß"),
+ (0x1E9F, "V"),
+ (0x1EA0, "M", "ạ"),
+ (0x1EA1, "V"),
+ (0x1EA2, "M", "ả"),
+ (0x1EA3, "V"),
+ (0x1EA4, "M", "ấ"),
+ (0x1EA5, "V"),
+ (0x1EA6, "M", "ầ"),
+ (0x1EA7, "V"),
+ (0x1EA8, "M", "ẩ"),
+ (0x1EA9, "V"),
+ (0x1EAA, "M", "ẫ"),
+ (0x1EAB, "V"),
+ (0x1EAC, "M", "ậ"),
+ (0x1EAD, "V"),
+ (0x1EAE, "M", "ắ"),
+ (0x1EAF, "V"),
+ (0x1EB0, "M", "ằ"),
+ (0x1EB1, "V"),
+ (0x1EB2, "M", "ẳ"),
+ (0x1EB3, "V"),
+ (0x1EB4, "M", "ẵ"),
+ (0x1EB5, "V"),
+ (0x1EB6, "M", "ặ"),
+ (0x1EB7, "V"),
+ (0x1EB8, "M", "ẹ"),
+ (0x1EB9, "V"),
+ (0x1EBA, "M", "ẻ"),
+ (0x1EBB, "V"),
+ (0x1EBC, "M", "ẽ"),
+ (0x1EBD, "V"),
+ (0x1EBE, "M", "ế"),
+ (0x1EBF, "V"),
+ (0x1EC0, "M", "ề"),
+ (0x1EC1, "V"),
+ (0x1EC2, "M", "ể"),
+ (0x1EC3, "V"),
+ (0x1EC4, "M", "ễ"),
+ (0x1EC5, "V"),
+ (0x1EC6, "M", "ệ"),
+ (0x1EC7, "V"),
+ (0x1EC8, "M", "ỉ"),
+ (0x1EC9, "V"),
+ (0x1ECA, "M", "ị"),
+ (0x1ECB, "V"),
+ (0x1ECC, "M", "ọ"),
+ (0x1ECD, "V"),
+ (0x1ECE, "M", "ỏ"),
+ ]
+
+
+def _seg_19() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1ECF, "V"),
+ (0x1ED0, "M", "ố"),
+ (0x1ED1, "V"),
+ (0x1ED2, "M", "ồ"),
+ (0x1ED3, "V"),
+ (0x1ED4, "M", "ổ"),
+ (0x1ED5, "V"),
+ (0x1ED6, "M", "ỗ"),
+ (0x1ED7, "V"),
+ (0x1ED8, "M", "ộ"),
+ (0x1ED9, "V"),
+ (0x1EDA, "M", "ớ"),
+ (0x1EDB, "V"),
+ (0x1EDC, "M", "ờ"),
+ (0x1EDD, "V"),
+ (0x1EDE, "M", "ở"),
+ (0x1EDF, "V"),
+ (0x1EE0, "M", "ỡ"),
+ (0x1EE1, "V"),
+ (0x1EE2, "M", "ợ"),
+ (0x1EE3, "V"),
+ (0x1EE4, "M", "ụ"),
+ (0x1EE5, "V"),
+ (0x1EE6, "M", "ủ"),
+ (0x1EE7, "V"),
+ (0x1EE8, "M", "ứ"),
+ (0x1EE9, "V"),
+ (0x1EEA, "M", "ừ"),
+ (0x1EEB, "V"),
+ (0x1EEC, "M", "ử"),
+ (0x1EED, "V"),
+ (0x1EEE, "M", "ữ"),
+ (0x1EEF, "V"),
+ (0x1EF0, "M", "ự"),
+ (0x1EF1, "V"),
+ (0x1EF2, "M", "ỳ"),
+ (0x1EF3, "V"),
+ (0x1EF4, "M", "ỵ"),
+ (0x1EF5, "V"),
+ (0x1EF6, "M", "ỷ"),
+ (0x1EF7, "V"),
+ (0x1EF8, "M", "ỹ"),
+ (0x1EF9, "V"),
+ (0x1EFA, "M", "ỻ"),
+ (0x1EFB, "V"),
+ (0x1EFC, "M", "ỽ"),
+ (0x1EFD, "V"),
+ (0x1EFE, "M", "ỿ"),
+ (0x1EFF, "V"),
+ (0x1F08, "M", "ἀ"),
+ (0x1F09, "M", "ἁ"),
+ (0x1F0A, "M", "ἂ"),
+ (0x1F0B, "M", "ἃ"),
+ (0x1F0C, "M", "ἄ"),
+ (0x1F0D, "M", "ἅ"),
+ (0x1F0E, "M", "ἆ"),
+ (0x1F0F, "M", "ἇ"),
+ (0x1F10, "V"),
+ (0x1F16, "X"),
+ (0x1F18, "M", "ἐ"),
+ (0x1F19, "M", "ἑ"),
+ (0x1F1A, "M", "ἒ"),
+ (0x1F1B, "M", "ἓ"),
+ (0x1F1C, "M", "ἔ"),
+ (0x1F1D, "M", "ἕ"),
+ (0x1F1E, "X"),
+ (0x1F20, "V"),
+ (0x1F28, "M", "ἠ"),
+ (0x1F29, "M", "ἡ"),
+ (0x1F2A, "M", "ἢ"),
+ (0x1F2B, "M", "ἣ"),
+ (0x1F2C, "M", "ἤ"),
+ (0x1F2D, "M", "ἥ"),
+ (0x1F2E, "M", "ἦ"),
+ (0x1F2F, "M", "ἧ"),
+ (0x1F30, "V"),
+ (0x1F38, "M", "ἰ"),
+ (0x1F39, "M", "ἱ"),
+ (0x1F3A, "M", "ἲ"),
+ (0x1F3B, "M", "ἳ"),
+ (0x1F3C, "M", "ἴ"),
+ (0x1F3D, "M", "ἵ"),
+ (0x1F3E, "M", "ἶ"),
+ (0x1F3F, "M", "ἷ"),
+ (0x1F40, "V"),
+ (0x1F46, "X"),
+ (0x1F48, "M", "ὀ"),
+ (0x1F49, "M", "ὁ"),
+ (0x1F4A, "M", "ὂ"),
+ (0x1F4B, "M", "ὃ"),
+ (0x1F4C, "M", "ὄ"),
+ (0x1F4D, "M", "ὅ"),
+ (0x1F4E, "X"),
+ (0x1F50, "V"),
+ (0x1F58, "X"),
+ (0x1F59, "M", "ὑ"),
+ (0x1F5A, "X"),
+ (0x1F5B, "M", "ὓ"),
+ (0x1F5C, "X"),
+ (0x1F5D, "M", "ὕ"),
+ ]
+
+
+def _seg_20() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1F5E, "X"),
+ (0x1F5F, "M", "ὗ"),
+ (0x1F60, "V"),
+ (0x1F68, "M", "ὠ"),
+ (0x1F69, "M", "ὡ"),
+ (0x1F6A, "M", "ὢ"),
+ (0x1F6B, "M", "ὣ"),
+ (0x1F6C, "M", "ὤ"),
+ (0x1F6D, "M", "ὥ"),
+ (0x1F6E, "M", "ὦ"),
+ (0x1F6F, "M", "ὧ"),
+ (0x1F70, "V"),
+ (0x1F71, "M", "ά"),
+ (0x1F72, "V"),
+ (0x1F73, "M", "έ"),
+ (0x1F74, "V"),
+ (0x1F75, "M", "ή"),
+ (0x1F76, "V"),
+ (0x1F77, "M", "ί"),
+ (0x1F78, "V"),
+ (0x1F79, "M", "ό"),
+ (0x1F7A, "V"),
+ (0x1F7B, "M", "ύ"),
+ (0x1F7C, "V"),
+ (0x1F7D, "M", "ώ"),
+ (0x1F7E, "X"),
+ (0x1F80, "M", "ἀι"),
+ (0x1F81, "M", "ἁι"),
+ (0x1F82, "M", "ἂι"),
+ (0x1F83, "M", "ἃι"),
+ (0x1F84, "M", "ἄι"),
+ (0x1F85, "M", "ἅι"),
+ (0x1F86, "M", "ἆι"),
+ (0x1F87, "M", "ἇι"),
+ (0x1F88, "M", "ἀι"),
+ (0x1F89, "M", "ἁι"),
+ (0x1F8A, "M", "ἂι"),
+ (0x1F8B, "M", "ἃι"),
+ (0x1F8C, "M", "ἄι"),
+ (0x1F8D, "M", "ἅι"),
+ (0x1F8E, "M", "ἆι"),
+ (0x1F8F, "M", "ἇι"),
+ (0x1F90, "M", "ἠι"),
+ (0x1F91, "M", "ἡι"),
+ (0x1F92, "M", "ἢι"),
+ (0x1F93, "M", "ἣι"),
+ (0x1F94, "M", "ἤι"),
+ (0x1F95, "M", "ἥι"),
+ (0x1F96, "M", "ἦι"),
+ (0x1F97, "M", "ἧι"),
+ (0x1F98, "M", "ἠι"),
+ (0x1F99, "M", "ἡι"),
+ (0x1F9A, "M", "ἢι"),
+ (0x1F9B, "M", "ἣι"),
+ (0x1F9C, "M", "ἤι"),
+ (0x1F9D, "M", "ἥι"),
+ (0x1F9E, "M", "ἦι"),
+ (0x1F9F, "M", "ἧι"),
+ (0x1FA0, "M", "ὠι"),
+ (0x1FA1, "M", "ὡι"),
+ (0x1FA2, "M", "ὢι"),
+ (0x1FA3, "M", "ὣι"),
+ (0x1FA4, "M", "ὤι"),
+ (0x1FA5, "M", "ὥι"),
+ (0x1FA6, "M", "ὦι"),
+ (0x1FA7, "M", "ὧι"),
+ (0x1FA8, "M", "ὠι"),
+ (0x1FA9, "M", "ὡι"),
+ (0x1FAA, "M", "ὢι"),
+ (0x1FAB, "M", "ὣι"),
+ (0x1FAC, "M", "ὤι"),
+ (0x1FAD, "M", "ὥι"),
+ (0x1FAE, "M", "ὦι"),
+ (0x1FAF, "M", "ὧι"),
+ (0x1FB0, "V"),
+ (0x1FB2, "M", "ὰι"),
+ (0x1FB3, "M", "αι"),
+ (0x1FB4, "M", "άι"),
+ (0x1FB5, "X"),
+ (0x1FB6, "V"),
+ (0x1FB7, "M", "ᾶι"),
+ (0x1FB8, "M", "ᾰ"),
+ (0x1FB9, "M", "ᾱ"),
+ (0x1FBA, "M", "ὰ"),
+ (0x1FBB, "M", "ά"),
+ (0x1FBC, "M", "αι"),
+ (0x1FBD, "M", " ̓"),
+ (0x1FBE, "M", "ι"),
+ (0x1FBF, "M", " ̓"),
+ (0x1FC0, "M", " ͂"),
+ (0x1FC1, "M", " ̈͂"),
+ (0x1FC2, "M", "ὴι"),
+ (0x1FC3, "M", "ηι"),
+ (0x1FC4, "M", "ήι"),
+ (0x1FC5, "X"),
+ (0x1FC6, "V"),
+ (0x1FC7, "M", "ῆι"),
+ (0x1FC8, "M", "ὲ"),
+ (0x1FC9, "M", "έ"),
+ (0x1FCA, "M", "ὴ"),
+ ]
+
+
+def _seg_21() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1FCB, "M", "ή"),
+ (0x1FCC, "M", "ηι"),
+ (0x1FCD, "M", " ̓̀"),
+ (0x1FCE, "M", " ̓́"),
+ (0x1FCF, "M", " ̓͂"),
+ (0x1FD0, "V"),
+ (0x1FD3, "M", "ΐ"),
+ (0x1FD4, "X"),
+ (0x1FD6, "V"),
+ (0x1FD8, "M", "ῐ"),
+ (0x1FD9, "M", "ῑ"),
+ (0x1FDA, "M", "ὶ"),
+ (0x1FDB, "M", "ί"),
+ (0x1FDC, "X"),
+ (0x1FDD, "M", " ̔̀"),
+ (0x1FDE, "M", " ̔́"),
+ (0x1FDF, "M", " ̔͂"),
+ (0x1FE0, "V"),
+ (0x1FE3, "M", "ΰ"),
+ (0x1FE4, "V"),
+ (0x1FE8, "M", "ῠ"),
+ (0x1FE9, "M", "ῡ"),
+ (0x1FEA, "M", "ὺ"),
+ (0x1FEB, "M", "ύ"),
+ (0x1FEC, "M", "ῥ"),
+ (0x1FED, "M", " ̈̀"),
+ (0x1FEE, "M", " ̈́"),
+ (0x1FEF, "M", "`"),
+ (0x1FF0, "X"),
+ (0x1FF2, "M", "ὼι"),
+ (0x1FF3, "M", "ωι"),
+ (0x1FF4, "M", "ώι"),
+ (0x1FF5, "X"),
+ (0x1FF6, "V"),
+ (0x1FF7, "M", "ῶι"),
+ (0x1FF8, "M", "ὸ"),
+ (0x1FF9, "M", "ό"),
+ (0x1FFA, "M", "ὼ"),
+ (0x1FFB, "M", "ώ"),
+ (0x1FFC, "M", "ωι"),
+ (0x1FFD, "M", " ́"),
+ (0x1FFE, "M", " ̔"),
+ (0x1FFF, "X"),
+ (0x2000, "M", " "),
+ (0x200B, "I"),
+ (0x200C, "D", ""),
+ (0x200E, "X"),
+ (0x2010, "V"),
+ (0x2011, "M", "‐"),
+ (0x2012, "V"),
+ (0x2017, "M", " ̳"),
+ (0x2018, "V"),
+ (0x2024, "X"),
+ (0x2027, "V"),
+ (0x2028, "X"),
+ (0x202F, "M", " "),
+ (0x2030, "V"),
+ (0x2033, "M", "′′"),
+ (0x2034, "M", "′′′"),
+ (0x2035, "V"),
+ (0x2036, "M", "‵‵"),
+ (0x2037, "M", "‵‵‵"),
+ (0x2038, "V"),
+ (0x203C, "M", "!!"),
+ (0x203D, "V"),
+ (0x203E, "M", " ̅"),
+ (0x203F, "V"),
+ (0x2047, "M", "??"),
+ (0x2048, "M", "?!"),
+ (0x2049, "M", "!?"),
+ (0x204A, "V"),
+ (0x2057, "M", "′′′′"),
+ (0x2058, "V"),
+ (0x205F, "M", " "),
+ (0x2060, "I"),
+ (0x2065, "X"),
+ (0x206A, "I"),
+ (0x2070, "M", "0"),
+ (0x2071, "M", "i"),
+ (0x2072, "X"),
+ (0x2074, "M", "4"),
+ (0x2075, "M", "5"),
+ (0x2076, "M", "6"),
+ (0x2077, "M", "7"),
+ (0x2078, "M", "8"),
+ (0x2079, "M", "9"),
+ (0x207A, "M", "+"),
+ (0x207B, "M", "−"),
+ (0x207C, "M", "="),
+ (0x207D, "M", "("),
+ (0x207E, "M", ")"),
+ (0x207F, "M", "n"),
+ (0x2080, "M", "0"),
+ (0x2081, "M", "1"),
+ (0x2082, "M", "2"),
+ (0x2083, "M", "3"),
+ (0x2084, "M", "4"),
+ (0x2085, "M", "5"),
+ (0x2086, "M", "6"),
+ (0x2087, "M", "7"),
+ ]
+
+
+def _seg_22() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2088, "M", "8"),
+ (0x2089, "M", "9"),
+ (0x208A, "M", "+"),
+ (0x208B, "M", "−"),
+ (0x208C, "M", "="),
+ (0x208D, "M", "("),
+ (0x208E, "M", ")"),
+ (0x208F, "X"),
+ (0x2090, "M", "a"),
+ (0x2091, "M", "e"),
+ (0x2092, "M", "o"),
+ (0x2093, "M", "x"),
+ (0x2094, "M", "ə"),
+ (0x2095, "M", "h"),
+ (0x2096, "M", "k"),
+ (0x2097, "M", "l"),
+ (0x2098, "M", "m"),
+ (0x2099, "M", "n"),
+ (0x209A, "M", "p"),
+ (0x209B, "M", "s"),
+ (0x209C, "M", "t"),
+ (0x209D, "X"),
+ (0x20A0, "V"),
+ (0x20A8, "M", "rs"),
+ (0x20A9, "V"),
+ (0x20C1, "X"),
+ (0x20D0, "V"),
+ (0x20F1, "X"),
+ (0x2100, "M", "a/c"),
+ (0x2101, "M", "a/s"),
+ (0x2102, "M", "c"),
+ (0x2103, "M", "°c"),
+ (0x2104, "V"),
+ (0x2105, "M", "c/o"),
+ (0x2106, "M", "c/u"),
+ (0x2107, "M", "ɛ"),
+ (0x2108, "V"),
+ (0x2109, "M", "°f"),
+ (0x210A, "M", "g"),
+ (0x210B, "M", "h"),
+ (0x210F, "M", "ħ"),
+ (0x2110, "M", "i"),
+ (0x2112, "M", "l"),
+ (0x2114, "V"),
+ (0x2115, "M", "n"),
+ (0x2116, "M", "no"),
+ (0x2117, "V"),
+ (0x2119, "M", "p"),
+ (0x211A, "M", "q"),
+ (0x211B, "M", "r"),
+ (0x211E, "V"),
+ (0x2120, "M", "sm"),
+ (0x2121, "M", "tel"),
+ (0x2122, "M", "tm"),
+ (0x2123, "V"),
+ (0x2124, "M", "z"),
+ (0x2125, "V"),
+ (0x2126, "M", "ω"),
+ (0x2127, "V"),
+ (0x2128, "M", "z"),
+ (0x2129, "V"),
+ (0x212A, "M", "k"),
+ (0x212B, "M", "å"),
+ (0x212C, "M", "b"),
+ (0x212D, "M", "c"),
+ (0x212E, "V"),
+ (0x212F, "M", "e"),
+ (0x2131, "M", "f"),
+ (0x2132, "M", "ⅎ"),
+ (0x2133, "M", "m"),
+ (0x2134, "M", "o"),
+ (0x2135, "M", "א"),
+ (0x2136, "M", "ב"),
+ (0x2137, "M", "ג"),
+ (0x2138, "M", "ד"),
+ (0x2139, "M", "i"),
+ (0x213A, "V"),
+ (0x213B, "M", "fax"),
+ (0x213C, "M", "π"),
+ (0x213D, "M", "γ"),
+ (0x213F, "M", "π"),
+ (0x2140, "M", "∑"),
+ (0x2141, "V"),
+ (0x2145, "M", "d"),
+ (0x2147, "M", "e"),
+ (0x2148, "M", "i"),
+ (0x2149, "M", "j"),
+ (0x214A, "V"),
+ (0x2150, "M", "1⁄7"),
+ (0x2151, "M", "1⁄9"),
+ (0x2152, "M", "1⁄10"),
+ (0x2153, "M", "1⁄3"),
+ (0x2154, "M", "2⁄3"),
+ (0x2155, "M", "1⁄5"),
+ (0x2156, "M", "2⁄5"),
+ (0x2157, "M", "3⁄5"),
+ (0x2158, "M", "4⁄5"),
+ (0x2159, "M", "1⁄6"),
+ (0x215A, "M", "5⁄6"),
+ (0x215B, "M", "1⁄8"),
+ ]
+
+
+def _seg_23() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x215C, "M", "3⁄8"),
+ (0x215D, "M", "5⁄8"),
+ (0x215E, "M", "7⁄8"),
+ (0x215F, "M", "1⁄"),
+ (0x2160, "M", "i"),
+ (0x2161, "M", "ii"),
+ (0x2162, "M", "iii"),
+ (0x2163, "M", "iv"),
+ (0x2164, "M", "v"),
+ (0x2165, "M", "vi"),
+ (0x2166, "M", "vii"),
+ (0x2167, "M", "viii"),
+ (0x2168, "M", "ix"),
+ (0x2169, "M", "x"),
+ (0x216A, "M", "xi"),
+ (0x216B, "M", "xii"),
+ (0x216C, "M", "l"),
+ (0x216D, "M", "c"),
+ (0x216E, "M", "d"),
+ (0x216F, "M", "m"),
+ (0x2170, "M", "i"),
+ (0x2171, "M", "ii"),
+ (0x2172, "M", "iii"),
+ (0x2173, "M", "iv"),
+ (0x2174, "M", "v"),
+ (0x2175, "M", "vi"),
+ (0x2176, "M", "vii"),
+ (0x2177, "M", "viii"),
+ (0x2178, "M", "ix"),
+ (0x2179, "M", "x"),
+ (0x217A, "M", "xi"),
+ (0x217B, "M", "xii"),
+ (0x217C, "M", "l"),
+ (0x217D, "M", "c"),
+ (0x217E, "M", "d"),
+ (0x217F, "M", "m"),
+ (0x2180, "V"),
+ (0x2183, "M", "ↄ"),
+ (0x2184, "V"),
+ (0x2189, "M", "0⁄3"),
+ (0x218A, "V"),
+ (0x218C, "X"),
+ (0x2190, "V"),
+ (0x222C, "M", "∫∫"),
+ (0x222D, "M", "∫∫∫"),
+ (0x222E, "V"),
+ (0x222F, "M", "∮∮"),
+ (0x2230, "M", "∮∮∮"),
+ (0x2231, "V"),
+ (0x2329, "M", "〈"),
+ (0x232A, "M", "〉"),
+ (0x232B, "V"),
+ (0x242A, "X"),
+ (0x2440, "V"),
+ (0x244B, "X"),
+ (0x2460, "M", "1"),
+ (0x2461, "M", "2"),
+ (0x2462, "M", "3"),
+ (0x2463, "M", "4"),
+ (0x2464, "M", "5"),
+ (0x2465, "M", "6"),
+ (0x2466, "M", "7"),
+ (0x2467, "M", "8"),
+ (0x2468, "M", "9"),
+ (0x2469, "M", "10"),
+ (0x246A, "M", "11"),
+ (0x246B, "M", "12"),
+ (0x246C, "M", "13"),
+ (0x246D, "M", "14"),
+ (0x246E, "M", "15"),
+ (0x246F, "M", "16"),
+ (0x2470, "M", "17"),
+ (0x2471, "M", "18"),
+ (0x2472, "M", "19"),
+ (0x2473, "M", "20"),
+ (0x2474, "M", "(1)"),
+ (0x2475, "M", "(2)"),
+ (0x2476, "M", "(3)"),
+ (0x2477, "M", "(4)"),
+ (0x2478, "M", "(5)"),
+ (0x2479, "M", "(6)"),
+ (0x247A, "M", "(7)"),
+ (0x247B, "M", "(8)"),
+ (0x247C, "M", "(9)"),
+ (0x247D, "M", "(10)"),
+ (0x247E, "M", "(11)"),
+ (0x247F, "M", "(12)"),
+ (0x2480, "M", "(13)"),
+ (0x2481, "M", "(14)"),
+ (0x2482, "M", "(15)"),
+ (0x2483, "M", "(16)"),
+ (0x2484, "M", "(17)"),
+ (0x2485, "M", "(18)"),
+ (0x2486, "M", "(19)"),
+ (0x2487, "M", "(20)"),
+ (0x2488, "X"),
+ (0x249C, "M", "(a)"),
+ (0x249D, "M", "(b)"),
+ (0x249E, "M", "(c)"),
+ (0x249F, "M", "(d)"),
+ ]
+
+
+def _seg_24() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x24A0, "M", "(e)"),
+ (0x24A1, "M", "(f)"),
+ (0x24A2, "M", "(g)"),
+ (0x24A3, "M", "(h)"),
+ (0x24A4, "M", "(i)"),
+ (0x24A5, "M", "(j)"),
+ (0x24A6, "M", "(k)"),
+ (0x24A7, "M", "(l)"),
+ (0x24A8, "M", "(m)"),
+ (0x24A9, "M", "(n)"),
+ (0x24AA, "M", "(o)"),
+ (0x24AB, "M", "(p)"),
+ (0x24AC, "M", "(q)"),
+ (0x24AD, "M", "(r)"),
+ (0x24AE, "M", "(s)"),
+ (0x24AF, "M", "(t)"),
+ (0x24B0, "M", "(u)"),
+ (0x24B1, "M", "(v)"),
+ (0x24B2, "M", "(w)"),
+ (0x24B3, "M", "(x)"),
+ (0x24B4, "M", "(y)"),
+ (0x24B5, "M", "(z)"),
+ (0x24B6, "M", "a"),
+ (0x24B7, "M", "b"),
+ (0x24B8, "M", "c"),
+ (0x24B9, "M", "d"),
+ (0x24BA, "M", "e"),
+ (0x24BB, "M", "f"),
+ (0x24BC, "M", "g"),
+ (0x24BD, "M", "h"),
+ (0x24BE, "M", "i"),
+ (0x24BF, "M", "j"),
+ (0x24C0, "M", "k"),
+ (0x24C1, "M", "l"),
+ (0x24C2, "M", "m"),
+ (0x24C3, "M", "n"),
+ (0x24C4, "M", "o"),
+ (0x24C5, "M", "p"),
+ (0x24C6, "M", "q"),
+ (0x24C7, "M", "r"),
+ (0x24C8, "M", "s"),
+ (0x24C9, "M", "t"),
+ (0x24CA, "M", "u"),
+ (0x24CB, "M", "v"),
+ (0x24CC, "M", "w"),
+ (0x24CD, "M", "x"),
+ (0x24CE, "M", "y"),
+ (0x24CF, "M", "z"),
+ (0x24D0, "M", "a"),
+ (0x24D1, "M", "b"),
+ (0x24D2, "M", "c"),
+ (0x24D3, "M", "d"),
+ (0x24D4, "M", "e"),
+ (0x24D5, "M", "f"),
+ (0x24D6, "M", "g"),
+ (0x24D7, "M", "h"),
+ (0x24D8, "M", "i"),
+ (0x24D9, "M", "j"),
+ (0x24DA, "M", "k"),
+ (0x24DB, "M", "l"),
+ (0x24DC, "M", "m"),
+ (0x24DD, "M", "n"),
+ (0x24DE, "M", "o"),
+ (0x24DF, "M", "p"),
+ (0x24E0, "M", "q"),
+ (0x24E1, "M", "r"),
+ (0x24E2, "M", "s"),
+ (0x24E3, "M", "t"),
+ (0x24E4, "M", "u"),
+ (0x24E5, "M", "v"),
+ (0x24E6, "M", "w"),
+ (0x24E7, "M", "x"),
+ (0x24E8, "M", "y"),
+ (0x24E9, "M", "z"),
+ (0x24EA, "M", "0"),
+ (0x24EB, "V"),
+ (0x2A0C, "M", "∫∫∫∫"),
+ (0x2A0D, "V"),
+ (0x2A74, "M", "::="),
+ (0x2A75, "M", "=="),
+ (0x2A76, "M", "==="),
+ (0x2A77, "V"),
+ (0x2ADC, "M", "⫝̸"),
+ (0x2ADD, "V"),
+ (0x2B74, "X"),
+ (0x2B76, "V"),
+ (0x2B96, "X"),
+ (0x2B97, "V"),
+ (0x2C00, "M", "ⰰ"),
+ (0x2C01, "M", "ⰱ"),
+ (0x2C02, "M", "ⰲ"),
+ (0x2C03, "M", "ⰳ"),
+ (0x2C04, "M", "ⰴ"),
+ (0x2C05, "M", "ⰵ"),
+ (0x2C06, "M", "ⰶ"),
+ (0x2C07, "M", "ⰷ"),
+ (0x2C08, "M", "ⰸ"),
+ (0x2C09, "M", "ⰹ"),
+ (0x2C0A, "M", "ⰺ"),
+ (0x2C0B, "M", "ⰻ"),
+ ]
+
+
+def _seg_25() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2C0C, "M", "ⰼ"),
+ (0x2C0D, "M", "ⰽ"),
+ (0x2C0E, "M", "ⰾ"),
+ (0x2C0F, "M", "ⰿ"),
+ (0x2C10, "M", "ⱀ"),
+ (0x2C11, "M", "ⱁ"),
+ (0x2C12, "M", "ⱂ"),
+ (0x2C13, "M", "ⱃ"),
+ (0x2C14, "M", "ⱄ"),
+ (0x2C15, "M", "ⱅ"),
+ (0x2C16, "M", "ⱆ"),
+ (0x2C17, "M", "ⱇ"),
+ (0x2C18, "M", "ⱈ"),
+ (0x2C19, "M", "ⱉ"),
+ (0x2C1A, "M", "ⱊ"),
+ (0x2C1B, "M", "ⱋ"),
+ (0x2C1C, "M", "ⱌ"),
+ (0x2C1D, "M", "ⱍ"),
+ (0x2C1E, "M", "ⱎ"),
+ (0x2C1F, "M", "ⱏ"),
+ (0x2C20, "M", "ⱐ"),
+ (0x2C21, "M", "ⱑ"),
+ (0x2C22, "M", "ⱒ"),
+ (0x2C23, "M", "ⱓ"),
+ (0x2C24, "M", "ⱔ"),
+ (0x2C25, "M", "ⱕ"),
+ (0x2C26, "M", "ⱖ"),
+ (0x2C27, "M", "ⱗ"),
+ (0x2C28, "M", "ⱘ"),
+ (0x2C29, "M", "ⱙ"),
+ (0x2C2A, "M", "ⱚ"),
+ (0x2C2B, "M", "ⱛ"),
+ (0x2C2C, "M", "ⱜ"),
+ (0x2C2D, "M", "ⱝ"),
+ (0x2C2E, "M", "ⱞ"),
+ (0x2C2F, "M", "ⱟ"),
+ (0x2C30, "V"),
+ (0x2C60, "M", "ⱡ"),
+ (0x2C61, "V"),
+ (0x2C62, "M", "ɫ"),
+ (0x2C63, "M", "ᵽ"),
+ (0x2C64, "M", "ɽ"),
+ (0x2C65, "V"),
+ (0x2C67, "M", "ⱨ"),
+ (0x2C68, "V"),
+ (0x2C69, "M", "ⱪ"),
+ (0x2C6A, "V"),
+ (0x2C6B, "M", "ⱬ"),
+ (0x2C6C, "V"),
+ (0x2C6D, "M", "ɑ"),
+ (0x2C6E, "M", "ɱ"),
+ (0x2C6F, "M", "ɐ"),
+ (0x2C70, "M", "ɒ"),
+ (0x2C71, "V"),
+ (0x2C72, "M", "ⱳ"),
+ (0x2C73, "V"),
+ (0x2C75, "M", "ⱶ"),
+ (0x2C76, "V"),
+ (0x2C7C, "M", "j"),
+ (0x2C7D, "M", "v"),
+ (0x2C7E, "M", "ȿ"),
+ (0x2C7F, "M", "ɀ"),
+ (0x2C80, "M", "ⲁ"),
+ (0x2C81, "V"),
+ (0x2C82, "M", "ⲃ"),
+ (0x2C83, "V"),
+ (0x2C84, "M", "ⲅ"),
+ (0x2C85, "V"),
+ (0x2C86, "M", "ⲇ"),
+ (0x2C87, "V"),
+ (0x2C88, "M", "ⲉ"),
+ (0x2C89, "V"),
+ (0x2C8A, "M", "ⲋ"),
+ (0x2C8B, "V"),
+ (0x2C8C, "M", "ⲍ"),
+ (0x2C8D, "V"),
+ (0x2C8E, "M", "ⲏ"),
+ (0x2C8F, "V"),
+ (0x2C90, "M", "ⲑ"),
+ (0x2C91, "V"),
+ (0x2C92, "M", "ⲓ"),
+ (0x2C93, "V"),
+ (0x2C94, "M", "ⲕ"),
+ (0x2C95, "V"),
+ (0x2C96, "M", "ⲗ"),
+ (0x2C97, "V"),
+ (0x2C98, "M", "ⲙ"),
+ (0x2C99, "V"),
+ (0x2C9A, "M", "ⲛ"),
+ (0x2C9B, "V"),
+ (0x2C9C, "M", "ⲝ"),
+ (0x2C9D, "V"),
+ (0x2C9E, "M", "ⲟ"),
+ (0x2C9F, "V"),
+ (0x2CA0, "M", "ⲡ"),
+ (0x2CA1, "V"),
+ (0x2CA2, "M", "ⲣ"),
+ (0x2CA3, "V"),
+ (0x2CA4, "M", "ⲥ"),
+ (0x2CA5, "V"),
+ ]
+
+
+def _seg_26() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2CA6, "M", "ⲧ"),
+ (0x2CA7, "V"),
+ (0x2CA8, "M", "ⲩ"),
+ (0x2CA9, "V"),
+ (0x2CAA, "M", "ⲫ"),
+ (0x2CAB, "V"),
+ (0x2CAC, "M", "ⲭ"),
+ (0x2CAD, "V"),
+ (0x2CAE, "M", "ⲯ"),
+ (0x2CAF, "V"),
+ (0x2CB0, "M", "ⲱ"),
+ (0x2CB1, "V"),
+ (0x2CB2, "M", "ⲳ"),
+ (0x2CB3, "V"),
+ (0x2CB4, "M", "ⲵ"),
+ (0x2CB5, "V"),
+ (0x2CB6, "M", "ⲷ"),
+ (0x2CB7, "V"),
+ (0x2CB8, "M", "ⲹ"),
+ (0x2CB9, "V"),
+ (0x2CBA, "M", "ⲻ"),
+ (0x2CBB, "V"),
+ (0x2CBC, "M", "ⲽ"),
+ (0x2CBD, "V"),
+ (0x2CBE, "M", "ⲿ"),
+ (0x2CBF, "V"),
+ (0x2CC0, "M", "ⳁ"),
+ (0x2CC1, "V"),
+ (0x2CC2, "M", "ⳃ"),
+ (0x2CC3, "V"),
+ (0x2CC4, "M", "ⳅ"),
+ (0x2CC5, "V"),
+ (0x2CC6, "M", "ⳇ"),
+ (0x2CC7, "V"),
+ (0x2CC8, "M", "ⳉ"),
+ (0x2CC9, "V"),
+ (0x2CCA, "M", "ⳋ"),
+ (0x2CCB, "V"),
+ (0x2CCC, "M", "ⳍ"),
+ (0x2CCD, "V"),
+ (0x2CCE, "M", "ⳏ"),
+ (0x2CCF, "V"),
+ (0x2CD0, "M", "ⳑ"),
+ (0x2CD1, "V"),
+ (0x2CD2, "M", "ⳓ"),
+ (0x2CD3, "V"),
+ (0x2CD4, "M", "ⳕ"),
+ (0x2CD5, "V"),
+ (0x2CD6, "M", "ⳗ"),
+ (0x2CD7, "V"),
+ (0x2CD8, "M", "ⳙ"),
+ (0x2CD9, "V"),
+ (0x2CDA, "M", "ⳛ"),
+ (0x2CDB, "V"),
+ (0x2CDC, "M", "ⳝ"),
+ (0x2CDD, "V"),
+ (0x2CDE, "M", "ⳟ"),
+ (0x2CDF, "V"),
+ (0x2CE0, "M", "ⳡ"),
+ (0x2CE1, "V"),
+ (0x2CE2, "M", "ⳣ"),
+ (0x2CE3, "V"),
+ (0x2CEB, "M", "ⳬ"),
+ (0x2CEC, "V"),
+ (0x2CED, "M", "ⳮ"),
+ (0x2CEE, "V"),
+ (0x2CF2, "M", "ⳳ"),
+ (0x2CF3, "V"),
+ (0x2CF4, "X"),
+ (0x2CF9, "V"),
+ (0x2D26, "X"),
+ (0x2D27, "V"),
+ (0x2D28, "X"),
+ (0x2D2D, "V"),
+ (0x2D2E, "X"),
+ (0x2D30, "V"),
+ (0x2D68, "X"),
+ (0x2D6F, "M", "ⵡ"),
+ (0x2D70, "V"),
+ (0x2D71, "X"),
+ (0x2D7F, "V"),
+ (0x2D97, "X"),
+ (0x2DA0, "V"),
+ (0x2DA7, "X"),
+ (0x2DA8, "V"),
+ (0x2DAF, "X"),
+ (0x2DB0, "V"),
+ (0x2DB7, "X"),
+ (0x2DB8, "V"),
+ (0x2DBF, "X"),
+ (0x2DC0, "V"),
+ (0x2DC7, "X"),
+ (0x2DC8, "V"),
+ (0x2DCF, "X"),
+ (0x2DD0, "V"),
+ (0x2DD7, "X"),
+ (0x2DD8, "V"),
+ (0x2DDF, "X"),
+ (0x2DE0, "V"),
+ (0x2E5E, "X"),
+ ]
+
+
+def _seg_27() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2E80, "V"),
+ (0x2E9A, "X"),
+ (0x2E9B, "V"),
+ (0x2E9F, "M", "母"),
+ (0x2EA0, "V"),
+ (0x2EF3, "M", "龟"),
+ (0x2EF4, "X"),
+ (0x2F00, "M", "一"),
+ (0x2F01, "M", "丨"),
+ (0x2F02, "M", "丶"),
+ (0x2F03, "M", "丿"),
+ (0x2F04, "M", "乙"),
+ (0x2F05, "M", "亅"),
+ (0x2F06, "M", "二"),
+ (0x2F07, "M", "亠"),
+ (0x2F08, "M", "人"),
+ (0x2F09, "M", "儿"),
+ (0x2F0A, "M", "入"),
+ (0x2F0B, "M", "八"),
+ (0x2F0C, "M", "冂"),
+ (0x2F0D, "M", "冖"),
+ (0x2F0E, "M", "冫"),
+ (0x2F0F, "M", "几"),
+ (0x2F10, "M", "凵"),
+ (0x2F11, "M", "刀"),
+ (0x2F12, "M", "力"),
+ (0x2F13, "M", "勹"),
+ (0x2F14, "M", "匕"),
+ (0x2F15, "M", "匚"),
+ (0x2F16, "M", "匸"),
+ (0x2F17, "M", "十"),
+ (0x2F18, "M", "卜"),
+ (0x2F19, "M", "卩"),
+ (0x2F1A, "M", "厂"),
+ (0x2F1B, "M", "厶"),
+ (0x2F1C, "M", "又"),
+ (0x2F1D, "M", "口"),
+ (0x2F1E, "M", "囗"),
+ (0x2F1F, "M", "土"),
+ (0x2F20, "M", "士"),
+ (0x2F21, "M", "夂"),
+ (0x2F22, "M", "夊"),
+ (0x2F23, "M", "夕"),
+ (0x2F24, "M", "大"),
+ (0x2F25, "M", "女"),
+ (0x2F26, "M", "子"),
+ (0x2F27, "M", "宀"),
+ (0x2F28, "M", "寸"),
+ (0x2F29, "M", "小"),
+ (0x2F2A, "M", "尢"),
+ (0x2F2B, "M", "尸"),
+ (0x2F2C, "M", "屮"),
+ (0x2F2D, "M", "山"),
+ (0x2F2E, "M", "巛"),
+ (0x2F2F, "M", "工"),
+ (0x2F30, "M", "己"),
+ (0x2F31, "M", "巾"),
+ (0x2F32, "M", "干"),
+ (0x2F33, "M", "幺"),
+ (0x2F34, "M", "广"),
+ (0x2F35, "M", "廴"),
+ (0x2F36, "M", "廾"),
+ (0x2F37, "M", "弋"),
+ (0x2F38, "M", "弓"),
+ (0x2F39, "M", "彐"),
+ (0x2F3A, "M", "彡"),
+ (0x2F3B, "M", "彳"),
+ (0x2F3C, "M", "心"),
+ (0x2F3D, "M", "戈"),
+ (0x2F3E, "M", "戶"),
+ (0x2F3F, "M", "手"),
+ (0x2F40, "M", "支"),
+ (0x2F41, "M", "攴"),
+ (0x2F42, "M", "文"),
+ (0x2F43, "M", "斗"),
+ (0x2F44, "M", "斤"),
+ (0x2F45, "M", "方"),
+ (0x2F46, "M", "无"),
+ (0x2F47, "M", "日"),
+ (0x2F48, "M", "曰"),
+ (0x2F49, "M", "月"),
+ (0x2F4A, "M", "木"),
+ (0x2F4B, "M", "欠"),
+ (0x2F4C, "M", "止"),
+ (0x2F4D, "M", "歹"),
+ (0x2F4E, "M", "殳"),
+ (0x2F4F, "M", "毋"),
+ (0x2F50, "M", "比"),
+ (0x2F51, "M", "毛"),
+ (0x2F52, "M", "氏"),
+ (0x2F53, "M", "气"),
+ (0x2F54, "M", "水"),
+ (0x2F55, "M", "火"),
+ (0x2F56, "M", "爪"),
+ (0x2F57, "M", "父"),
+ (0x2F58, "M", "爻"),
+ (0x2F59, "M", "爿"),
+ (0x2F5A, "M", "片"),
+ (0x2F5B, "M", "牙"),
+ (0x2F5C, "M", "牛"),
+ ]
+
+
+def _seg_28() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F5D, "M", "犬"),
+ (0x2F5E, "M", "玄"),
+ (0x2F5F, "M", "玉"),
+ (0x2F60, "M", "瓜"),
+ (0x2F61, "M", "瓦"),
+ (0x2F62, "M", "甘"),
+ (0x2F63, "M", "生"),
+ (0x2F64, "M", "用"),
+ (0x2F65, "M", "田"),
+ (0x2F66, "M", "疋"),
+ (0x2F67, "M", "疒"),
+ (0x2F68, "M", "癶"),
+ (0x2F69, "M", "白"),
+ (0x2F6A, "M", "皮"),
+ (0x2F6B, "M", "皿"),
+ (0x2F6C, "M", "目"),
+ (0x2F6D, "M", "矛"),
+ (0x2F6E, "M", "矢"),
+ (0x2F6F, "M", "石"),
+ (0x2F70, "M", "示"),
+ (0x2F71, "M", "禸"),
+ (0x2F72, "M", "禾"),
+ (0x2F73, "M", "穴"),
+ (0x2F74, "M", "立"),
+ (0x2F75, "M", "竹"),
+ (0x2F76, "M", "米"),
+ (0x2F77, "M", "糸"),
+ (0x2F78, "M", "缶"),
+ (0x2F79, "M", "网"),
+ (0x2F7A, "M", "羊"),
+ (0x2F7B, "M", "羽"),
+ (0x2F7C, "M", "老"),
+ (0x2F7D, "M", "而"),
+ (0x2F7E, "M", "耒"),
+ (0x2F7F, "M", "耳"),
+ (0x2F80, "M", "聿"),
+ (0x2F81, "M", "肉"),
+ (0x2F82, "M", "臣"),
+ (0x2F83, "M", "自"),
+ (0x2F84, "M", "至"),
+ (0x2F85, "M", "臼"),
+ (0x2F86, "M", "舌"),
+ (0x2F87, "M", "舛"),
+ (0x2F88, "M", "舟"),
+ (0x2F89, "M", "艮"),
+ (0x2F8A, "M", "色"),
+ (0x2F8B, "M", "艸"),
+ (0x2F8C, "M", "虍"),
+ (0x2F8D, "M", "虫"),
+ (0x2F8E, "M", "血"),
+ (0x2F8F, "M", "行"),
+ (0x2F90, "M", "衣"),
+ (0x2F91, "M", "襾"),
+ (0x2F92, "M", "見"),
+ (0x2F93, "M", "角"),
+ (0x2F94, "M", "言"),
+ (0x2F95, "M", "谷"),
+ (0x2F96, "M", "豆"),
+ (0x2F97, "M", "豕"),
+ (0x2F98, "M", "豸"),
+ (0x2F99, "M", "貝"),
+ (0x2F9A, "M", "赤"),
+ (0x2F9B, "M", "走"),
+ (0x2F9C, "M", "足"),
+ (0x2F9D, "M", "身"),
+ (0x2F9E, "M", "車"),
+ (0x2F9F, "M", "辛"),
+ (0x2FA0, "M", "辰"),
+ (0x2FA1, "M", "辵"),
+ (0x2FA2, "M", "邑"),
+ (0x2FA3, "M", "酉"),
+ (0x2FA4, "M", "釆"),
+ (0x2FA5, "M", "里"),
+ (0x2FA6, "M", "金"),
+ (0x2FA7, "M", "長"),
+ (0x2FA8, "M", "門"),
+ (0x2FA9, "M", "阜"),
+ (0x2FAA, "M", "隶"),
+ (0x2FAB, "M", "隹"),
+ (0x2FAC, "M", "雨"),
+ (0x2FAD, "M", "靑"),
+ (0x2FAE, "M", "非"),
+ (0x2FAF, "M", "面"),
+ (0x2FB0, "M", "革"),
+ (0x2FB1, "M", "韋"),
+ (0x2FB2, "M", "韭"),
+ (0x2FB3, "M", "音"),
+ (0x2FB4, "M", "頁"),
+ (0x2FB5, "M", "風"),
+ (0x2FB6, "M", "飛"),
+ (0x2FB7, "M", "食"),
+ (0x2FB8, "M", "首"),
+ (0x2FB9, "M", "香"),
+ (0x2FBA, "M", "馬"),
+ (0x2FBB, "M", "骨"),
+ (0x2FBC, "M", "高"),
+ (0x2FBD, "M", "髟"),
+ (0x2FBE, "M", "鬥"),
+ (0x2FBF, "M", "鬯"),
+ (0x2FC0, "M", "鬲"),
+ ]
+
+
+def _seg_29() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2FC1, "M", "鬼"),
+ (0x2FC2, "M", "魚"),
+ (0x2FC3, "M", "鳥"),
+ (0x2FC4, "M", "鹵"),
+ (0x2FC5, "M", "鹿"),
+ (0x2FC6, "M", "麥"),
+ (0x2FC7, "M", "麻"),
+ (0x2FC8, "M", "黃"),
+ (0x2FC9, "M", "黍"),
+ (0x2FCA, "M", "黑"),
+ (0x2FCB, "M", "黹"),
+ (0x2FCC, "M", "黽"),
+ (0x2FCD, "M", "鼎"),
+ (0x2FCE, "M", "鼓"),
+ (0x2FCF, "M", "鼠"),
+ (0x2FD0, "M", "鼻"),
+ (0x2FD1, "M", "齊"),
+ (0x2FD2, "M", "齒"),
+ (0x2FD3, "M", "龍"),
+ (0x2FD4, "M", "龜"),
+ (0x2FD5, "M", "龠"),
+ (0x2FD6, "X"),
+ (0x3000, "M", " "),
+ (0x3001, "V"),
+ (0x3002, "M", "."),
+ (0x3003, "V"),
+ (0x3036, "M", "〒"),
+ (0x3037, "V"),
+ (0x3038, "M", "十"),
+ (0x3039, "M", "卄"),
+ (0x303A, "M", "卅"),
+ (0x303B, "V"),
+ (0x3040, "X"),
+ (0x3041, "V"),
+ (0x3097, "X"),
+ (0x3099, "V"),
+ (0x309B, "M", " ゙"),
+ (0x309C, "M", " ゚"),
+ (0x309D, "V"),
+ (0x309F, "M", "より"),
+ (0x30A0, "V"),
+ (0x30FF, "M", "コト"),
+ (0x3100, "X"),
+ (0x3105, "V"),
+ (0x3130, "X"),
+ (0x3131, "M", "ᄀ"),
+ (0x3132, "M", "ᄁ"),
+ (0x3133, "M", "ᆪ"),
+ (0x3134, "M", "ᄂ"),
+ (0x3135, "M", "ᆬ"),
+ (0x3136, "M", "ᆭ"),
+ (0x3137, "M", "ᄃ"),
+ (0x3138, "M", "ᄄ"),
+ (0x3139, "M", "ᄅ"),
+ (0x313A, "M", "ᆰ"),
+ (0x313B, "M", "ᆱ"),
+ (0x313C, "M", "ᆲ"),
+ (0x313D, "M", "ᆳ"),
+ (0x313E, "M", "ᆴ"),
+ (0x313F, "M", "ᆵ"),
+ (0x3140, "M", "ᄚ"),
+ (0x3141, "M", "ᄆ"),
+ (0x3142, "M", "ᄇ"),
+ (0x3143, "M", "ᄈ"),
+ (0x3144, "M", "ᄡ"),
+ (0x3145, "M", "ᄉ"),
+ (0x3146, "M", "ᄊ"),
+ (0x3147, "M", "ᄋ"),
+ (0x3148, "M", "ᄌ"),
+ (0x3149, "M", "ᄍ"),
+ (0x314A, "M", "ᄎ"),
+ (0x314B, "M", "ᄏ"),
+ (0x314C, "M", "ᄐ"),
+ (0x314D, "M", "ᄑ"),
+ (0x314E, "M", "ᄒ"),
+ (0x314F, "M", "ᅡ"),
+ (0x3150, "M", "ᅢ"),
+ (0x3151, "M", "ᅣ"),
+ (0x3152, "M", "ᅤ"),
+ (0x3153, "M", "ᅥ"),
+ (0x3154, "M", "ᅦ"),
+ (0x3155, "M", "ᅧ"),
+ (0x3156, "M", "ᅨ"),
+ (0x3157, "M", "ᅩ"),
+ (0x3158, "M", "ᅪ"),
+ (0x3159, "M", "ᅫ"),
+ (0x315A, "M", "ᅬ"),
+ (0x315B, "M", "ᅭ"),
+ (0x315C, "M", "ᅮ"),
+ (0x315D, "M", "ᅯ"),
+ (0x315E, "M", "ᅰ"),
+ (0x315F, "M", "ᅱ"),
+ (0x3160, "M", "ᅲ"),
+ (0x3161, "M", "ᅳ"),
+ (0x3162, "M", "ᅴ"),
+ (0x3163, "M", "ᅵ"),
+ (0x3164, "I"),
+ (0x3165, "M", "ᄔ"),
+ (0x3166, "M", "ᄕ"),
+ (0x3167, "M", "ᇇ"),
+ ]
+
+
+def _seg_30() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x3168, "M", "ᇈ"),
+ (0x3169, "M", "ᇌ"),
+ (0x316A, "M", "ᇎ"),
+ (0x316B, "M", "ᇓ"),
+ (0x316C, "M", "ᇗ"),
+ (0x316D, "M", "ᇙ"),
+ (0x316E, "M", "ᄜ"),
+ (0x316F, "M", "ᇝ"),
+ (0x3170, "M", "ᇟ"),
+ (0x3171, "M", "ᄝ"),
+ (0x3172, "M", "ᄞ"),
+ (0x3173, "M", "ᄠ"),
+ (0x3174, "M", "ᄢ"),
+ (0x3175, "M", "ᄣ"),
+ (0x3176, "M", "ᄧ"),
+ (0x3177, "M", "ᄩ"),
+ (0x3178, "M", "ᄫ"),
+ (0x3179, "M", "ᄬ"),
+ (0x317A, "M", "ᄭ"),
+ (0x317B, "M", "ᄮ"),
+ (0x317C, "M", "ᄯ"),
+ (0x317D, "M", "ᄲ"),
+ (0x317E, "M", "ᄶ"),
+ (0x317F, "M", "ᅀ"),
+ (0x3180, "M", "ᅇ"),
+ (0x3181, "M", "ᅌ"),
+ (0x3182, "M", "ᇱ"),
+ (0x3183, "M", "ᇲ"),
+ (0x3184, "M", "ᅗ"),
+ (0x3185, "M", "ᅘ"),
+ (0x3186, "M", "ᅙ"),
+ (0x3187, "M", "ᆄ"),
+ (0x3188, "M", "ᆅ"),
+ (0x3189, "M", "ᆈ"),
+ (0x318A, "M", "ᆑ"),
+ (0x318B, "M", "ᆒ"),
+ (0x318C, "M", "ᆔ"),
+ (0x318D, "M", "ᆞ"),
+ (0x318E, "M", "ᆡ"),
+ (0x318F, "X"),
+ (0x3190, "V"),
+ (0x3192, "M", "一"),
+ (0x3193, "M", "二"),
+ (0x3194, "M", "三"),
+ (0x3195, "M", "四"),
+ (0x3196, "M", "上"),
+ (0x3197, "M", "中"),
+ (0x3198, "M", "下"),
+ (0x3199, "M", "甲"),
+ (0x319A, "M", "乙"),
+ (0x319B, "M", "丙"),
+ (0x319C, "M", "丁"),
+ (0x319D, "M", "天"),
+ (0x319E, "M", "地"),
+ (0x319F, "M", "人"),
+ (0x31A0, "V"),
+ (0x31E6, "X"),
+ (0x31F0, "V"),
+ (0x3200, "M", "(ᄀ)"),
+ (0x3201, "M", "(ᄂ)"),
+ (0x3202, "M", "(ᄃ)"),
+ (0x3203, "M", "(ᄅ)"),
+ (0x3204, "M", "(ᄆ)"),
+ (0x3205, "M", "(ᄇ)"),
+ (0x3206, "M", "(ᄉ)"),
+ (0x3207, "M", "(ᄋ)"),
+ (0x3208, "M", "(ᄌ)"),
+ (0x3209, "M", "(ᄎ)"),
+ (0x320A, "M", "(ᄏ)"),
+ (0x320B, "M", "(ᄐ)"),
+ (0x320C, "M", "(ᄑ)"),
+ (0x320D, "M", "(ᄒ)"),
+ (0x320E, "M", "(가)"),
+ (0x320F, "M", "(나)"),
+ (0x3210, "M", "(다)"),
+ (0x3211, "M", "(라)"),
+ (0x3212, "M", "(마)"),
+ (0x3213, "M", "(바)"),
+ (0x3214, "M", "(사)"),
+ (0x3215, "M", "(아)"),
+ (0x3216, "M", "(자)"),
+ (0x3217, "M", "(차)"),
+ (0x3218, "M", "(카)"),
+ (0x3219, "M", "(타)"),
+ (0x321A, "M", "(파)"),
+ (0x321B, "M", "(하)"),
+ (0x321C, "M", "(주)"),
+ (0x321D, "M", "(오전)"),
+ (0x321E, "M", "(오후)"),
+ (0x321F, "X"),
+ (0x3220, "M", "(一)"),
+ (0x3221, "M", "(二)"),
+ (0x3222, "M", "(三)"),
+ (0x3223, "M", "(四)"),
+ (0x3224, "M", "(五)"),
+ (0x3225, "M", "(六)"),
+ (0x3226, "M", "(七)"),
+ (0x3227, "M", "(八)"),
+ (0x3228, "M", "(九)"),
+ (0x3229, "M", "(十)"),
+ ]
+
+
+def _seg_31() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x322A, "M", "(月)"),
+ (0x322B, "M", "(火)"),
+ (0x322C, "M", "(水)"),
+ (0x322D, "M", "(木)"),
+ (0x322E, "M", "(金)"),
+ (0x322F, "M", "(土)"),
+ (0x3230, "M", "(日)"),
+ (0x3231, "M", "(株)"),
+ (0x3232, "M", "(有)"),
+ (0x3233, "M", "(社)"),
+ (0x3234, "M", "(名)"),
+ (0x3235, "M", "(特)"),
+ (0x3236, "M", "(財)"),
+ (0x3237, "M", "(祝)"),
+ (0x3238, "M", "(労)"),
+ (0x3239, "M", "(代)"),
+ (0x323A, "M", "(呼)"),
+ (0x323B, "M", "(学)"),
+ (0x323C, "M", "(監)"),
+ (0x323D, "M", "(企)"),
+ (0x323E, "M", "(資)"),
+ (0x323F, "M", "(協)"),
+ (0x3240, "M", "(祭)"),
+ (0x3241, "M", "(休)"),
+ (0x3242, "M", "(自)"),
+ (0x3243, "M", "(至)"),
+ (0x3244, "M", "問"),
+ (0x3245, "M", "幼"),
+ (0x3246, "M", "文"),
+ (0x3247, "M", "箏"),
+ (0x3248, "V"),
+ (0x3250, "M", "pte"),
+ (0x3251, "M", "21"),
+ (0x3252, "M", "22"),
+ (0x3253, "M", "23"),
+ (0x3254, "M", "24"),
+ (0x3255, "M", "25"),
+ (0x3256, "M", "26"),
+ (0x3257, "M", "27"),
+ (0x3258, "M", "28"),
+ (0x3259, "M", "29"),
+ (0x325A, "M", "30"),
+ (0x325B, "M", "31"),
+ (0x325C, "M", "32"),
+ (0x325D, "M", "33"),
+ (0x325E, "M", "34"),
+ (0x325F, "M", "35"),
+ (0x3260, "M", "ᄀ"),
+ (0x3261, "M", "ᄂ"),
+ (0x3262, "M", "ᄃ"),
+ (0x3263, "M", "ᄅ"),
+ (0x3264, "M", "ᄆ"),
+ (0x3265, "M", "ᄇ"),
+ (0x3266, "M", "ᄉ"),
+ (0x3267, "M", "ᄋ"),
+ (0x3268, "M", "ᄌ"),
+ (0x3269, "M", "ᄎ"),
+ (0x326A, "M", "ᄏ"),
+ (0x326B, "M", "ᄐ"),
+ (0x326C, "M", "ᄑ"),
+ (0x326D, "M", "ᄒ"),
+ (0x326E, "M", "가"),
+ (0x326F, "M", "나"),
+ (0x3270, "M", "다"),
+ (0x3271, "M", "라"),
+ (0x3272, "M", "마"),
+ (0x3273, "M", "바"),
+ (0x3274, "M", "사"),
+ (0x3275, "M", "아"),
+ (0x3276, "M", "자"),
+ (0x3277, "M", "차"),
+ (0x3278, "M", "카"),
+ (0x3279, "M", "타"),
+ (0x327A, "M", "파"),
+ (0x327B, "M", "하"),
+ (0x327C, "M", "참고"),
+ (0x327D, "M", "주의"),
+ (0x327E, "M", "우"),
+ (0x327F, "V"),
+ (0x3280, "M", "一"),
+ (0x3281, "M", "二"),
+ (0x3282, "M", "三"),
+ (0x3283, "M", "四"),
+ (0x3284, "M", "五"),
+ (0x3285, "M", "六"),
+ (0x3286, "M", "七"),
+ (0x3287, "M", "八"),
+ (0x3288, "M", "九"),
+ (0x3289, "M", "十"),
+ (0x328A, "M", "月"),
+ (0x328B, "M", "火"),
+ (0x328C, "M", "水"),
+ (0x328D, "M", "木"),
+ (0x328E, "M", "金"),
+ (0x328F, "M", "土"),
+ (0x3290, "M", "日"),
+ (0x3291, "M", "株"),
+ (0x3292, "M", "有"),
+ (0x3293, "M", "社"),
+ (0x3294, "M", "名"),
+ ]
+
+
+def _seg_32() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x3295, "M", "特"),
+ (0x3296, "M", "財"),
+ (0x3297, "M", "祝"),
+ (0x3298, "M", "労"),
+ (0x3299, "M", "秘"),
+ (0x329A, "M", "男"),
+ (0x329B, "M", "女"),
+ (0x329C, "M", "適"),
+ (0x329D, "M", "優"),
+ (0x329E, "M", "印"),
+ (0x329F, "M", "注"),
+ (0x32A0, "M", "項"),
+ (0x32A1, "M", "休"),
+ (0x32A2, "M", "写"),
+ (0x32A3, "M", "正"),
+ (0x32A4, "M", "上"),
+ (0x32A5, "M", "中"),
+ (0x32A6, "M", "下"),
+ (0x32A7, "M", "左"),
+ (0x32A8, "M", "右"),
+ (0x32A9, "M", "医"),
+ (0x32AA, "M", "宗"),
+ (0x32AB, "M", "学"),
+ (0x32AC, "M", "監"),
+ (0x32AD, "M", "企"),
+ (0x32AE, "M", "資"),
+ (0x32AF, "M", "協"),
+ (0x32B0, "M", "夜"),
+ (0x32B1, "M", "36"),
+ (0x32B2, "M", "37"),
+ (0x32B3, "M", "38"),
+ (0x32B4, "M", "39"),
+ (0x32B5, "M", "40"),
+ (0x32B6, "M", "41"),
+ (0x32B7, "M", "42"),
+ (0x32B8, "M", "43"),
+ (0x32B9, "M", "44"),
+ (0x32BA, "M", "45"),
+ (0x32BB, "M", "46"),
+ (0x32BC, "M", "47"),
+ (0x32BD, "M", "48"),
+ (0x32BE, "M", "49"),
+ (0x32BF, "M", "50"),
+ (0x32C0, "M", "1月"),
+ (0x32C1, "M", "2月"),
+ (0x32C2, "M", "3月"),
+ (0x32C3, "M", "4月"),
+ (0x32C4, "M", "5月"),
+ (0x32C5, "M", "6月"),
+ (0x32C6, "M", "7月"),
+ (0x32C7, "M", "8月"),
+ (0x32C8, "M", "9月"),
+ (0x32C9, "M", "10月"),
+ (0x32CA, "M", "11月"),
+ (0x32CB, "M", "12月"),
+ (0x32CC, "M", "hg"),
+ (0x32CD, "M", "erg"),
+ (0x32CE, "M", "ev"),
+ (0x32CF, "M", "ltd"),
+ (0x32D0, "M", "ア"),
+ (0x32D1, "M", "イ"),
+ (0x32D2, "M", "ウ"),
+ (0x32D3, "M", "エ"),
+ (0x32D4, "M", "オ"),
+ (0x32D5, "M", "カ"),
+ (0x32D6, "M", "キ"),
+ (0x32D7, "M", "ク"),
+ (0x32D8, "M", "ケ"),
+ (0x32D9, "M", "コ"),
+ (0x32DA, "M", "サ"),
+ (0x32DB, "M", "シ"),
+ (0x32DC, "M", "ス"),
+ (0x32DD, "M", "セ"),
+ (0x32DE, "M", "ソ"),
+ (0x32DF, "M", "タ"),
+ (0x32E0, "M", "チ"),
+ (0x32E1, "M", "ツ"),
+ (0x32E2, "M", "テ"),
+ (0x32E3, "M", "ト"),
+ (0x32E4, "M", "ナ"),
+ (0x32E5, "M", "ニ"),
+ (0x32E6, "M", "ヌ"),
+ (0x32E7, "M", "ネ"),
+ (0x32E8, "M", "ノ"),
+ (0x32E9, "M", "ハ"),
+ (0x32EA, "M", "ヒ"),
+ (0x32EB, "M", "フ"),
+ (0x32EC, "M", "ヘ"),
+ (0x32ED, "M", "ホ"),
+ (0x32EE, "M", "マ"),
+ (0x32EF, "M", "ミ"),
+ (0x32F0, "M", "ム"),
+ (0x32F1, "M", "メ"),
+ (0x32F2, "M", "モ"),
+ (0x32F3, "M", "ヤ"),
+ (0x32F4, "M", "ユ"),
+ (0x32F5, "M", "ヨ"),
+ (0x32F6, "M", "ラ"),
+ (0x32F7, "M", "リ"),
+ (0x32F8, "M", "ル"),
+ ]
+
+
+def _seg_33() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x32F9, "M", "レ"),
+ (0x32FA, "M", "ロ"),
+ (0x32FB, "M", "ワ"),
+ (0x32FC, "M", "ヰ"),
+ (0x32FD, "M", "ヱ"),
+ (0x32FE, "M", "ヲ"),
+ (0x32FF, "M", "令和"),
+ (0x3300, "M", "アパート"),
+ (0x3301, "M", "アルファ"),
+ (0x3302, "M", "アンペア"),
+ (0x3303, "M", "アール"),
+ (0x3304, "M", "イニング"),
+ (0x3305, "M", "インチ"),
+ (0x3306, "M", "ウォン"),
+ (0x3307, "M", "エスクード"),
+ (0x3308, "M", "エーカー"),
+ (0x3309, "M", "オンス"),
+ (0x330A, "M", "オーム"),
+ (0x330B, "M", "カイリ"),
+ (0x330C, "M", "カラット"),
+ (0x330D, "M", "カロリー"),
+ (0x330E, "M", "ガロン"),
+ (0x330F, "M", "ガンマ"),
+ (0x3310, "M", "ギガ"),
+ (0x3311, "M", "ギニー"),
+ (0x3312, "M", "キュリー"),
+ (0x3313, "M", "ギルダー"),
+ (0x3314, "M", "キロ"),
+ (0x3315, "M", "キログラム"),
+ (0x3316, "M", "キロメートル"),
+ (0x3317, "M", "キロワット"),
+ (0x3318, "M", "グラム"),
+ (0x3319, "M", "グラムトン"),
+ (0x331A, "M", "クルゼイロ"),
+ (0x331B, "M", "クローネ"),
+ (0x331C, "M", "ケース"),
+ (0x331D, "M", "コルナ"),
+ (0x331E, "M", "コーポ"),
+ (0x331F, "M", "サイクル"),
+ (0x3320, "M", "サンチーム"),
+ (0x3321, "M", "シリング"),
+ (0x3322, "M", "センチ"),
+ (0x3323, "M", "セント"),
+ (0x3324, "M", "ダース"),
+ (0x3325, "M", "デシ"),
+ (0x3326, "M", "ドル"),
+ (0x3327, "M", "トン"),
+ (0x3328, "M", "ナノ"),
+ (0x3329, "M", "ノット"),
+ (0x332A, "M", "ハイツ"),
+ (0x332B, "M", "パーセント"),
+ (0x332C, "M", "パーツ"),
+ (0x332D, "M", "バーレル"),
+ (0x332E, "M", "ピアストル"),
+ (0x332F, "M", "ピクル"),
+ (0x3330, "M", "ピコ"),
+ (0x3331, "M", "ビル"),
+ (0x3332, "M", "ファラッド"),
+ (0x3333, "M", "フィート"),
+ (0x3334, "M", "ブッシェル"),
+ (0x3335, "M", "フラン"),
+ (0x3336, "M", "ヘクタール"),
+ (0x3337, "M", "ペソ"),
+ (0x3338, "M", "ペニヒ"),
+ (0x3339, "M", "ヘルツ"),
+ (0x333A, "M", "ペンス"),
+ (0x333B, "M", "ページ"),
+ (0x333C, "M", "ベータ"),
+ (0x333D, "M", "ポイント"),
+ (0x333E, "M", "ボルト"),
+ (0x333F, "M", "ホン"),
+ (0x3340, "M", "ポンド"),
+ (0x3341, "M", "ホール"),
+ (0x3342, "M", "ホーン"),
+ (0x3343, "M", "マイクロ"),
+ (0x3344, "M", "マイル"),
+ (0x3345, "M", "マッハ"),
+ (0x3346, "M", "マルク"),
+ (0x3347, "M", "マンション"),
+ (0x3348, "M", "ミクロン"),
+ (0x3349, "M", "ミリ"),
+ (0x334A, "M", "ミリバール"),
+ (0x334B, "M", "メガ"),
+ (0x334C, "M", "メガトン"),
+ (0x334D, "M", "メートル"),
+ (0x334E, "M", "ヤード"),
+ (0x334F, "M", "ヤール"),
+ (0x3350, "M", "ユアン"),
+ (0x3351, "M", "リットル"),
+ (0x3352, "M", "リラ"),
+ (0x3353, "M", "ルピー"),
+ (0x3354, "M", "ルーブル"),
+ (0x3355, "M", "レム"),
+ (0x3356, "M", "レントゲン"),
+ (0x3357, "M", "ワット"),
+ (0x3358, "M", "0点"),
+ (0x3359, "M", "1点"),
+ (0x335A, "M", "2点"),
+ (0x335B, "M", "3点"),
+ (0x335C, "M", "4点"),
+ ]
+
+
+def _seg_34() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x335D, "M", "5点"),
+ (0x335E, "M", "6点"),
+ (0x335F, "M", "7点"),
+ (0x3360, "M", "8点"),
+ (0x3361, "M", "9点"),
+ (0x3362, "M", "10点"),
+ (0x3363, "M", "11点"),
+ (0x3364, "M", "12点"),
+ (0x3365, "M", "13点"),
+ (0x3366, "M", "14点"),
+ (0x3367, "M", "15点"),
+ (0x3368, "M", "16点"),
+ (0x3369, "M", "17点"),
+ (0x336A, "M", "18点"),
+ (0x336B, "M", "19点"),
+ (0x336C, "M", "20点"),
+ (0x336D, "M", "21点"),
+ (0x336E, "M", "22点"),
+ (0x336F, "M", "23点"),
+ (0x3370, "M", "24点"),
+ (0x3371, "M", "hpa"),
+ (0x3372, "M", "da"),
+ (0x3373, "M", "au"),
+ (0x3374, "M", "bar"),
+ (0x3375, "M", "ov"),
+ (0x3376, "M", "pc"),
+ (0x3377, "M", "dm"),
+ (0x3378, "M", "dm2"),
+ (0x3379, "M", "dm3"),
+ (0x337A, "M", "iu"),
+ (0x337B, "M", "平成"),
+ (0x337C, "M", "昭和"),
+ (0x337D, "M", "大正"),
+ (0x337E, "M", "明治"),
+ (0x337F, "M", "株式会社"),
+ (0x3380, "M", "pa"),
+ (0x3381, "M", "na"),
+ (0x3382, "M", "μa"),
+ (0x3383, "M", "ma"),
+ (0x3384, "M", "ka"),
+ (0x3385, "M", "kb"),
+ (0x3386, "M", "mb"),
+ (0x3387, "M", "gb"),
+ (0x3388, "M", "cal"),
+ (0x3389, "M", "kcal"),
+ (0x338A, "M", "pf"),
+ (0x338B, "M", "nf"),
+ (0x338C, "M", "μf"),
+ (0x338D, "M", "μg"),
+ (0x338E, "M", "mg"),
+ (0x338F, "M", "kg"),
+ (0x3390, "M", "hz"),
+ (0x3391, "M", "khz"),
+ (0x3392, "M", "mhz"),
+ (0x3393, "M", "ghz"),
+ (0x3394, "M", "thz"),
+ (0x3395, "M", "μl"),
+ (0x3396, "M", "ml"),
+ (0x3397, "M", "dl"),
+ (0x3398, "M", "kl"),
+ (0x3399, "M", "fm"),
+ (0x339A, "M", "nm"),
+ (0x339B, "M", "μm"),
+ (0x339C, "M", "mm"),
+ (0x339D, "M", "cm"),
+ (0x339E, "M", "km"),
+ (0x339F, "M", "mm2"),
+ (0x33A0, "M", "cm2"),
+ (0x33A1, "M", "m2"),
+ (0x33A2, "M", "km2"),
+ (0x33A3, "M", "mm3"),
+ (0x33A4, "M", "cm3"),
+ (0x33A5, "M", "m3"),
+ (0x33A6, "M", "km3"),
+ (0x33A7, "M", "m∕s"),
+ (0x33A8, "M", "m∕s2"),
+ (0x33A9, "M", "pa"),
+ (0x33AA, "M", "kpa"),
+ (0x33AB, "M", "mpa"),
+ (0x33AC, "M", "gpa"),
+ (0x33AD, "M", "rad"),
+ (0x33AE, "M", "rad∕s"),
+ (0x33AF, "M", "rad∕s2"),
+ (0x33B0, "M", "ps"),
+ (0x33B1, "M", "ns"),
+ (0x33B2, "M", "μs"),
+ (0x33B3, "M", "ms"),
+ (0x33B4, "M", "pv"),
+ (0x33B5, "M", "nv"),
+ (0x33B6, "M", "μv"),
+ (0x33B7, "M", "mv"),
+ (0x33B8, "M", "kv"),
+ (0x33B9, "M", "mv"),
+ (0x33BA, "M", "pw"),
+ (0x33BB, "M", "nw"),
+ (0x33BC, "M", "μw"),
+ (0x33BD, "M", "mw"),
+ (0x33BE, "M", "kw"),
+ (0x33BF, "M", "mw"),
+ (0x33C0, "M", "kω"),
+ ]
+
+
+def _seg_35() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x33C1, "M", "mω"),
+ (0x33C2, "X"),
+ (0x33C3, "M", "bq"),
+ (0x33C4, "M", "cc"),
+ (0x33C5, "M", "cd"),
+ (0x33C6, "M", "c∕kg"),
+ (0x33C7, "X"),
+ (0x33C8, "M", "db"),
+ (0x33C9, "M", "gy"),
+ (0x33CA, "M", "ha"),
+ (0x33CB, "M", "hp"),
+ (0x33CC, "M", "in"),
+ (0x33CD, "M", "kk"),
+ (0x33CE, "M", "km"),
+ (0x33CF, "M", "kt"),
+ (0x33D0, "M", "lm"),
+ (0x33D1, "M", "ln"),
+ (0x33D2, "M", "log"),
+ (0x33D3, "M", "lx"),
+ (0x33D4, "M", "mb"),
+ (0x33D5, "M", "mil"),
+ (0x33D6, "M", "mol"),
+ (0x33D7, "M", "ph"),
+ (0x33D8, "X"),
+ (0x33D9, "M", "ppm"),
+ (0x33DA, "M", "pr"),
+ (0x33DB, "M", "sr"),
+ (0x33DC, "M", "sv"),
+ (0x33DD, "M", "wb"),
+ (0x33DE, "M", "v∕m"),
+ (0x33DF, "M", "a∕m"),
+ (0x33E0, "M", "1日"),
+ (0x33E1, "M", "2日"),
+ (0x33E2, "M", "3日"),
+ (0x33E3, "M", "4日"),
+ (0x33E4, "M", "5日"),
+ (0x33E5, "M", "6日"),
+ (0x33E6, "M", "7日"),
+ (0x33E7, "M", "8日"),
+ (0x33E8, "M", "9日"),
+ (0x33E9, "M", "10日"),
+ (0x33EA, "M", "11日"),
+ (0x33EB, "M", "12日"),
+ (0x33EC, "M", "13日"),
+ (0x33ED, "M", "14日"),
+ (0x33EE, "M", "15日"),
+ (0x33EF, "M", "16日"),
+ (0x33F0, "M", "17日"),
+ (0x33F1, "M", "18日"),
+ (0x33F2, "M", "19日"),
+ (0x33F3, "M", "20日"),
+ (0x33F4, "M", "21日"),
+ (0x33F5, "M", "22日"),
+ (0x33F6, "M", "23日"),
+ (0x33F7, "M", "24日"),
+ (0x33F8, "M", "25日"),
+ (0x33F9, "M", "26日"),
+ (0x33FA, "M", "27日"),
+ (0x33FB, "M", "28日"),
+ (0x33FC, "M", "29日"),
+ (0x33FD, "M", "30日"),
+ (0x33FE, "M", "31日"),
+ (0x33FF, "M", "gal"),
+ (0x3400, "V"),
+ (0xA48D, "X"),
+ (0xA490, "V"),
+ (0xA4C7, "X"),
+ (0xA4D0, "V"),
+ (0xA62C, "X"),
+ (0xA640, "M", "ꙁ"),
+ (0xA641, "V"),
+ (0xA642, "M", "ꙃ"),
+ (0xA643, "V"),
+ (0xA644, "M", "ꙅ"),
+ (0xA645, "V"),
+ (0xA646, "M", "ꙇ"),
+ (0xA647, "V"),
+ (0xA648, "M", "ꙉ"),
+ (0xA649, "V"),
+ (0xA64A, "M", "ꙋ"),
+ (0xA64B, "V"),
+ (0xA64C, "M", "ꙍ"),
+ (0xA64D, "V"),
+ (0xA64E, "M", "ꙏ"),
+ (0xA64F, "V"),
+ (0xA650, "M", "ꙑ"),
+ (0xA651, "V"),
+ (0xA652, "M", "ꙓ"),
+ (0xA653, "V"),
+ (0xA654, "M", "ꙕ"),
+ (0xA655, "V"),
+ (0xA656, "M", "ꙗ"),
+ (0xA657, "V"),
+ (0xA658, "M", "ꙙ"),
+ (0xA659, "V"),
+ (0xA65A, "M", "ꙛ"),
+ (0xA65B, "V"),
+ (0xA65C, "M", "ꙝ"),
+ (0xA65D, "V"),
+ (0xA65E, "M", "ꙟ"),
+ ]
+
+
+def _seg_36() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA65F, "V"),
+ (0xA660, "M", "ꙡ"),
+ (0xA661, "V"),
+ (0xA662, "M", "ꙣ"),
+ (0xA663, "V"),
+ (0xA664, "M", "ꙥ"),
+ (0xA665, "V"),
+ (0xA666, "M", "ꙧ"),
+ (0xA667, "V"),
+ (0xA668, "M", "ꙩ"),
+ (0xA669, "V"),
+ (0xA66A, "M", "ꙫ"),
+ (0xA66B, "V"),
+ (0xA66C, "M", "ꙭ"),
+ (0xA66D, "V"),
+ (0xA680, "M", "ꚁ"),
+ (0xA681, "V"),
+ (0xA682, "M", "ꚃ"),
+ (0xA683, "V"),
+ (0xA684, "M", "ꚅ"),
+ (0xA685, "V"),
+ (0xA686, "M", "ꚇ"),
+ (0xA687, "V"),
+ (0xA688, "M", "ꚉ"),
+ (0xA689, "V"),
+ (0xA68A, "M", "ꚋ"),
+ (0xA68B, "V"),
+ (0xA68C, "M", "ꚍ"),
+ (0xA68D, "V"),
+ (0xA68E, "M", "ꚏ"),
+ (0xA68F, "V"),
+ (0xA690, "M", "ꚑ"),
+ (0xA691, "V"),
+ (0xA692, "M", "ꚓ"),
+ (0xA693, "V"),
+ (0xA694, "M", "ꚕ"),
+ (0xA695, "V"),
+ (0xA696, "M", "ꚗ"),
+ (0xA697, "V"),
+ (0xA698, "M", "ꚙ"),
+ (0xA699, "V"),
+ (0xA69A, "M", "ꚛ"),
+ (0xA69B, "V"),
+ (0xA69C, "M", "ъ"),
+ (0xA69D, "M", "ь"),
+ (0xA69E, "V"),
+ (0xA6F8, "X"),
+ (0xA700, "V"),
+ (0xA722, "M", "ꜣ"),
+ (0xA723, "V"),
+ (0xA724, "M", "ꜥ"),
+ (0xA725, "V"),
+ (0xA726, "M", "ꜧ"),
+ (0xA727, "V"),
+ (0xA728, "M", "ꜩ"),
+ (0xA729, "V"),
+ (0xA72A, "M", "ꜫ"),
+ (0xA72B, "V"),
+ (0xA72C, "M", "ꜭ"),
+ (0xA72D, "V"),
+ (0xA72E, "M", "ꜯ"),
+ (0xA72F, "V"),
+ (0xA732, "M", "ꜳ"),
+ (0xA733, "V"),
+ (0xA734, "M", "ꜵ"),
+ (0xA735, "V"),
+ (0xA736, "M", "ꜷ"),
+ (0xA737, "V"),
+ (0xA738, "M", "ꜹ"),
+ (0xA739, "V"),
+ (0xA73A, "M", "ꜻ"),
+ (0xA73B, "V"),
+ (0xA73C, "M", "ꜽ"),
+ (0xA73D, "V"),
+ (0xA73E, "M", "ꜿ"),
+ (0xA73F, "V"),
+ (0xA740, "M", "ꝁ"),
+ (0xA741, "V"),
+ (0xA742, "M", "ꝃ"),
+ (0xA743, "V"),
+ (0xA744, "M", "ꝅ"),
+ (0xA745, "V"),
+ (0xA746, "M", "ꝇ"),
+ (0xA747, "V"),
+ (0xA748, "M", "ꝉ"),
+ (0xA749, "V"),
+ (0xA74A, "M", "ꝋ"),
+ (0xA74B, "V"),
+ (0xA74C, "M", "ꝍ"),
+ (0xA74D, "V"),
+ (0xA74E, "M", "ꝏ"),
+ (0xA74F, "V"),
+ (0xA750, "M", "ꝑ"),
+ (0xA751, "V"),
+ (0xA752, "M", "ꝓ"),
+ (0xA753, "V"),
+ (0xA754, "M", "ꝕ"),
+ (0xA755, "V"),
+ (0xA756, "M", "ꝗ"),
+ (0xA757, "V"),
+ ]
+
+
+def _seg_37() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA758, "M", "ꝙ"),
+ (0xA759, "V"),
+ (0xA75A, "M", "ꝛ"),
+ (0xA75B, "V"),
+ (0xA75C, "M", "ꝝ"),
+ (0xA75D, "V"),
+ (0xA75E, "M", "ꝟ"),
+ (0xA75F, "V"),
+ (0xA760, "M", "ꝡ"),
+ (0xA761, "V"),
+ (0xA762, "M", "ꝣ"),
+ (0xA763, "V"),
+ (0xA764, "M", "ꝥ"),
+ (0xA765, "V"),
+ (0xA766, "M", "ꝧ"),
+ (0xA767, "V"),
+ (0xA768, "M", "ꝩ"),
+ (0xA769, "V"),
+ (0xA76A, "M", "ꝫ"),
+ (0xA76B, "V"),
+ (0xA76C, "M", "ꝭ"),
+ (0xA76D, "V"),
+ (0xA76E, "M", "ꝯ"),
+ (0xA76F, "V"),
+ (0xA770, "M", "ꝯ"),
+ (0xA771, "V"),
+ (0xA779, "M", "ꝺ"),
+ (0xA77A, "V"),
+ (0xA77B, "M", "ꝼ"),
+ (0xA77C, "V"),
+ (0xA77D, "M", "ᵹ"),
+ (0xA77E, "M", "ꝿ"),
+ (0xA77F, "V"),
+ (0xA780, "M", "ꞁ"),
+ (0xA781, "V"),
+ (0xA782, "M", "ꞃ"),
+ (0xA783, "V"),
+ (0xA784, "M", "ꞅ"),
+ (0xA785, "V"),
+ (0xA786, "M", "ꞇ"),
+ (0xA787, "V"),
+ (0xA78B, "M", "ꞌ"),
+ (0xA78C, "V"),
+ (0xA78D, "M", "ɥ"),
+ (0xA78E, "V"),
+ (0xA790, "M", "ꞑ"),
+ (0xA791, "V"),
+ (0xA792, "M", "ꞓ"),
+ (0xA793, "V"),
+ (0xA796, "M", "ꞗ"),
+ (0xA797, "V"),
+ (0xA798, "M", "ꞙ"),
+ (0xA799, "V"),
+ (0xA79A, "M", "ꞛ"),
+ (0xA79B, "V"),
+ (0xA79C, "M", "ꞝ"),
+ (0xA79D, "V"),
+ (0xA79E, "M", "ꞟ"),
+ (0xA79F, "V"),
+ (0xA7A0, "M", "ꞡ"),
+ (0xA7A1, "V"),
+ (0xA7A2, "M", "ꞣ"),
+ (0xA7A3, "V"),
+ (0xA7A4, "M", "ꞥ"),
+ (0xA7A5, "V"),
+ (0xA7A6, "M", "ꞧ"),
+ (0xA7A7, "V"),
+ (0xA7A8, "M", "ꞩ"),
+ (0xA7A9, "V"),
+ (0xA7AA, "M", "ɦ"),
+ (0xA7AB, "M", "ɜ"),
+ (0xA7AC, "M", "ɡ"),
+ (0xA7AD, "M", "ɬ"),
+ (0xA7AE, "M", "ɪ"),
+ (0xA7AF, "V"),
+ (0xA7B0, "M", "ʞ"),
+ (0xA7B1, "M", "ʇ"),
+ (0xA7B2, "M", "ʝ"),
+ (0xA7B3, "M", "ꭓ"),
+ (0xA7B4, "M", "ꞵ"),
+ (0xA7B5, "V"),
+ (0xA7B6, "M", "ꞷ"),
+ (0xA7B7, "V"),
+ (0xA7B8, "M", "ꞹ"),
+ (0xA7B9, "V"),
+ (0xA7BA, "M", "ꞻ"),
+ (0xA7BB, "V"),
+ (0xA7BC, "M", "ꞽ"),
+ (0xA7BD, "V"),
+ (0xA7BE, "M", "ꞿ"),
+ (0xA7BF, "V"),
+ (0xA7C0, "M", "ꟁ"),
+ (0xA7C1, "V"),
+ (0xA7C2, "M", "ꟃ"),
+ (0xA7C3, "V"),
+ (0xA7C4, "M", "ꞔ"),
+ (0xA7C5, "M", "ʂ"),
+ (0xA7C6, "M", "ᶎ"),
+ (0xA7C7, "M", "ꟈ"),
+ (0xA7C8, "V"),
+ ]
+
+
+def _seg_38() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xA7C9, "M", "ꟊ"),
+ (0xA7CA, "V"),
+ (0xA7CB, "M", "ɤ"),
+ (0xA7CC, "M", ""),
+ (0xA7CD, "V"),
+ (0xA7CE, "X"),
+ (0xA7D0, "M", "ꟑ"),
+ (0xA7D1, "V"),
+ (0xA7D2, "X"),
+ (0xA7D3, "V"),
+ (0xA7D4, "X"),
+ (0xA7D5, "V"),
+ (0xA7D6, "M", "ꟗ"),
+ (0xA7D7, "V"),
+ (0xA7D8, "M", "ꟙ"),
+ (0xA7D9, "V"),
+ (0xA7DA, "M", ""),
+ (0xA7DB, "V"),
+ (0xA7DC, "M", "ƛ"),
+ (0xA7DD, "X"),
+ (0xA7F2, "M", "c"),
+ (0xA7F3, "M", "f"),
+ (0xA7F4, "M", "q"),
+ (0xA7F5, "M", "ꟶ"),
+ (0xA7F6, "V"),
+ (0xA7F8, "M", "ħ"),
+ (0xA7F9, "M", "œ"),
+ (0xA7FA, "V"),
+ (0xA82D, "X"),
+ (0xA830, "V"),
+ (0xA83A, "X"),
+ (0xA840, "V"),
+ (0xA878, "X"),
+ (0xA880, "V"),
+ (0xA8C6, "X"),
+ (0xA8CE, "V"),
+ (0xA8DA, "X"),
+ (0xA8E0, "V"),
+ (0xA954, "X"),
+ (0xA95F, "V"),
+ (0xA97D, "X"),
+ (0xA980, "V"),
+ (0xA9CE, "X"),
+ (0xA9CF, "V"),
+ (0xA9DA, "X"),
+ (0xA9DE, "V"),
+ (0xA9FF, "X"),
+ (0xAA00, "V"),
+ (0xAA37, "X"),
+ (0xAA40, "V"),
+ (0xAA4E, "X"),
+ (0xAA50, "V"),
+ (0xAA5A, "X"),
+ (0xAA5C, "V"),
+ (0xAAC3, "X"),
+ (0xAADB, "V"),
+ (0xAAF7, "X"),
+ (0xAB01, "V"),
+ (0xAB07, "X"),
+ (0xAB09, "V"),
+ (0xAB0F, "X"),
+ (0xAB11, "V"),
+ (0xAB17, "X"),
+ (0xAB20, "V"),
+ (0xAB27, "X"),
+ (0xAB28, "V"),
+ (0xAB2F, "X"),
+ (0xAB30, "V"),
+ (0xAB5C, "M", "ꜧ"),
+ (0xAB5D, "M", "ꬷ"),
+ (0xAB5E, "M", "ɫ"),
+ (0xAB5F, "M", "ꭒ"),
+ (0xAB60, "V"),
+ (0xAB69, "M", "ʍ"),
+ (0xAB6A, "V"),
+ (0xAB6C, "X"),
+ (0xAB70, "M", "Ꭰ"),
+ (0xAB71, "M", "Ꭱ"),
+ (0xAB72, "M", "Ꭲ"),
+ (0xAB73, "M", "Ꭳ"),
+ (0xAB74, "M", "Ꭴ"),
+ (0xAB75, "M", "Ꭵ"),
+ (0xAB76, "M", "Ꭶ"),
+ (0xAB77, "M", "Ꭷ"),
+ (0xAB78, "M", "Ꭸ"),
+ (0xAB79, "M", "Ꭹ"),
+ (0xAB7A, "M", "Ꭺ"),
+ (0xAB7B, "M", "Ꭻ"),
+ (0xAB7C, "M", "Ꭼ"),
+ (0xAB7D, "M", "Ꭽ"),
+ (0xAB7E, "M", "Ꭾ"),
+ (0xAB7F, "M", "Ꭿ"),
+ (0xAB80, "M", "Ꮀ"),
+ (0xAB81, "M", "Ꮁ"),
+ (0xAB82, "M", "Ꮂ"),
+ (0xAB83, "M", "Ꮃ"),
+ (0xAB84, "M", "Ꮄ"),
+ (0xAB85, "M", "Ꮅ"),
+ (0xAB86, "M", "Ꮆ"),
+ (0xAB87, "M", "Ꮇ"),
+ ]
+
+
+def _seg_39() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xAB88, "M", "Ꮈ"),
+ (0xAB89, "M", "Ꮉ"),
+ (0xAB8A, "M", "Ꮊ"),
+ (0xAB8B, "M", "Ꮋ"),
+ (0xAB8C, "M", "Ꮌ"),
+ (0xAB8D, "M", "Ꮍ"),
+ (0xAB8E, "M", "Ꮎ"),
+ (0xAB8F, "M", "Ꮏ"),
+ (0xAB90, "M", "Ꮐ"),
+ (0xAB91, "M", "Ꮑ"),
+ (0xAB92, "M", "Ꮒ"),
+ (0xAB93, "M", "Ꮓ"),
+ (0xAB94, "M", "Ꮔ"),
+ (0xAB95, "M", "Ꮕ"),
+ (0xAB96, "M", "Ꮖ"),
+ (0xAB97, "M", "Ꮗ"),
+ (0xAB98, "M", "Ꮘ"),
+ (0xAB99, "M", "Ꮙ"),
+ (0xAB9A, "M", "Ꮚ"),
+ (0xAB9B, "M", "Ꮛ"),
+ (0xAB9C, "M", "Ꮜ"),
+ (0xAB9D, "M", "Ꮝ"),
+ (0xAB9E, "M", "Ꮞ"),
+ (0xAB9F, "M", "Ꮟ"),
+ (0xABA0, "M", "Ꮠ"),
+ (0xABA1, "M", "Ꮡ"),
+ (0xABA2, "M", "Ꮢ"),
+ (0xABA3, "M", "Ꮣ"),
+ (0xABA4, "M", "Ꮤ"),
+ (0xABA5, "M", "Ꮥ"),
+ (0xABA6, "M", "Ꮦ"),
+ (0xABA7, "M", "Ꮧ"),
+ (0xABA8, "M", "Ꮨ"),
+ (0xABA9, "M", "Ꮩ"),
+ (0xABAA, "M", "Ꮪ"),
+ (0xABAB, "M", "Ꮫ"),
+ (0xABAC, "M", "Ꮬ"),
+ (0xABAD, "M", "Ꮭ"),
+ (0xABAE, "M", "Ꮮ"),
+ (0xABAF, "M", "Ꮯ"),
+ (0xABB0, "M", "Ꮰ"),
+ (0xABB1, "M", "Ꮱ"),
+ (0xABB2, "M", "Ꮲ"),
+ (0xABB3, "M", "Ꮳ"),
+ (0xABB4, "M", "Ꮴ"),
+ (0xABB5, "M", "Ꮵ"),
+ (0xABB6, "M", "Ꮶ"),
+ (0xABB7, "M", "Ꮷ"),
+ (0xABB8, "M", "Ꮸ"),
+ (0xABB9, "M", "Ꮹ"),
+ (0xABBA, "M", "Ꮺ"),
+ (0xABBB, "M", "Ꮻ"),
+ (0xABBC, "M", "Ꮼ"),
+ (0xABBD, "M", "Ꮽ"),
+ (0xABBE, "M", "Ꮾ"),
+ (0xABBF, "M", "Ꮿ"),
+ (0xABC0, "V"),
+ (0xABEE, "X"),
+ (0xABF0, "V"),
+ (0xABFA, "X"),
+ (0xAC00, "V"),
+ (0xD7A4, "X"),
+ (0xD7B0, "V"),
+ (0xD7C7, "X"),
+ (0xD7CB, "V"),
+ (0xD7FC, "X"),
+ (0xF900, "M", "豈"),
+ (0xF901, "M", "更"),
+ (0xF902, "M", "車"),
+ (0xF903, "M", "賈"),
+ (0xF904, "M", "滑"),
+ (0xF905, "M", "串"),
+ (0xF906, "M", "句"),
+ (0xF907, "M", "龜"),
+ (0xF909, "M", "契"),
+ (0xF90A, "M", "金"),
+ (0xF90B, "M", "喇"),
+ (0xF90C, "M", "奈"),
+ (0xF90D, "M", "懶"),
+ (0xF90E, "M", "癩"),
+ (0xF90F, "M", "羅"),
+ (0xF910, "M", "蘿"),
+ (0xF911, "M", "螺"),
+ (0xF912, "M", "裸"),
+ (0xF913, "M", "邏"),
+ (0xF914, "M", "樂"),
+ (0xF915, "M", "洛"),
+ (0xF916, "M", "烙"),
+ (0xF917, "M", "珞"),
+ (0xF918, "M", "落"),
+ (0xF919, "M", "酪"),
+ (0xF91A, "M", "駱"),
+ (0xF91B, "M", "亂"),
+ (0xF91C, "M", "卵"),
+ (0xF91D, "M", "欄"),
+ (0xF91E, "M", "爛"),
+ (0xF91F, "M", "蘭"),
+ (0xF920, "M", "鸞"),
+ (0xF921, "M", "嵐"),
+ (0xF922, "M", "濫"),
+ ]
+
+
+def _seg_40() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xF923, "M", "藍"),
+ (0xF924, "M", "襤"),
+ (0xF925, "M", "拉"),
+ (0xF926, "M", "臘"),
+ (0xF927, "M", "蠟"),
+ (0xF928, "M", "廊"),
+ (0xF929, "M", "朗"),
+ (0xF92A, "M", "浪"),
+ (0xF92B, "M", "狼"),
+ (0xF92C, "M", "郎"),
+ (0xF92D, "M", "來"),
+ (0xF92E, "M", "冷"),
+ (0xF92F, "M", "勞"),
+ (0xF930, "M", "擄"),
+ (0xF931, "M", "櫓"),
+ (0xF932, "M", "爐"),
+ (0xF933, "M", "盧"),
+ (0xF934, "M", "老"),
+ (0xF935, "M", "蘆"),
+ (0xF936, "M", "虜"),
+ (0xF937, "M", "路"),
+ (0xF938, "M", "露"),
+ (0xF939, "M", "魯"),
+ (0xF93A, "M", "鷺"),
+ (0xF93B, "M", "碌"),
+ (0xF93C, "M", "祿"),
+ (0xF93D, "M", "綠"),
+ (0xF93E, "M", "菉"),
+ (0xF93F, "M", "錄"),
+ (0xF940, "M", "鹿"),
+ (0xF941, "M", "論"),
+ (0xF942, "M", "壟"),
+ (0xF943, "M", "弄"),
+ (0xF944, "M", "籠"),
+ (0xF945, "M", "聾"),
+ (0xF946, "M", "牢"),
+ (0xF947, "M", "磊"),
+ (0xF948, "M", "賂"),
+ (0xF949, "M", "雷"),
+ (0xF94A, "M", "壘"),
+ (0xF94B, "M", "屢"),
+ (0xF94C, "M", "樓"),
+ (0xF94D, "M", "淚"),
+ (0xF94E, "M", "漏"),
+ (0xF94F, "M", "累"),
+ (0xF950, "M", "縷"),
+ (0xF951, "M", "陋"),
+ (0xF952, "M", "勒"),
+ (0xF953, "M", "肋"),
+ (0xF954, "M", "凜"),
+ (0xF955, "M", "凌"),
+ (0xF956, "M", "稜"),
+ (0xF957, "M", "綾"),
+ (0xF958, "M", "菱"),
+ (0xF959, "M", "陵"),
+ (0xF95A, "M", "讀"),
+ (0xF95B, "M", "拏"),
+ (0xF95C, "M", "樂"),
+ (0xF95D, "M", "諾"),
+ (0xF95E, "M", "丹"),
+ (0xF95F, "M", "寧"),
+ (0xF960, "M", "怒"),
+ (0xF961, "M", "率"),
+ (0xF962, "M", "異"),
+ (0xF963, "M", "北"),
+ (0xF964, "M", "磻"),
+ (0xF965, "M", "便"),
+ (0xF966, "M", "復"),
+ (0xF967, "M", "不"),
+ (0xF968, "M", "泌"),
+ (0xF969, "M", "數"),
+ (0xF96A, "M", "索"),
+ (0xF96B, "M", "參"),
+ (0xF96C, "M", "塞"),
+ (0xF96D, "M", "省"),
+ (0xF96E, "M", "葉"),
+ (0xF96F, "M", "說"),
+ (0xF970, "M", "殺"),
+ (0xF971, "M", "辰"),
+ (0xF972, "M", "沈"),
+ (0xF973, "M", "拾"),
+ (0xF974, "M", "若"),
+ (0xF975, "M", "掠"),
+ (0xF976, "M", "略"),
+ (0xF977, "M", "亮"),
+ (0xF978, "M", "兩"),
+ (0xF979, "M", "凉"),
+ (0xF97A, "M", "梁"),
+ (0xF97B, "M", "糧"),
+ (0xF97C, "M", "良"),
+ (0xF97D, "M", "諒"),
+ (0xF97E, "M", "量"),
+ (0xF97F, "M", "勵"),
+ (0xF980, "M", "呂"),
+ (0xF981, "M", "女"),
+ (0xF982, "M", "廬"),
+ (0xF983, "M", "旅"),
+ (0xF984, "M", "濾"),
+ (0xF985, "M", "礪"),
+ (0xF986, "M", "閭"),
+ ]
+
+
+def _seg_41() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xF987, "M", "驪"),
+ (0xF988, "M", "麗"),
+ (0xF989, "M", "黎"),
+ (0xF98A, "M", "力"),
+ (0xF98B, "M", "曆"),
+ (0xF98C, "M", "歷"),
+ (0xF98D, "M", "轢"),
+ (0xF98E, "M", "年"),
+ (0xF98F, "M", "憐"),
+ (0xF990, "M", "戀"),
+ (0xF991, "M", "撚"),
+ (0xF992, "M", "漣"),
+ (0xF993, "M", "煉"),
+ (0xF994, "M", "璉"),
+ (0xF995, "M", "秊"),
+ (0xF996, "M", "練"),
+ (0xF997, "M", "聯"),
+ (0xF998, "M", "輦"),
+ (0xF999, "M", "蓮"),
+ (0xF99A, "M", "連"),
+ (0xF99B, "M", "鍊"),
+ (0xF99C, "M", "列"),
+ (0xF99D, "M", "劣"),
+ (0xF99E, "M", "咽"),
+ (0xF99F, "M", "烈"),
+ (0xF9A0, "M", "裂"),
+ (0xF9A1, "M", "說"),
+ (0xF9A2, "M", "廉"),
+ (0xF9A3, "M", "念"),
+ (0xF9A4, "M", "捻"),
+ (0xF9A5, "M", "殮"),
+ (0xF9A6, "M", "簾"),
+ (0xF9A7, "M", "獵"),
+ (0xF9A8, "M", "令"),
+ (0xF9A9, "M", "囹"),
+ (0xF9AA, "M", "寧"),
+ (0xF9AB, "M", "嶺"),
+ (0xF9AC, "M", "怜"),
+ (0xF9AD, "M", "玲"),
+ (0xF9AE, "M", "瑩"),
+ (0xF9AF, "M", "羚"),
+ (0xF9B0, "M", "聆"),
+ (0xF9B1, "M", "鈴"),
+ (0xF9B2, "M", "零"),
+ (0xF9B3, "M", "靈"),
+ (0xF9B4, "M", "領"),
+ (0xF9B5, "M", "例"),
+ (0xF9B6, "M", "禮"),
+ (0xF9B7, "M", "醴"),
+ (0xF9B8, "M", "隸"),
+ (0xF9B9, "M", "惡"),
+ (0xF9BA, "M", "了"),
+ (0xF9BB, "M", "僚"),
+ (0xF9BC, "M", "寮"),
+ (0xF9BD, "M", "尿"),
+ (0xF9BE, "M", "料"),
+ (0xF9BF, "M", "樂"),
+ (0xF9C0, "M", "燎"),
+ (0xF9C1, "M", "療"),
+ (0xF9C2, "M", "蓼"),
+ (0xF9C3, "M", "遼"),
+ (0xF9C4, "M", "龍"),
+ (0xF9C5, "M", "暈"),
+ (0xF9C6, "M", "阮"),
+ (0xF9C7, "M", "劉"),
+ (0xF9C8, "M", "杻"),
+ (0xF9C9, "M", "柳"),
+ (0xF9CA, "M", "流"),
+ (0xF9CB, "M", "溜"),
+ (0xF9CC, "M", "琉"),
+ (0xF9CD, "M", "留"),
+ (0xF9CE, "M", "硫"),
+ (0xF9CF, "M", "紐"),
+ (0xF9D0, "M", "類"),
+ (0xF9D1, "M", "六"),
+ (0xF9D2, "M", "戮"),
+ (0xF9D3, "M", "陸"),
+ (0xF9D4, "M", "倫"),
+ (0xF9D5, "M", "崙"),
+ (0xF9D6, "M", "淪"),
+ (0xF9D7, "M", "輪"),
+ (0xF9D8, "M", "律"),
+ (0xF9D9, "M", "慄"),
+ (0xF9DA, "M", "栗"),
+ (0xF9DB, "M", "率"),
+ (0xF9DC, "M", "隆"),
+ (0xF9DD, "M", "利"),
+ (0xF9DE, "M", "吏"),
+ (0xF9DF, "M", "履"),
+ (0xF9E0, "M", "易"),
+ (0xF9E1, "M", "李"),
+ (0xF9E2, "M", "梨"),
+ (0xF9E3, "M", "泥"),
+ (0xF9E4, "M", "理"),
+ (0xF9E5, "M", "痢"),
+ (0xF9E6, "M", "罹"),
+ (0xF9E7, "M", "裏"),
+ (0xF9E8, "M", "裡"),
+ (0xF9E9, "M", "里"),
+ (0xF9EA, "M", "離"),
+ ]
+
+
+def _seg_42() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xF9EB, "M", "匿"),
+ (0xF9EC, "M", "溺"),
+ (0xF9ED, "M", "吝"),
+ (0xF9EE, "M", "燐"),
+ (0xF9EF, "M", "璘"),
+ (0xF9F0, "M", "藺"),
+ (0xF9F1, "M", "隣"),
+ (0xF9F2, "M", "鱗"),
+ (0xF9F3, "M", "麟"),
+ (0xF9F4, "M", "林"),
+ (0xF9F5, "M", "淋"),
+ (0xF9F6, "M", "臨"),
+ (0xF9F7, "M", "立"),
+ (0xF9F8, "M", "笠"),
+ (0xF9F9, "M", "粒"),
+ (0xF9FA, "M", "狀"),
+ (0xF9FB, "M", "炙"),
+ (0xF9FC, "M", "識"),
+ (0xF9FD, "M", "什"),
+ (0xF9FE, "M", "茶"),
+ (0xF9FF, "M", "刺"),
+ (0xFA00, "M", "切"),
+ (0xFA01, "M", "度"),
+ (0xFA02, "M", "拓"),
+ (0xFA03, "M", "糖"),
+ (0xFA04, "M", "宅"),
+ (0xFA05, "M", "洞"),
+ (0xFA06, "M", "暴"),
+ (0xFA07, "M", "輻"),
+ (0xFA08, "M", "行"),
+ (0xFA09, "M", "降"),
+ (0xFA0A, "M", "見"),
+ (0xFA0B, "M", "廓"),
+ (0xFA0C, "M", "兀"),
+ (0xFA0D, "M", "嗀"),
+ (0xFA0E, "V"),
+ (0xFA10, "M", "塚"),
+ (0xFA11, "V"),
+ (0xFA12, "M", "晴"),
+ (0xFA13, "V"),
+ (0xFA15, "M", "凞"),
+ (0xFA16, "M", "猪"),
+ (0xFA17, "M", "益"),
+ (0xFA18, "M", "礼"),
+ (0xFA19, "M", "神"),
+ (0xFA1A, "M", "祥"),
+ (0xFA1B, "M", "福"),
+ (0xFA1C, "M", "靖"),
+ (0xFA1D, "M", "精"),
+ (0xFA1E, "M", "羽"),
+ (0xFA1F, "V"),
+ (0xFA20, "M", "蘒"),
+ (0xFA21, "V"),
+ (0xFA22, "M", "諸"),
+ (0xFA23, "V"),
+ (0xFA25, "M", "逸"),
+ (0xFA26, "M", "都"),
+ (0xFA27, "V"),
+ (0xFA2A, "M", "飯"),
+ (0xFA2B, "M", "飼"),
+ (0xFA2C, "M", "館"),
+ (0xFA2D, "M", "鶴"),
+ (0xFA2E, "M", "郞"),
+ (0xFA2F, "M", "隷"),
+ (0xFA30, "M", "侮"),
+ (0xFA31, "M", "僧"),
+ (0xFA32, "M", "免"),
+ (0xFA33, "M", "勉"),
+ (0xFA34, "M", "勤"),
+ (0xFA35, "M", "卑"),
+ (0xFA36, "M", "喝"),
+ (0xFA37, "M", "嘆"),
+ (0xFA38, "M", "器"),
+ (0xFA39, "M", "塀"),
+ (0xFA3A, "M", "墨"),
+ (0xFA3B, "M", "層"),
+ (0xFA3C, "M", "屮"),
+ (0xFA3D, "M", "悔"),
+ (0xFA3E, "M", "慨"),
+ (0xFA3F, "M", "憎"),
+ (0xFA40, "M", "懲"),
+ (0xFA41, "M", "敏"),
+ (0xFA42, "M", "既"),
+ (0xFA43, "M", "暑"),
+ (0xFA44, "M", "梅"),
+ (0xFA45, "M", "海"),
+ (0xFA46, "M", "渚"),
+ (0xFA47, "M", "漢"),
+ (0xFA48, "M", "煮"),
+ (0xFA49, "M", "爫"),
+ (0xFA4A, "M", "琢"),
+ (0xFA4B, "M", "碑"),
+ (0xFA4C, "M", "社"),
+ (0xFA4D, "M", "祉"),
+ (0xFA4E, "M", "祈"),
+ (0xFA4F, "M", "祐"),
+ (0xFA50, "M", "祖"),
+ (0xFA51, "M", "祝"),
+ (0xFA52, "M", "禍"),
+ (0xFA53, "M", "禎"),
+ ]
+
+
+def _seg_43() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFA54, "M", "穀"),
+ (0xFA55, "M", "突"),
+ (0xFA56, "M", "節"),
+ (0xFA57, "M", "練"),
+ (0xFA58, "M", "縉"),
+ (0xFA59, "M", "繁"),
+ (0xFA5A, "M", "署"),
+ (0xFA5B, "M", "者"),
+ (0xFA5C, "M", "臭"),
+ (0xFA5D, "M", "艹"),
+ (0xFA5F, "M", "著"),
+ (0xFA60, "M", "褐"),
+ (0xFA61, "M", "視"),
+ (0xFA62, "M", "謁"),
+ (0xFA63, "M", "謹"),
+ (0xFA64, "M", "賓"),
+ (0xFA65, "M", "贈"),
+ (0xFA66, "M", "辶"),
+ (0xFA67, "M", "逸"),
+ (0xFA68, "M", "難"),
+ (0xFA69, "M", "響"),
+ (0xFA6A, "M", "頻"),
+ (0xFA6B, "M", "恵"),
+ (0xFA6C, "M", "𤋮"),
+ (0xFA6D, "M", "舘"),
+ (0xFA6E, "X"),
+ (0xFA70, "M", "並"),
+ (0xFA71, "M", "况"),
+ (0xFA72, "M", "全"),
+ (0xFA73, "M", "侀"),
+ (0xFA74, "M", "充"),
+ (0xFA75, "M", "冀"),
+ (0xFA76, "M", "勇"),
+ (0xFA77, "M", "勺"),
+ (0xFA78, "M", "喝"),
+ (0xFA79, "M", "啕"),
+ (0xFA7A, "M", "喙"),
+ (0xFA7B, "M", "嗢"),
+ (0xFA7C, "M", "塚"),
+ (0xFA7D, "M", "墳"),
+ (0xFA7E, "M", "奄"),
+ (0xFA7F, "M", "奔"),
+ (0xFA80, "M", "婢"),
+ (0xFA81, "M", "嬨"),
+ (0xFA82, "M", "廒"),
+ (0xFA83, "M", "廙"),
+ (0xFA84, "M", "彩"),
+ (0xFA85, "M", "徭"),
+ (0xFA86, "M", "惘"),
+ (0xFA87, "M", "慎"),
+ (0xFA88, "M", "愈"),
+ (0xFA89, "M", "憎"),
+ (0xFA8A, "M", "慠"),
+ (0xFA8B, "M", "懲"),
+ (0xFA8C, "M", "戴"),
+ (0xFA8D, "M", "揄"),
+ (0xFA8E, "M", "搜"),
+ (0xFA8F, "M", "摒"),
+ (0xFA90, "M", "敖"),
+ (0xFA91, "M", "晴"),
+ (0xFA92, "M", "朗"),
+ (0xFA93, "M", "望"),
+ (0xFA94, "M", "杖"),
+ (0xFA95, "M", "歹"),
+ (0xFA96, "M", "殺"),
+ (0xFA97, "M", "流"),
+ (0xFA98, "M", "滛"),
+ (0xFA99, "M", "滋"),
+ (0xFA9A, "M", "漢"),
+ (0xFA9B, "M", "瀞"),
+ (0xFA9C, "M", "煮"),
+ (0xFA9D, "M", "瞧"),
+ (0xFA9E, "M", "爵"),
+ (0xFA9F, "M", "犯"),
+ (0xFAA0, "M", "猪"),
+ (0xFAA1, "M", "瑱"),
+ (0xFAA2, "M", "甆"),
+ (0xFAA3, "M", "画"),
+ (0xFAA4, "M", "瘝"),
+ (0xFAA5, "M", "瘟"),
+ (0xFAA6, "M", "益"),
+ (0xFAA7, "M", "盛"),
+ (0xFAA8, "M", "直"),
+ (0xFAA9, "M", "睊"),
+ (0xFAAA, "M", "着"),
+ (0xFAAB, "M", "磌"),
+ (0xFAAC, "M", "窱"),
+ (0xFAAD, "M", "節"),
+ (0xFAAE, "M", "类"),
+ (0xFAAF, "M", "絛"),
+ (0xFAB0, "M", "練"),
+ (0xFAB1, "M", "缾"),
+ (0xFAB2, "M", "者"),
+ (0xFAB3, "M", "荒"),
+ (0xFAB4, "M", "華"),
+ (0xFAB5, "M", "蝹"),
+ (0xFAB6, "M", "襁"),
+ (0xFAB7, "M", "覆"),
+ (0xFAB8, "M", "視"),
+ (0xFAB9, "M", "調"),
+ ]
+
+
+def _seg_44() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFABA, "M", "諸"),
+ (0xFABB, "M", "請"),
+ (0xFABC, "M", "謁"),
+ (0xFABD, "M", "諾"),
+ (0xFABE, "M", "諭"),
+ (0xFABF, "M", "謹"),
+ (0xFAC0, "M", "變"),
+ (0xFAC1, "M", "贈"),
+ (0xFAC2, "M", "輸"),
+ (0xFAC3, "M", "遲"),
+ (0xFAC4, "M", "醙"),
+ (0xFAC5, "M", "鉶"),
+ (0xFAC6, "M", "陼"),
+ (0xFAC7, "M", "難"),
+ (0xFAC8, "M", "靖"),
+ (0xFAC9, "M", "韛"),
+ (0xFACA, "M", "響"),
+ (0xFACB, "M", "頋"),
+ (0xFACC, "M", "頻"),
+ (0xFACD, "M", "鬒"),
+ (0xFACE, "M", "龜"),
+ (0xFACF, "M", "𢡊"),
+ (0xFAD0, "M", "𢡄"),
+ (0xFAD1, "M", "𣏕"),
+ (0xFAD2, "M", "㮝"),
+ (0xFAD3, "M", "䀘"),
+ (0xFAD4, "M", "䀹"),
+ (0xFAD5, "M", "𥉉"),
+ (0xFAD6, "M", "𥳐"),
+ (0xFAD7, "M", "𧻓"),
+ (0xFAD8, "M", "齃"),
+ (0xFAD9, "M", "龎"),
+ (0xFADA, "X"),
+ (0xFB00, "M", "ff"),
+ (0xFB01, "M", "fi"),
+ (0xFB02, "M", "fl"),
+ (0xFB03, "M", "ffi"),
+ (0xFB04, "M", "ffl"),
+ (0xFB05, "M", "st"),
+ (0xFB07, "X"),
+ (0xFB13, "M", "մն"),
+ (0xFB14, "M", "մե"),
+ (0xFB15, "M", "մի"),
+ (0xFB16, "M", "վն"),
+ (0xFB17, "M", "մխ"),
+ (0xFB18, "X"),
+ (0xFB1D, "M", "יִ"),
+ (0xFB1E, "V"),
+ (0xFB1F, "M", "ײַ"),
+ (0xFB20, "M", "ע"),
+ (0xFB21, "M", "א"),
+ (0xFB22, "M", "ד"),
+ (0xFB23, "M", "ה"),
+ (0xFB24, "M", "כ"),
+ (0xFB25, "M", "ל"),
+ (0xFB26, "M", "ם"),
+ (0xFB27, "M", "ר"),
+ (0xFB28, "M", "ת"),
+ (0xFB29, "M", "+"),
+ (0xFB2A, "M", "שׁ"),
+ (0xFB2B, "M", "שׂ"),
+ (0xFB2C, "M", "שּׁ"),
+ (0xFB2D, "M", "שּׂ"),
+ (0xFB2E, "M", "אַ"),
+ (0xFB2F, "M", "אָ"),
+ (0xFB30, "M", "אּ"),
+ (0xFB31, "M", "בּ"),
+ (0xFB32, "M", "גּ"),
+ (0xFB33, "M", "דּ"),
+ (0xFB34, "M", "הּ"),
+ (0xFB35, "M", "וּ"),
+ (0xFB36, "M", "זּ"),
+ (0xFB37, "X"),
+ (0xFB38, "M", "טּ"),
+ (0xFB39, "M", "יּ"),
+ (0xFB3A, "M", "ךּ"),
+ (0xFB3B, "M", "כּ"),
+ (0xFB3C, "M", "לּ"),
+ (0xFB3D, "X"),
+ (0xFB3E, "M", "מּ"),
+ (0xFB3F, "X"),
+ (0xFB40, "M", "נּ"),
+ (0xFB41, "M", "סּ"),
+ (0xFB42, "X"),
+ (0xFB43, "M", "ףּ"),
+ (0xFB44, "M", "פּ"),
+ (0xFB45, "X"),
+ (0xFB46, "M", "צּ"),
+ (0xFB47, "M", "קּ"),
+ (0xFB48, "M", "רּ"),
+ (0xFB49, "M", "שּ"),
+ (0xFB4A, "M", "תּ"),
+ (0xFB4B, "M", "וֹ"),
+ (0xFB4C, "M", "בֿ"),
+ (0xFB4D, "M", "כֿ"),
+ (0xFB4E, "M", "פֿ"),
+ (0xFB4F, "M", "אל"),
+ (0xFB50, "M", "ٱ"),
+ (0xFB52, "M", "ٻ"),
+ (0xFB56, "M", "پ"),
+ ]
+
+
+def _seg_45() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFB5A, "M", "ڀ"),
+ (0xFB5E, "M", "ٺ"),
+ (0xFB62, "M", "ٿ"),
+ (0xFB66, "M", "ٹ"),
+ (0xFB6A, "M", "ڤ"),
+ (0xFB6E, "M", "ڦ"),
+ (0xFB72, "M", "ڄ"),
+ (0xFB76, "M", "ڃ"),
+ (0xFB7A, "M", "چ"),
+ (0xFB7E, "M", "ڇ"),
+ (0xFB82, "M", "ڍ"),
+ (0xFB84, "M", "ڌ"),
+ (0xFB86, "M", "ڎ"),
+ (0xFB88, "M", "ڈ"),
+ (0xFB8A, "M", "ژ"),
+ (0xFB8C, "M", "ڑ"),
+ (0xFB8E, "M", "ک"),
+ (0xFB92, "M", "گ"),
+ (0xFB96, "M", "ڳ"),
+ (0xFB9A, "M", "ڱ"),
+ (0xFB9E, "M", "ں"),
+ (0xFBA0, "M", "ڻ"),
+ (0xFBA4, "M", "ۀ"),
+ (0xFBA6, "M", "ہ"),
+ (0xFBAA, "M", "ھ"),
+ (0xFBAE, "M", "ے"),
+ (0xFBB0, "M", "ۓ"),
+ (0xFBB2, "V"),
+ (0xFBC3, "X"),
+ (0xFBD3, "M", "ڭ"),
+ (0xFBD7, "M", "ۇ"),
+ (0xFBD9, "M", "ۆ"),
+ (0xFBDB, "M", "ۈ"),
+ (0xFBDD, "M", "ۇٴ"),
+ (0xFBDE, "M", "ۋ"),
+ (0xFBE0, "M", "ۅ"),
+ (0xFBE2, "M", "ۉ"),
+ (0xFBE4, "M", "ې"),
+ (0xFBE8, "M", "ى"),
+ (0xFBEA, "M", "ئا"),
+ (0xFBEC, "M", "ئە"),
+ (0xFBEE, "M", "ئو"),
+ (0xFBF0, "M", "ئۇ"),
+ (0xFBF2, "M", "ئۆ"),
+ (0xFBF4, "M", "ئۈ"),
+ (0xFBF6, "M", "ئې"),
+ (0xFBF9, "M", "ئى"),
+ (0xFBFC, "M", "ی"),
+ (0xFC00, "M", "ئج"),
+ (0xFC01, "M", "ئح"),
+ (0xFC02, "M", "ئم"),
+ (0xFC03, "M", "ئى"),
+ (0xFC04, "M", "ئي"),
+ (0xFC05, "M", "بج"),
+ (0xFC06, "M", "بح"),
+ (0xFC07, "M", "بخ"),
+ (0xFC08, "M", "بم"),
+ (0xFC09, "M", "بى"),
+ (0xFC0A, "M", "بي"),
+ (0xFC0B, "M", "تج"),
+ (0xFC0C, "M", "تح"),
+ (0xFC0D, "M", "تخ"),
+ (0xFC0E, "M", "تم"),
+ (0xFC0F, "M", "تى"),
+ (0xFC10, "M", "تي"),
+ (0xFC11, "M", "ثج"),
+ (0xFC12, "M", "ثم"),
+ (0xFC13, "M", "ثى"),
+ (0xFC14, "M", "ثي"),
+ (0xFC15, "M", "جح"),
+ (0xFC16, "M", "جم"),
+ (0xFC17, "M", "حج"),
+ (0xFC18, "M", "حم"),
+ (0xFC19, "M", "خج"),
+ (0xFC1A, "M", "خح"),
+ (0xFC1B, "M", "خم"),
+ (0xFC1C, "M", "سج"),
+ (0xFC1D, "M", "سح"),
+ (0xFC1E, "M", "سخ"),
+ (0xFC1F, "M", "سم"),
+ (0xFC20, "M", "صح"),
+ (0xFC21, "M", "صم"),
+ (0xFC22, "M", "ضج"),
+ (0xFC23, "M", "ضح"),
+ (0xFC24, "M", "ضخ"),
+ (0xFC25, "M", "ضم"),
+ (0xFC26, "M", "طح"),
+ (0xFC27, "M", "طم"),
+ (0xFC28, "M", "ظم"),
+ (0xFC29, "M", "عج"),
+ (0xFC2A, "M", "عم"),
+ (0xFC2B, "M", "غج"),
+ (0xFC2C, "M", "غم"),
+ (0xFC2D, "M", "فج"),
+ (0xFC2E, "M", "فح"),
+ (0xFC2F, "M", "فخ"),
+ (0xFC30, "M", "فم"),
+ (0xFC31, "M", "فى"),
+ (0xFC32, "M", "في"),
+ (0xFC33, "M", "قح"),
+ ]
+
+
+def _seg_46() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFC34, "M", "قم"),
+ (0xFC35, "M", "قى"),
+ (0xFC36, "M", "قي"),
+ (0xFC37, "M", "كا"),
+ (0xFC38, "M", "كج"),
+ (0xFC39, "M", "كح"),
+ (0xFC3A, "M", "كخ"),
+ (0xFC3B, "M", "كل"),
+ (0xFC3C, "M", "كم"),
+ (0xFC3D, "M", "كى"),
+ (0xFC3E, "M", "كي"),
+ (0xFC3F, "M", "لج"),
+ (0xFC40, "M", "لح"),
+ (0xFC41, "M", "لخ"),
+ (0xFC42, "M", "لم"),
+ (0xFC43, "M", "لى"),
+ (0xFC44, "M", "لي"),
+ (0xFC45, "M", "مج"),
+ (0xFC46, "M", "مح"),
+ (0xFC47, "M", "مخ"),
+ (0xFC48, "M", "مم"),
+ (0xFC49, "M", "مى"),
+ (0xFC4A, "M", "مي"),
+ (0xFC4B, "M", "نج"),
+ (0xFC4C, "M", "نح"),
+ (0xFC4D, "M", "نخ"),
+ (0xFC4E, "M", "نم"),
+ (0xFC4F, "M", "نى"),
+ (0xFC50, "M", "ني"),
+ (0xFC51, "M", "هج"),
+ (0xFC52, "M", "هم"),
+ (0xFC53, "M", "هى"),
+ (0xFC54, "M", "هي"),
+ (0xFC55, "M", "يج"),
+ (0xFC56, "M", "يح"),
+ (0xFC57, "M", "يخ"),
+ (0xFC58, "M", "يم"),
+ (0xFC59, "M", "يى"),
+ (0xFC5A, "M", "يي"),
+ (0xFC5B, "M", "ذٰ"),
+ (0xFC5C, "M", "رٰ"),
+ (0xFC5D, "M", "ىٰ"),
+ (0xFC5E, "M", " ٌّ"),
+ (0xFC5F, "M", " ٍّ"),
+ (0xFC60, "M", " َّ"),
+ (0xFC61, "M", " ُّ"),
+ (0xFC62, "M", " ِّ"),
+ (0xFC63, "M", " ّٰ"),
+ (0xFC64, "M", "ئر"),
+ (0xFC65, "M", "ئز"),
+ (0xFC66, "M", "ئم"),
+ (0xFC67, "M", "ئن"),
+ (0xFC68, "M", "ئى"),
+ (0xFC69, "M", "ئي"),
+ (0xFC6A, "M", "بر"),
+ (0xFC6B, "M", "بز"),
+ (0xFC6C, "M", "بم"),
+ (0xFC6D, "M", "بن"),
+ (0xFC6E, "M", "بى"),
+ (0xFC6F, "M", "بي"),
+ (0xFC70, "M", "تر"),
+ (0xFC71, "M", "تز"),
+ (0xFC72, "M", "تم"),
+ (0xFC73, "M", "تن"),
+ (0xFC74, "M", "تى"),
+ (0xFC75, "M", "تي"),
+ (0xFC76, "M", "ثر"),
+ (0xFC77, "M", "ثز"),
+ (0xFC78, "M", "ثم"),
+ (0xFC79, "M", "ثن"),
+ (0xFC7A, "M", "ثى"),
+ (0xFC7B, "M", "ثي"),
+ (0xFC7C, "M", "فى"),
+ (0xFC7D, "M", "في"),
+ (0xFC7E, "M", "قى"),
+ (0xFC7F, "M", "قي"),
+ (0xFC80, "M", "كا"),
+ (0xFC81, "M", "كل"),
+ (0xFC82, "M", "كم"),
+ (0xFC83, "M", "كى"),
+ (0xFC84, "M", "كي"),
+ (0xFC85, "M", "لم"),
+ (0xFC86, "M", "لى"),
+ (0xFC87, "M", "لي"),
+ (0xFC88, "M", "ما"),
+ (0xFC89, "M", "مم"),
+ (0xFC8A, "M", "نر"),
+ (0xFC8B, "M", "نز"),
+ (0xFC8C, "M", "نم"),
+ (0xFC8D, "M", "نن"),
+ (0xFC8E, "M", "نى"),
+ (0xFC8F, "M", "ني"),
+ (0xFC90, "M", "ىٰ"),
+ (0xFC91, "M", "ير"),
+ (0xFC92, "M", "يز"),
+ (0xFC93, "M", "يم"),
+ (0xFC94, "M", "ين"),
+ (0xFC95, "M", "يى"),
+ (0xFC96, "M", "يي"),
+ (0xFC97, "M", "ئج"),
+ ]
+
+
+def _seg_47() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFC98, "M", "ئح"),
+ (0xFC99, "M", "ئخ"),
+ (0xFC9A, "M", "ئم"),
+ (0xFC9B, "M", "ئه"),
+ (0xFC9C, "M", "بج"),
+ (0xFC9D, "M", "بح"),
+ (0xFC9E, "M", "بخ"),
+ (0xFC9F, "M", "بم"),
+ (0xFCA0, "M", "به"),
+ (0xFCA1, "M", "تج"),
+ (0xFCA2, "M", "تح"),
+ (0xFCA3, "M", "تخ"),
+ (0xFCA4, "M", "تم"),
+ (0xFCA5, "M", "ته"),
+ (0xFCA6, "M", "ثم"),
+ (0xFCA7, "M", "جح"),
+ (0xFCA8, "M", "جم"),
+ (0xFCA9, "M", "حج"),
+ (0xFCAA, "M", "حم"),
+ (0xFCAB, "M", "خج"),
+ (0xFCAC, "M", "خم"),
+ (0xFCAD, "M", "سج"),
+ (0xFCAE, "M", "سح"),
+ (0xFCAF, "M", "سخ"),
+ (0xFCB0, "M", "سم"),
+ (0xFCB1, "M", "صح"),
+ (0xFCB2, "M", "صخ"),
+ (0xFCB3, "M", "صم"),
+ (0xFCB4, "M", "ضج"),
+ (0xFCB5, "M", "ضح"),
+ (0xFCB6, "M", "ضخ"),
+ (0xFCB7, "M", "ضم"),
+ (0xFCB8, "M", "طح"),
+ (0xFCB9, "M", "ظم"),
+ (0xFCBA, "M", "عج"),
+ (0xFCBB, "M", "عم"),
+ (0xFCBC, "M", "غج"),
+ (0xFCBD, "M", "غم"),
+ (0xFCBE, "M", "فج"),
+ (0xFCBF, "M", "فح"),
+ (0xFCC0, "M", "فخ"),
+ (0xFCC1, "M", "فم"),
+ (0xFCC2, "M", "قح"),
+ (0xFCC3, "M", "قم"),
+ (0xFCC4, "M", "كج"),
+ (0xFCC5, "M", "كح"),
+ (0xFCC6, "M", "كخ"),
+ (0xFCC7, "M", "كل"),
+ (0xFCC8, "M", "كم"),
+ (0xFCC9, "M", "لج"),
+ (0xFCCA, "M", "لح"),
+ (0xFCCB, "M", "لخ"),
+ (0xFCCC, "M", "لم"),
+ (0xFCCD, "M", "له"),
+ (0xFCCE, "M", "مج"),
+ (0xFCCF, "M", "مح"),
+ (0xFCD0, "M", "مخ"),
+ (0xFCD1, "M", "مم"),
+ (0xFCD2, "M", "نج"),
+ (0xFCD3, "M", "نح"),
+ (0xFCD4, "M", "نخ"),
+ (0xFCD5, "M", "نم"),
+ (0xFCD6, "M", "نه"),
+ (0xFCD7, "M", "هج"),
+ (0xFCD8, "M", "هم"),
+ (0xFCD9, "M", "هٰ"),
+ (0xFCDA, "M", "يج"),
+ (0xFCDB, "M", "يح"),
+ (0xFCDC, "M", "يخ"),
+ (0xFCDD, "M", "يم"),
+ (0xFCDE, "M", "يه"),
+ (0xFCDF, "M", "ئم"),
+ (0xFCE0, "M", "ئه"),
+ (0xFCE1, "M", "بم"),
+ (0xFCE2, "M", "به"),
+ (0xFCE3, "M", "تم"),
+ (0xFCE4, "M", "ته"),
+ (0xFCE5, "M", "ثم"),
+ (0xFCE6, "M", "ثه"),
+ (0xFCE7, "M", "سم"),
+ (0xFCE8, "M", "سه"),
+ (0xFCE9, "M", "شم"),
+ (0xFCEA, "M", "شه"),
+ (0xFCEB, "M", "كل"),
+ (0xFCEC, "M", "كم"),
+ (0xFCED, "M", "لم"),
+ (0xFCEE, "M", "نم"),
+ (0xFCEF, "M", "نه"),
+ (0xFCF0, "M", "يم"),
+ (0xFCF1, "M", "يه"),
+ (0xFCF2, "M", "ـَّ"),
+ (0xFCF3, "M", "ـُّ"),
+ (0xFCF4, "M", "ـِّ"),
+ (0xFCF5, "M", "طى"),
+ (0xFCF6, "M", "طي"),
+ (0xFCF7, "M", "عى"),
+ (0xFCF8, "M", "عي"),
+ (0xFCF9, "M", "غى"),
+ (0xFCFA, "M", "غي"),
+ (0xFCFB, "M", "سى"),
+ ]
+
+
+def _seg_48() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFCFC, "M", "سي"),
+ (0xFCFD, "M", "شى"),
+ (0xFCFE, "M", "شي"),
+ (0xFCFF, "M", "حى"),
+ (0xFD00, "M", "حي"),
+ (0xFD01, "M", "جى"),
+ (0xFD02, "M", "جي"),
+ (0xFD03, "M", "خى"),
+ (0xFD04, "M", "خي"),
+ (0xFD05, "M", "صى"),
+ (0xFD06, "M", "صي"),
+ (0xFD07, "M", "ضى"),
+ (0xFD08, "M", "ضي"),
+ (0xFD09, "M", "شج"),
+ (0xFD0A, "M", "شح"),
+ (0xFD0B, "M", "شخ"),
+ (0xFD0C, "M", "شم"),
+ (0xFD0D, "M", "شر"),
+ (0xFD0E, "M", "سر"),
+ (0xFD0F, "M", "صر"),
+ (0xFD10, "M", "ضر"),
+ (0xFD11, "M", "طى"),
+ (0xFD12, "M", "طي"),
+ (0xFD13, "M", "عى"),
+ (0xFD14, "M", "عي"),
+ (0xFD15, "M", "غى"),
+ (0xFD16, "M", "غي"),
+ (0xFD17, "M", "سى"),
+ (0xFD18, "M", "سي"),
+ (0xFD19, "M", "شى"),
+ (0xFD1A, "M", "شي"),
+ (0xFD1B, "M", "حى"),
+ (0xFD1C, "M", "حي"),
+ (0xFD1D, "M", "جى"),
+ (0xFD1E, "M", "جي"),
+ (0xFD1F, "M", "خى"),
+ (0xFD20, "M", "خي"),
+ (0xFD21, "M", "صى"),
+ (0xFD22, "M", "صي"),
+ (0xFD23, "M", "ضى"),
+ (0xFD24, "M", "ضي"),
+ (0xFD25, "M", "شج"),
+ (0xFD26, "M", "شح"),
+ (0xFD27, "M", "شخ"),
+ (0xFD28, "M", "شم"),
+ (0xFD29, "M", "شر"),
+ (0xFD2A, "M", "سر"),
+ (0xFD2B, "M", "صر"),
+ (0xFD2C, "M", "ضر"),
+ (0xFD2D, "M", "شج"),
+ (0xFD2E, "M", "شح"),
+ (0xFD2F, "M", "شخ"),
+ (0xFD30, "M", "شم"),
+ (0xFD31, "M", "سه"),
+ (0xFD32, "M", "شه"),
+ (0xFD33, "M", "طم"),
+ (0xFD34, "M", "سج"),
+ (0xFD35, "M", "سح"),
+ (0xFD36, "M", "سخ"),
+ (0xFD37, "M", "شج"),
+ (0xFD38, "M", "شح"),
+ (0xFD39, "M", "شخ"),
+ (0xFD3A, "M", "طم"),
+ (0xFD3B, "M", "ظم"),
+ (0xFD3C, "M", "اً"),
+ (0xFD3E, "V"),
+ (0xFD50, "M", "تجم"),
+ (0xFD51, "M", "تحج"),
+ (0xFD53, "M", "تحم"),
+ (0xFD54, "M", "تخم"),
+ (0xFD55, "M", "تمج"),
+ (0xFD56, "M", "تمح"),
+ (0xFD57, "M", "تمخ"),
+ (0xFD58, "M", "جمح"),
+ (0xFD5A, "M", "حمي"),
+ (0xFD5B, "M", "حمى"),
+ (0xFD5C, "M", "سحج"),
+ (0xFD5D, "M", "سجح"),
+ (0xFD5E, "M", "سجى"),
+ (0xFD5F, "M", "سمح"),
+ (0xFD61, "M", "سمج"),
+ (0xFD62, "M", "سمم"),
+ (0xFD64, "M", "صحح"),
+ (0xFD66, "M", "صمم"),
+ (0xFD67, "M", "شحم"),
+ (0xFD69, "M", "شجي"),
+ (0xFD6A, "M", "شمخ"),
+ (0xFD6C, "M", "شمم"),
+ (0xFD6E, "M", "ضحى"),
+ (0xFD6F, "M", "ضخم"),
+ (0xFD71, "M", "طمح"),
+ (0xFD73, "M", "طمم"),
+ (0xFD74, "M", "طمي"),
+ (0xFD75, "M", "عجم"),
+ (0xFD76, "M", "عمم"),
+ (0xFD78, "M", "عمى"),
+ (0xFD79, "M", "غمم"),
+ (0xFD7A, "M", "غمي"),
+ (0xFD7B, "M", "غمى"),
+ (0xFD7C, "M", "فخم"),
+ ]
+
+
+def _seg_49() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFD7E, "M", "قمح"),
+ (0xFD7F, "M", "قمم"),
+ (0xFD80, "M", "لحم"),
+ (0xFD81, "M", "لحي"),
+ (0xFD82, "M", "لحى"),
+ (0xFD83, "M", "لجج"),
+ (0xFD85, "M", "لخم"),
+ (0xFD87, "M", "لمح"),
+ (0xFD89, "M", "محج"),
+ (0xFD8A, "M", "محم"),
+ (0xFD8B, "M", "محي"),
+ (0xFD8C, "M", "مجح"),
+ (0xFD8D, "M", "مجم"),
+ (0xFD8E, "M", "مخج"),
+ (0xFD8F, "M", "مخم"),
+ (0xFD90, "X"),
+ (0xFD92, "M", "مجخ"),
+ (0xFD93, "M", "همج"),
+ (0xFD94, "M", "همم"),
+ (0xFD95, "M", "نحم"),
+ (0xFD96, "M", "نحى"),
+ (0xFD97, "M", "نجم"),
+ (0xFD99, "M", "نجى"),
+ (0xFD9A, "M", "نمي"),
+ (0xFD9B, "M", "نمى"),
+ (0xFD9C, "M", "يمم"),
+ (0xFD9E, "M", "بخي"),
+ (0xFD9F, "M", "تجي"),
+ (0xFDA0, "M", "تجى"),
+ (0xFDA1, "M", "تخي"),
+ (0xFDA2, "M", "تخى"),
+ (0xFDA3, "M", "تمي"),
+ (0xFDA4, "M", "تمى"),
+ (0xFDA5, "M", "جمي"),
+ (0xFDA6, "M", "جحى"),
+ (0xFDA7, "M", "جمى"),
+ (0xFDA8, "M", "سخى"),
+ (0xFDA9, "M", "صحي"),
+ (0xFDAA, "M", "شحي"),
+ (0xFDAB, "M", "ضحي"),
+ (0xFDAC, "M", "لجي"),
+ (0xFDAD, "M", "لمي"),
+ (0xFDAE, "M", "يحي"),
+ (0xFDAF, "M", "يجي"),
+ (0xFDB0, "M", "يمي"),
+ (0xFDB1, "M", "ممي"),
+ (0xFDB2, "M", "قمي"),
+ (0xFDB3, "M", "نحي"),
+ (0xFDB4, "M", "قمح"),
+ (0xFDB5, "M", "لحم"),
+ (0xFDB6, "M", "عمي"),
+ (0xFDB7, "M", "كمي"),
+ (0xFDB8, "M", "نجح"),
+ (0xFDB9, "M", "مخي"),
+ (0xFDBA, "M", "لجم"),
+ (0xFDBB, "M", "كمم"),
+ (0xFDBC, "M", "لجم"),
+ (0xFDBD, "M", "نجح"),
+ (0xFDBE, "M", "جحي"),
+ (0xFDBF, "M", "حجي"),
+ (0xFDC0, "M", "مجي"),
+ (0xFDC1, "M", "فمي"),
+ (0xFDC2, "M", "بحي"),
+ (0xFDC3, "M", "كمم"),
+ (0xFDC4, "M", "عجم"),
+ (0xFDC5, "M", "صمم"),
+ (0xFDC6, "M", "سخي"),
+ (0xFDC7, "M", "نجي"),
+ (0xFDC8, "X"),
+ (0xFDCF, "V"),
+ (0xFDD0, "X"),
+ (0xFDF0, "M", "صلے"),
+ (0xFDF1, "M", "قلے"),
+ (0xFDF2, "M", "الله"),
+ (0xFDF3, "M", "اكبر"),
+ (0xFDF4, "M", "محمد"),
+ (0xFDF5, "M", "صلعم"),
+ (0xFDF6, "M", "رسول"),
+ (0xFDF7, "M", "عليه"),
+ (0xFDF8, "M", "وسلم"),
+ (0xFDF9, "M", "صلى"),
+ (0xFDFA, "M", "صلى الله عليه وسلم"),
+ (0xFDFB, "M", "جل جلاله"),
+ (0xFDFC, "M", "ریال"),
+ (0xFDFD, "V"),
+ (0xFE00, "I"),
+ (0xFE10, "M", ","),
+ (0xFE11, "M", "、"),
+ (0xFE12, "X"),
+ (0xFE13, "M", ":"),
+ (0xFE14, "M", ";"),
+ (0xFE15, "M", "!"),
+ (0xFE16, "M", "?"),
+ (0xFE17, "M", "〖"),
+ (0xFE18, "M", "〗"),
+ (0xFE19, "X"),
+ (0xFE20, "V"),
+ (0xFE30, "X"),
+ (0xFE31, "M", "—"),
+ (0xFE32, "M", "–"),
+ ]
+
+
+def _seg_50() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFE33, "M", "_"),
+ (0xFE35, "M", "("),
+ (0xFE36, "M", ")"),
+ (0xFE37, "M", "{"),
+ (0xFE38, "M", "}"),
+ (0xFE39, "M", "〔"),
+ (0xFE3A, "M", "〕"),
+ (0xFE3B, "M", "【"),
+ (0xFE3C, "M", "】"),
+ (0xFE3D, "M", "《"),
+ (0xFE3E, "M", "》"),
+ (0xFE3F, "M", "〈"),
+ (0xFE40, "M", "〉"),
+ (0xFE41, "M", "「"),
+ (0xFE42, "M", "」"),
+ (0xFE43, "M", "『"),
+ (0xFE44, "M", "』"),
+ (0xFE45, "V"),
+ (0xFE47, "M", "["),
+ (0xFE48, "M", "]"),
+ (0xFE49, "M", " ̅"),
+ (0xFE4D, "M", "_"),
+ (0xFE50, "M", ","),
+ (0xFE51, "M", "、"),
+ (0xFE52, "X"),
+ (0xFE54, "M", ";"),
+ (0xFE55, "M", ":"),
+ (0xFE56, "M", "?"),
+ (0xFE57, "M", "!"),
+ (0xFE58, "M", "—"),
+ (0xFE59, "M", "("),
+ (0xFE5A, "M", ")"),
+ (0xFE5B, "M", "{"),
+ (0xFE5C, "M", "}"),
+ (0xFE5D, "M", "〔"),
+ (0xFE5E, "M", "〕"),
+ (0xFE5F, "M", "#"),
+ (0xFE60, "M", "&"),
+ (0xFE61, "M", "*"),
+ (0xFE62, "M", "+"),
+ (0xFE63, "M", "-"),
+ (0xFE64, "M", "<"),
+ (0xFE65, "M", ">"),
+ (0xFE66, "M", "="),
+ (0xFE67, "X"),
+ (0xFE68, "M", "\\"),
+ (0xFE69, "M", "$"),
+ (0xFE6A, "M", "%"),
+ (0xFE6B, "M", "@"),
+ (0xFE6C, "X"),
+ (0xFE70, "M", " ً"),
+ (0xFE71, "M", "ـً"),
+ (0xFE72, "M", " ٌ"),
+ (0xFE73, "V"),
+ (0xFE74, "M", " ٍ"),
+ (0xFE75, "X"),
+ (0xFE76, "M", " َ"),
+ (0xFE77, "M", "ـَ"),
+ (0xFE78, "M", " ُ"),
+ (0xFE79, "M", "ـُ"),
+ (0xFE7A, "M", " ِ"),
+ (0xFE7B, "M", "ـِ"),
+ (0xFE7C, "M", " ّ"),
+ (0xFE7D, "M", "ـّ"),
+ (0xFE7E, "M", " ْ"),
+ (0xFE7F, "M", "ـْ"),
+ (0xFE80, "M", "ء"),
+ (0xFE81, "M", "آ"),
+ (0xFE83, "M", "أ"),
+ (0xFE85, "M", "ؤ"),
+ (0xFE87, "M", "إ"),
+ (0xFE89, "M", "ئ"),
+ (0xFE8D, "M", "ا"),
+ (0xFE8F, "M", "ب"),
+ (0xFE93, "M", "ة"),
+ (0xFE95, "M", "ت"),
+ (0xFE99, "M", "ث"),
+ (0xFE9D, "M", "ج"),
+ (0xFEA1, "M", "ح"),
+ (0xFEA5, "M", "خ"),
+ (0xFEA9, "M", "د"),
+ (0xFEAB, "M", "ذ"),
+ (0xFEAD, "M", "ر"),
+ (0xFEAF, "M", "ز"),
+ (0xFEB1, "M", "س"),
+ (0xFEB5, "M", "ش"),
+ (0xFEB9, "M", "ص"),
+ (0xFEBD, "M", "ض"),
+ (0xFEC1, "M", "ط"),
+ (0xFEC5, "M", "ظ"),
+ (0xFEC9, "M", "ع"),
+ (0xFECD, "M", "غ"),
+ (0xFED1, "M", "ف"),
+ (0xFED5, "M", "ق"),
+ (0xFED9, "M", "ك"),
+ (0xFEDD, "M", "ل"),
+ (0xFEE1, "M", "م"),
+ (0xFEE5, "M", "ن"),
+ (0xFEE9, "M", "ه"),
+ (0xFEED, "M", "و"),
+ ]
+
+
+def _seg_51() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFEEF, "M", "ى"),
+ (0xFEF1, "M", "ي"),
+ (0xFEF5, "M", "لآ"),
+ (0xFEF7, "M", "لأ"),
+ (0xFEF9, "M", "لإ"),
+ (0xFEFB, "M", "لا"),
+ (0xFEFD, "X"),
+ (0xFEFF, "I"),
+ (0xFF00, "X"),
+ (0xFF01, "M", "!"),
+ (0xFF02, "M", '"'),
+ (0xFF03, "M", "#"),
+ (0xFF04, "M", "$"),
+ (0xFF05, "M", "%"),
+ (0xFF06, "M", "&"),
+ (0xFF07, "M", "'"),
+ (0xFF08, "M", "("),
+ (0xFF09, "M", ")"),
+ (0xFF0A, "M", "*"),
+ (0xFF0B, "M", "+"),
+ (0xFF0C, "M", ","),
+ (0xFF0D, "M", "-"),
+ (0xFF0E, "M", "."),
+ (0xFF0F, "M", "/"),
+ (0xFF10, "M", "0"),
+ (0xFF11, "M", "1"),
+ (0xFF12, "M", "2"),
+ (0xFF13, "M", "3"),
+ (0xFF14, "M", "4"),
+ (0xFF15, "M", "5"),
+ (0xFF16, "M", "6"),
+ (0xFF17, "M", "7"),
+ (0xFF18, "M", "8"),
+ (0xFF19, "M", "9"),
+ (0xFF1A, "M", ":"),
+ (0xFF1B, "M", ";"),
+ (0xFF1C, "M", "<"),
+ (0xFF1D, "M", "="),
+ (0xFF1E, "M", ">"),
+ (0xFF1F, "M", "?"),
+ (0xFF20, "M", "@"),
+ (0xFF21, "M", "a"),
+ (0xFF22, "M", "b"),
+ (0xFF23, "M", "c"),
+ (0xFF24, "M", "d"),
+ (0xFF25, "M", "e"),
+ (0xFF26, "M", "f"),
+ (0xFF27, "M", "g"),
+ (0xFF28, "M", "h"),
+ (0xFF29, "M", "i"),
+ (0xFF2A, "M", "j"),
+ (0xFF2B, "M", "k"),
+ (0xFF2C, "M", "l"),
+ (0xFF2D, "M", "m"),
+ (0xFF2E, "M", "n"),
+ (0xFF2F, "M", "o"),
+ (0xFF30, "M", "p"),
+ (0xFF31, "M", "q"),
+ (0xFF32, "M", "r"),
+ (0xFF33, "M", "s"),
+ (0xFF34, "M", "t"),
+ (0xFF35, "M", "u"),
+ (0xFF36, "M", "v"),
+ (0xFF37, "M", "w"),
+ (0xFF38, "M", "x"),
+ (0xFF39, "M", "y"),
+ (0xFF3A, "M", "z"),
+ (0xFF3B, "M", "["),
+ (0xFF3C, "M", "\\"),
+ (0xFF3D, "M", "]"),
+ (0xFF3E, "M", "^"),
+ (0xFF3F, "M", "_"),
+ (0xFF40, "M", "`"),
+ (0xFF41, "M", "a"),
+ (0xFF42, "M", "b"),
+ (0xFF43, "M", "c"),
+ (0xFF44, "M", "d"),
+ (0xFF45, "M", "e"),
+ (0xFF46, "M", "f"),
+ (0xFF47, "M", "g"),
+ (0xFF48, "M", "h"),
+ (0xFF49, "M", "i"),
+ (0xFF4A, "M", "j"),
+ (0xFF4B, "M", "k"),
+ (0xFF4C, "M", "l"),
+ (0xFF4D, "M", "m"),
+ (0xFF4E, "M", "n"),
+ (0xFF4F, "M", "o"),
+ (0xFF50, "M", "p"),
+ (0xFF51, "M", "q"),
+ (0xFF52, "M", "r"),
+ (0xFF53, "M", "s"),
+ (0xFF54, "M", "t"),
+ (0xFF55, "M", "u"),
+ (0xFF56, "M", "v"),
+ (0xFF57, "M", "w"),
+ (0xFF58, "M", "x"),
+ (0xFF59, "M", "y"),
+ (0xFF5A, "M", "z"),
+ (0xFF5B, "M", "{"),
+ ]
+
+
+def _seg_52() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFF5C, "M", "|"),
+ (0xFF5D, "M", "}"),
+ (0xFF5E, "M", "~"),
+ (0xFF5F, "M", "⦅"),
+ (0xFF60, "M", "⦆"),
+ (0xFF61, "M", "."),
+ (0xFF62, "M", "「"),
+ (0xFF63, "M", "」"),
+ (0xFF64, "M", "、"),
+ (0xFF65, "M", "・"),
+ (0xFF66, "M", "ヲ"),
+ (0xFF67, "M", "ァ"),
+ (0xFF68, "M", "ィ"),
+ (0xFF69, "M", "ゥ"),
+ (0xFF6A, "M", "ェ"),
+ (0xFF6B, "M", "ォ"),
+ (0xFF6C, "M", "ャ"),
+ (0xFF6D, "M", "ュ"),
+ (0xFF6E, "M", "ョ"),
+ (0xFF6F, "M", "ッ"),
+ (0xFF70, "M", "ー"),
+ (0xFF71, "M", "ア"),
+ (0xFF72, "M", "イ"),
+ (0xFF73, "M", "ウ"),
+ (0xFF74, "M", "エ"),
+ (0xFF75, "M", "オ"),
+ (0xFF76, "M", "カ"),
+ (0xFF77, "M", "キ"),
+ (0xFF78, "M", "ク"),
+ (0xFF79, "M", "ケ"),
+ (0xFF7A, "M", "コ"),
+ (0xFF7B, "M", "サ"),
+ (0xFF7C, "M", "シ"),
+ (0xFF7D, "M", "ス"),
+ (0xFF7E, "M", "セ"),
+ (0xFF7F, "M", "ソ"),
+ (0xFF80, "M", "タ"),
+ (0xFF81, "M", "チ"),
+ (0xFF82, "M", "ツ"),
+ (0xFF83, "M", "テ"),
+ (0xFF84, "M", "ト"),
+ (0xFF85, "M", "ナ"),
+ (0xFF86, "M", "ニ"),
+ (0xFF87, "M", "ヌ"),
+ (0xFF88, "M", "ネ"),
+ (0xFF89, "M", "ノ"),
+ (0xFF8A, "M", "ハ"),
+ (0xFF8B, "M", "ヒ"),
+ (0xFF8C, "M", "フ"),
+ (0xFF8D, "M", "ヘ"),
+ (0xFF8E, "M", "ホ"),
+ (0xFF8F, "M", "マ"),
+ (0xFF90, "M", "ミ"),
+ (0xFF91, "M", "ム"),
+ (0xFF92, "M", "メ"),
+ (0xFF93, "M", "モ"),
+ (0xFF94, "M", "ヤ"),
+ (0xFF95, "M", "ユ"),
+ (0xFF96, "M", "ヨ"),
+ (0xFF97, "M", "ラ"),
+ (0xFF98, "M", "リ"),
+ (0xFF99, "M", "ル"),
+ (0xFF9A, "M", "レ"),
+ (0xFF9B, "M", "ロ"),
+ (0xFF9C, "M", "ワ"),
+ (0xFF9D, "M", "ン"),
+ (0xFF9E, "M", "゙"),
+ (0xFF9F, "M", "゚"),
+ (0xFFA0, "I"),
+ (0xFFA1, "M", "ᄀ"),
+ (0xFFA2, "M", "ᄁ"),
+ (0xFFA3, "M", "ᆪ"),
+ (0xFFA4, "M", "ᄂ"),
+ (0xFFA5, "M", "ᆬ"),
+ (0xFFA6, "M", "ᆭ"),
+ (0xFFA7, "M", "ᄃ"),
+ (0xFFA8, "M", "ᄄ"),
+ (0xFFA9, "M", "ᄅ"),
+ (0xFFAA, "M", "ᆰ"),
+ (0xFFAB, "M", "ᆱ"),
+ (0xFFAC, "M", "ᆲ"),
+ (0xFFAD, "M", "ᆳ"),
+ (0xFFAE, "M", "ᆴ"),
+ (0xFFAF, "M", "ᆵ"),
+ (0xFFB0, "M", "ᄚ"),
+ (0xFFB1, "M", "ᄆ"),
+ (0xFFB2, "M", "ᄇ"),
+ (0xFFB3, "M", "ᄈ"),
+ (0xFFB4, "M", "ᄡ"),
+ (0xFFB5, "M", "ᄉ"),
+ (0xFFB6, "M", "ᄊ"),
+ (0xFFB7, "M", "ᄋ"),
+ (0xFFB8, "M", "ᄌ"),
+ (0xFFB9, "M", "ᄍ"),
+ (0xFFBA, "M", "ᄎ"),
+ (0xFFBB, "M", "ᄏ"),
+ (0xFFBC, "M", "ᄐ"),
+ (0xFFBD, "M", "ᄑ"),
+ (0xFFBE, "M", "ᄒ"),
+ (0xFFBF, "X"),
+ ]
+
+
+def _seg_53() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0xFFC2, "M", "ᅡ"),
+ (0xFFC3, "M", "ᅢ"),
+ (0xFFC4, "M", "ᅣ"),
+ (0xFFC5, "M", "ᅤ"),
+ (0xFFC6, "M", "ᅥ"),
+ (0xFFC7, "M", "ᅦ"),
+ (0xFFC8, "X"),
+ (0xFFCA, "M", "ᅧ"),
+ (0xFFCB, "M", "ᅨ"),
+ (0xFFCC, "M", "ᅩ"),
+ (0xFFCD, "M", "ᅪ"),
+ (0xFFCE, "M", "ᅫ"),
+ (0xFFCF, "M", "ᅬ"),
+ (0xFFD0, "X"),
+ (0xFFD2, "M", "ᅭ"),
+ (0xFFD3, "M", "ᅮ"),
+ (0xFFD4, "M", "ᅯ"),
+ (0xFFD5, "M", "ᅰ"),
+ (0xFFD6, "M", "ᅱ"),
+ (0xFFD7, "M", "ᅲ"),
+ (0xFFD8, "X"),
+ (0xFFDA, "M", "ᅳ"),
+ (0xFFDB, "M", "ᅴ"),
+ (0xFFDC, "M", "ᅵ"),
+ (0xFFDD, "X"),
+ (0xFFE0, "M", "¢"),
+ (0xFFE1, "M", "£"),
+ (0xFFE2, "M", "¬"),
+ (0xFFE3, "M", " ̄"),
+ (0xFFE4, "M", "¦"),
+ (0xFFE5, "M", "¥"),
+ (0xFFE6, "M", "₩"),
+ (0xFFE7, "X"),
+ (0xFFE8, "M", "│"),
+ (0xFFE9, "M", "←"),
+ (0xFFEA, "M", "↑"),
+ (0xFFEB, "M", "→"),
+ (0xFFEC, "M", "↓"),
+ (0xFFED, "M", "■"),
+ (0xFFEE, "M", "○"),
+ (0xFFEF, "X"),
+ (0x10000, "V"),
+ (0x1000C, "X"),
+ (0x1000D, "V"),
+ (0x10027, "X"),
+ (0x10028, "V"),
+ (0x1003B, "X"),
+ (0x1003C, "V"),
+ (0x1003E, "X"),
+ (0x1003F, "V"),
+ (0x1004E, "X"),
+ (0x10050, "V"),
+ (0x1005E, "X"),
+ (0x10080, "V"),
+ (0x100FB, "X"),
+ (0x10100, "V"),
+ (0x10103, "X"),
+ (0x10107, "V"),
+ (0x10134, "X"),
+ (0x10137, "V"),
+ (0x1018F, "X"),
+ (0x10190, "V"),
+ (0x1019D, "X"),
+ (0x101A0, "V"),
+ (0x101A1, "X"),
+ (0x101D0, "V"),
+ (0x101FE, "X"),
+ (0x10280, "V"),
+ (0x1029D, "X"),
+ (0x102A0, "V"),
+ (0x102D1, "X"),
+ (0x102E0, "V"),
+ (0x102FC, "X"),
+ (0x10300, "V"),
+ (0x10324, "X"),
+ (0x1032D, "V"),
+ (0x1034B, "X"),
+ (0x10350, "V"),
+ (0x1037B, "X"),
+ (0x10380, "V"),
+ (0x1039E, "X"),
+ (0x1039F, "V"),
+ (0x103C4, "X"),
+ (0x103C8, "V"),
+ (0x103D6, "X"),
+ (0x10400, "M", "𐐨"),
+ (0x10401, "M", "𐐩"),
+ (0x10402, "M", "𐐪"),
+ (0x10403, "M", "𐐫"),
+ (0x10404, "M", "𐐬"),
+ (0x10405, "M", "𐐭"),
+ (0x10406, "M", "𐐮"),
+ (0x10407, "M", "𐐯"),
+ (0x10408, "M", "𐐰"),
+ (0x10409, "M", "𐐱"),
+ (0x1040A, "M", "𐐲"),
+ (0x1040B, "M", "𐐳"),
+ (0x1040C, "M", "𐐴"),
+ (0x1040D, "M", "𐐵"),
+ (0x1040E, "M", "𐐶"),
+ ]
+
+
+def _seg_54() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1040F, "M", "𐐷"),
+ (0x10410, "M", "𐐸"),
+ (0x10411, "M", "𐐹"),
+ (0x10412, "M", "𐐺"),
+ (0x10413, "M", "𐐻"),
+ (0x10414, "M", "𐐼"),
+ (0x10415, "M", "𐐽"),
+ (0x10416, "M", "𐐾"),
+ (0x10417, "M", "𐐿"),
+ (0x10418, "M", "𐑀"),
+ (0x10419, "M", "𐑁"),
+ (0x1041A, "M", "𐑂"),
+ (0x1041B, "M", "𐑃"),
+ (0x1041C, "M", "𐑄"),
+ (0x1041D, "M", "𐑅"),
+ (0x1041E, "M", "𐑆"),
+ (0x1041F, "M", "𐑇"),
+ (0x10420, "M", "𐑈"),
+ (0x10421, "M", "𐑉"),
+ (0x10422, "M", "𐑊"),
+ (0x10423, "M", "𐑋"),
+ (0x10424, "M", "𐑌"),
+ (0x10425, "M", "𐑍"),
+ (0x10426, "M", "𐑎"),
+ (0x10427, "M", "𐑏"),
+ (0x10428, "V"),
+ (0x1049E, "X"),
+ (0x104A0, "V"),
+ (0x104AA, "X"),
+ (0x104B0, "M", "𐓘"),
+ (0x104B1, "M", "𐓙"),
+ (0x104B2, "M", "𐓚"),
+ (0x104B3, "M", "𐓛"),
+ (0x104B4, "M", "𐓜"),
+ (0x104B5, "M", "𐓝"),
+ (0x104B6, "M", "𐓞"),
+ (0x104B7, "M", "𐓟"),
+ (0x104B8, "M", "𐓠"),
+ (0x104B9, "M", "𐓡"),
+ (0x104BA, "M", "𐓢"),
+ (0x104BB, "M", "𐓣"),
+ (0x104BC, "M", "𐓤"),
+ (0x104BD, "M", "𐓥"),
+ (0x104BE, "M", "𐓦"),
+ (0x104BF, "M", "𐓧"),
+ (0x104C0, "M", "𐓨"),
+ (0x104C1, "M", "𐓩"),
+ (0x104C2, "M", "𐓪"),
+ (0x104C3, "M", "𐓫"),
+ (0x104C4, "M", "𐓬"),
+ (0x104C5, "M", "𐓭"),
+ (0x104C6, "M", "𐓮"),
+ (0x104C7, "M", "𐓯"),
+ (0x104C8, "M", "𐓰"),
+ (0x104C9, "M", "𐓱"),
+ (0x104CA, "M", "𐓲"),
+ (0x104CB, "M", "𐓳"),
+ (0x104CC, "M", "𐓴"),
+ (0x104CD, "M", "𐓵"),
+ (0x104CE, "M", "𐓶"),
+ (0x104CF, "M", "𐓷"),
+ (0x104D0, "M", "𐓸"),
+ (0x104D1, "M", "𐓹"),
+ (0x104D2, "M", "𐓺"),
+ (0x104D3, "M", "𐓻"),
+ (0x104D4, "X"),
+ (0x104D8, "V"),
+ (0x104FC, "X"),
+ (0x10500, "V"),
+ (0x10528, "X"),
+ (0x10530, "V"),
+ (0x10564, "X"),
+ (0x1056F, "V"),
+ (0x10570, "M", "𐖗"),
+ (0x10571, "M", "𐖘"),
+ (0x10572, "M", "𐖙"),
+ (0x10573, "M", "𐖚"),
+ (0x10574, "M", "𐖛"),
+ (0x10575, "M", "𐖜"),
+ (0x10576, "M", "𐖝"),
+ (0x10577, "M", "𐖞"),
+ (0x10578, "M", "𐖟"),
+ (0x10579, "M", "𐖠"),
+ (0x1057A, "M", "𐖡"),
+ (0x1057B, "X"),
+ (0x1057C, "M", "𐖣"),
+ (0x1057D, "M", "𐖤"),
+ (0x1057E, "M", "𐖥"),
+ (0x1057F, "M", "𐖦"),
+ (0x10580, "M", "𐖧"),
+ (0x10581, "M", "𐖨"),
+ (0x10582, "M", "𐖩"),
+ (0x10583, "M", "𐖪"),
+ (0x10584, "M", "𐖫"),
+ (0x10585, "M", "𐖬"),
+ (0x10586, "M", "𐖭"),
+ (0x10587, "M", "𐖮"),
+ (0x10588, "M", "𐖯"),
+ (0x10589, "M", "𐖰"),
+ (0x1058A, "M", "𐖱"),
+ ]
+
+
+def _seg_55() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1058B, "X"),
+ (0x1058C, "M", "𐖳"),
+ (0x1058D, "M", "𐖴"),
+ (0x1058E, "M", "𐖵"),
+ (0x1058F, "M", "𐖶"),
+ (0x10590, "M", "𐖷"),
+ (0x10591, "M", "𐖸"),
+ (0x10592, "M", "𐖹"),
+ (0x10593, "X"),
+ (0x10594, "M", "𐖻"),
+ (0x10595, "M", "𐖼"),
+ (0x10596, "X"),
+ (0x10597, "V"),
+ (0x105A2, "X"),
+ (0x105A3, "V"),
+ (0x105B2, "X"),
+ (0x105B3, "V"),
+ (0x105BA, "X"),
+ (0x105BB, "V"),
+ (0x105BD, "X"),
+ (0x105C0, "V"),
+ (0x105F4, "X"),
+ (0x10600, "V"),
+ (0x10737, "X"),
+ (0x10740, "V"),
+ (0x10756, "X"),
+ (0x10760, "V"),
+ (0x10768, "X"),
+ (0x10780, "V"),
+ (0x10781, "M", "ː"),
+ (0x10782, "M", "ˑ"),
+ (0x10783, "M", "æ"),
+ (0x10784, "M", "ʙ"),
+ (0x10785, "M", "ɓ"),
+ (0x10786, "X"),
+ (0x10787, "M", "ʣ"),
+ (0x10788, "M", "ꭦ"),
+ (0x10789, "M", "ʥ"),
+ (0x1078A, "M", "ʤ"),
+ (0x1078B, "M", "ɖ"),
+ (0x1078C, "M", "ɗ"),
+ (0x1078D, "M", "ᶑ"),
+ (0x1078E, "M", "ɘ"),
+ (0x1078F, "M", "ɞ"),
+ (0x10790, "M", "ʩ"),
+ (0x10791, "M", "ɤ"),
+ (0x10792, "M", "ɢ"),
+ (0x10793, "M", "ɠ"),
+ (0x10794, "M", "ʛ"),
+ (0x10795, "M", "ħ"),
+ (0x10796, "M", "ʜ"),
+ (0x10797, "M", "ɧ"),
+ (0x10798, "M", "ʄ"),
+ (0x10799, "M", "ʪ"),
+ (0x1079A, "M", "ʫ"),
+ (0x1079B, "M", "ɬ"),
+ (0x1079C, "M", "𝼄"),
+ (0x1079D, "M", "ꞎ"),
+ (0x1079E, "M", "ɮ"),
+ (0x1079F, "M", "𝼅"),
+ (0x107A0, "M", "ʎ"),
+ (0x107A1, "M", "𝼆"),
+ (0x107A2, "M", "ø"),
+ (0x107A3, "M", "ɶ"),
+ (0x107A4, "M", "ɷ"),
+ (0x107A5, "M", "q"),
+ (0x107A6, "M", "ɺ"),
+ (0x107A7, "M", "𝼈"),
+ (0x107A8, "M", "ɽ"),
+ (0x107A9, "M", "ɾ"),
+ (0x107AA, "M", "ʀ"),
+ (0x107AB, "M", "ʨ"),
+ (0x107AC, "M", "ʦ"),
+ (0x107AD, "M", "ꭧ"),
+ (0x107AE, "M", "ʧ"),
+ (0x107AF, "M", "ʈ"),
+ (0x107B0, "M", "ⱱ"),
+ (0x107B1, "X"),
+ (0x107B2, "M", "ʏ"),
+ (0x107B3, "M", "ʡ"),
+ (0x107B4, "M", "ʢ"),
+ (0x107B5, "M", "ʘ"),
+ (0x107B6, "M", "ǀ"),
+ (0x107B7, "M", "ǁ"),
+ (0x107B8, "M", "ǂ"),
+ (0x107B9, "M", "𝼊"),
+ (0x107BA, "M", "𝼞"),
+ (0x107BB, "X"),
+ (0x10800, "V"),
+ (0x10806, "X"),
+ (0x10808, "V"),
+ (0x10809, "X"),
+ (0x1080A, "V"),
+ (0x10836, "X"),
+ (0x10837, "V"),
+ (0x10839, "X"),
+ (0x1083C, "V"),
+ (0x1083D, "X"),
+ (0x1083F, "V"),
+ (0x10856, "X"),
+ ]
+
+
+def _seg_56() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x10857, "V"),
+ (0x1089F, "X"),
+ (0x108A7, "V"),
+ (0x108B0, "X"),
+ (0x108E0, "V"),
+ (0x108F3, "X"),
+ (0x108F4, "V"),
+ (0x108F6, "X"),
+ (0x108FB, "V"),
+ (0x1091C, "X"),
+ (0x1091F, "V"),
+ (0x1093A, "X"),
+ (0x1093F, "V"),
+ (0x10940, "X"),
+ (0x10980, "V"),
+ (0x109B8, "X"),
+ (0x109BC, "V"),
+ (0x109D0, "X"),
+ (0x109D2, "V"),
+ (0x10A04, "X"),
+ (0x10A05, "V"),
+ (0x10A07, "X"),
+ (0x10A0C, "V"),
+ (0x10A14, "X"),
+ (0x10A15, "V"),
+ (0x10A18, "X"),
+ (0x10A19, "V"),
+ (0x10A36, "X"),
+ (0x10A38, "V"),
+ (0x10A3B, "X"),
+ (0x10A3F, "V"),
+ (0x10A49, "X"),
+ (0x10A50, "V"),
+ (0x10A59, "X"),
+ (0x10A60, "V"),
+ (0x10AA0, "X"),
+ (0x10AC0, "V"),
+ (0x10AE7, "X"),
+ (0x10AEB, "V"),
+ (0x10AF7, "X"),
+ (0x10B00, "V"),
+ (0x10B36, "X"),
+ (0x10B39, "V"),
+ (0x10B56, "X"),
+ (0x10B58, "V"),
+ (0x10B73, "X"),
+ (0x10B78, "V"),
+ (0x10B92, "X"),
+ (0x10B99, "V"),
+ (0x10B9D, "X"),
+ (0x10BA9, "V"),
+ (0x10BB0, "X"),
+ (0x10C00, "V"),
+ (0x10C49, "X"),
+ (0x10C80, "M", "𐳀"),
+ (0x10C81, "M", "𐳁"),
+ (0x10C82, "M", "𐳂"),
+ (0x10C83, "M", "𐳃"),
+ (0x10C84, "M", "𐳄"),
+ (0x10C85, "M", "𐳅"),
+ (0x10C86, "M", "𐳆"),
+ (0x10C87, "M", "𐳇"),
+ (0x10C88, "M", "𐳈"),
+ (0x10C89, "M", "𐳉"),
+ (0x10C8A, "M", "𐳊"),
+ (0x10C8B, "M", "𐳋"),
+ (0x10C8C, "M", "𐳌"),
+ (0x10C8D, "M", "𐳍"),
+ (0x10C8E, "M", "𐳎"),
+ (0x10C8F, "M", "𐳏"),
+ (0x10C90, "M", "𐳐"),
+ (0x10C91, "M", "𐳑"),
+ (0x10C92, "M", "𐳒"),
+ (0x10C93, "M", "𐳓"),
+ (0x10C94, "M", "𐳔"),
+ (0x10C95, "M", "𐳕"),
+ (0x10C96, "M", "𐳖"),
+ (0x10C97, "M", "𐳗"),
+ (0x10C98, "M", "𐳘"),
+ (0x10C99, "M", "𐳙"),
+ (0x10C9A, "M", "𐳚"),
+ (0x10C9B, "M", "𐳛"),
+ (0x10C9C, "M", "𐳜"),
+ (0x10C9D, "M", "𐳝"),
+ (0x10C9E, "M", "𐳞"),
+ (0x10C9F, "M", "𐳟"),
+ (0x10CA0, "M", "𐳠"),
+ (0x10CA1, "M", "𐳡"),
+ (0x10CA2, "M", "𐳢"),
+ (0x10CA3, "M", "𐳣"),
+ (0x10CA4, "M", "𐳤"),
+ (0x10CA5, "M", "𐳥"),
+ (0x10CA6, "M", "𐳦"),
+ (0x10CA7, "M", "𐳧"),
+ (0x10CA8, "M", "𐳨"),
+ (0x10CA9, "M", "𐳩"),
+ (0x10CAA, "M", "𐳪"),
+ (0x10CAB, "M", "𐳫"),
+ (0x10CAC, "M", "𐳬"),
+ (0x10CAD, "M", "𐳭"),
+ ]
+
+
+def _seg_57() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x10CAE, "M", "𐳮"),
+ (0x10CAF, "M", "𐳯"),
+ (0x10CB0, "M", "𐳰"),
+ (0x10CB1, "M", "𐳱"),
+ (0x10CB2, "M", "𐳲"),
+ (0x10CB3, "X"),
+ (0x10CC0, "V"),
+ (0x10CF3, "X"),
+ (0x10CFA, "V"),
+ (0x10D28, "X"),
+ (0x10D30, "V"),
+ (0x10D3A, "X"),
+ (0x10D40, "V"),
+ (0x10D50, "M", ""),
+ (0x10D51, "M", ""),
+ (0x10D52, "M", ""),
+ (0x10D53, "M", ""),
+ (0x10D54, "M", ""),
+ (0x10D55, "M", ""),
+ (0x10D56, "M", ""),
+ (0x10D57, "M", ""),
+ (0x10D58, "M", ""),
+ (0x10D59, "M", ""),
+ (0x10D5A, "M", ""),
+ (0x10D5B, "M", ""),
+ (0x10D5C, "M", ""),
+ (0x10D5D, "M", ""),
+ (0x10D5E, "M", ""),
+ (0x10D5F, "M", ""),
+ (0x10D60, "M", ""),
+ (0x10D61, "M", ""),
+ (0x10D62, "M", ""),
+ (0x10D63, "M", ""),
+ (0x10D64, "M", ""),
+ (0x10D65, "M", ""),
+ (0x10D66, "X"),
+ (0x10D69, "V"),
+ (0x10D86, "X"),
+ (0x10D8E, "V"),
+ (0x10D90, "X"),
+ (0x10E60, "V"),
+ (0x10E7F, "X"),
+ (0x10E80, "V"),
+ (0x10EAA, "X"),
+ (0x10EAB, "V"),
+ (0x10EAE, "X"),
+ (0x10EB0, "V"),
+ (0x10EB2, "X"),
+ (0x10EC2, "V"),
+ (0x10EC5, "X"),
+ (0x10EFC, "V"),
+ (0x10F28, "X"),
+ (0x10F30, "V"),
+ (0x10F5A, "X"),
+ (0x10F70, "V"),
+ (0x10F8A, "X"),
+ (0x10FB0, "V"),
+ (0x10FCC, "X"),
+ (0x10FE0, "V"),
+ (0x10FF7, "X"),
+ (0x11000, "V"),
+ (0x1104E, "X"),
+ (0x11052, "V"),
+ (0x11076, "X"),
+ (0x1107F, "V"),
+ (0x110BD, "X"),
+ (0x110BE, "V"),
+ (0x110C3, "X"),
+ (0x110D0, "V"),
+ (0x110E9, "X"),
+ (0x110F0, "V"),
+ (0x110FA, "X"),
+ (0x11100, "V"),
+ (0x11135, "X"),
+ (0x11136, "V"),
+ (0x11148, "X"),
+ (0x11150, "V"),
+ (0x11177, "X"),
+ (0x11180, "V"),
+ (0x111E0, "X"),
+ (0x111E1, "V"),
+ (0x111F5, "X"),
+ (0x11200, "V"),
+ (0x11212, "X"),
+ (0x11213, "V"),
+ (0x11242, "X"),
+ (0x11280, "V"),
+ (0x11287, "X"),
+ (0x11288, "V"),
+ (0x11289, "X"),
+ (0x1128A, "V"),
+ (0x1128E, "X"),
+ (0x1128F, "V"),
+ (0x1129E, "X"),
+ (0x1129F, "V"),
+ (0x112AA, "X"),
+ (0x112B0, "V"),
+ (0x112EB, "X"),
+ (0x112F0, "V"),
+ (0x112FA, "X"),
+ ]
+
+
+def _seg_58() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x11300, "V"),
+ (0x11304, "X"),
+ (0x11305, "V"),
+ (0x1130D, "X"),
+ (0x1130F, "V"),
+ (0x11311, "X"),
+ (0x11313, "V"),
+ (0x11329, "X"),
+ (0x1132A, "V"),
+ (0x11331, "X"),
+ (0x11332, "V"),
+ (0x11334, "X"),
+ (0x11335, "V"),
+ (0x1133A, "X"),
+ (0x1133B, "V"),
+ (0x11345, "X"),
+ (0x11347, "V"),
+ (0x11349, "X"),
+ (0x1134B, "V"),
+ (0x1134E, "X"),
+ (0x11350, "V"),
+ (0x11351, "X"),
+ (0x11357, "V"),
+ (0x11358, "X"),
+ (0x1135D, "V"),
+ (0x11364, "X"),
+ (0x11366, "V"),
+ (0x1136D, "X"),
+ (0x11370, "V"),
+ (0x11375, "X"),
+ (0x11380, "V"),
+ (0x1138A, "X"),
+ (0x1138B, "V"),
+ (0x1138C, "X"),
+ (0x1138E, "V"),
+ (0x1138F, "X"),
+ (0x11390, "V"),
+ (0x113B6, "X"),
+ (0x113B7, "V"),
+ (0x113C1, "X"),
+ (0x113C2, "V"),
+ (0x113C3, "X"),
+ (0x113C5, "V"),
+ (0x113C6, "X"),
+ (0x113C7, "V"),
+ (0x113CB, "X"),
+ (0x113CC, "V"),
+ (0x113D6, "X"),
+ (0x113D7, "V"),
+ (0x113D9, "X"),
+ (0x113E1, "V"),
+ (0x113E3, "X"),
+ (0x11400, "V"),
+ (0x1145C, "X"),
+ (0x1145D, "V"),
+ (0x11462, "X"),
+ (0x11480, "V"),
+ (0x114C8, "X"),
+ (0x114D0, "V"),
+ (0x114DA, "X"),
+ (0x11580, "V"),
+ (0x115B6, "X"),
+ (0x115B8, "V"),
+ (0x115DE, "X"),
+ (0x11600, "V"),
+ (0x11645, "X"),
+ (0x11650, "V"),
+ (0x1165A, "X"),
+ (0x11660, "V"),
+ (0x1166D, "X"),
+ (0x11680, "V"),
+ (0x116BA, "X"),
+ (0x116C0, "V"),
+ (0x116CA, "X"),
+ (0x116D0, "V"),
+ (0x116E4, "X"),
+ (0x11700, "V"),
+ (0x1171B, "X"),
+ (0x1171D, "V"),
+ (0x1172C, "X"),
+ (0x11730, "V"),
+ (0x11747, "X"),
+ (0x11800, "V"),
+ (0x1183C, "X"),
+ (0x118A0, "M", "𑣀"),
+ (0x118A1, "M", "𑣁"),
+ (0x118A2, "M", "𑣂"),
+ (0x118A3, "M", "𑣃"),
+ (0x118A4, "M", "𑣄"),
+ (0x118A5, "M", "𑣅"),
+ (0x118A6, "M", "𑣆"),
+ (0x118A7, "M", "𑣇"),
+ (0x118A8, "M", "𑣈"),
+ (0x118A9, "M", "𑣉"),
+ (0x118AA, "M", "𑣊"),
+ (0x118AB, "M", "𑣋"),
+ (0x118AC, "M", "𑣌"),
+ (0x118AD, "M", "𑣍"),
+ (0x118AE, "M", "𑣎"),
+ (0x118AF, "M", "𑣏"),
+ ]
+
+
+def _seg_59() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x118B0, "M", "𑣐"),
+ (0x118B1, "M", "𑣑"),
+ (0x118B2, "M", "𑣒"),
+ (0x118B3, "M", "𑣓"),
+ (0x118B4, "M", "𑣔"),
+ (0x118B5, "M", "𑣕"),
+ (0x118B6, "M", "𑣖"),
+ (0x118B7, "M", "𑣗"),
+ (0x118B8, "M", "𑣘"),
+ (0x118B9, "M", "𑣙"),
+ (0x118BA, "M", "𑣚"),
+ (0x118BB, "M", "𑣛"),
+ (0x118BC, "M", "𑣜"),
+ (0x118BD, "M", "𑣝"),
+ (0x118BE, "M", "𑣞"),
+ (0x118BF, "M", "𑣟"),
+ (0x118C0, "V"),
+ (0x118F3, "X"),
+ (0x118FF, "V"),
+ (0x11907, "X"),
+ (0x11909, "V"),
+ (0x1190A, "X"),
+ (0x1190C, "V"),
+ (0x11914, "X"),
+ (0x11915, "V"),
+ (0x11917, "X"),
+ (0x11918, "V"),
+ (0x11936, "X"),
+ (0x11937, "V"),
+ (0x11939, "X"),
+ (0x1193B, "V"),
+ (0x11947, "X"),
+ (0x11950, "V"),
+ (0x1195A, "X"),
+ (0x119A0, "V"),
+ (0x119A8, "X"),
+ (0x119AA, "V"),
+ (0x119D8, "X"),
+ (0x119DA, "V"),
+ (0x119E5, "X"),
+ (0x11A00, "V"),
+ (0x11A48, "X"),
+ (0x11A50, "V"),
+ (0x11AA3, "X"),
+ (0x11AB0, "V"),
+ (0x11AF9, "X"),
+ (0x11B00, "V"),
+ (0x11B0A, "X"),
+ (0x11BC0, "V"),
+ (0x11BE2, "X"),
+ (0x11BF0, "V"),
+ (0x11BFA, "X"),
+ (0x11C00, "V"),
+ (0x11C09, "X"),
+ (0x11C0A, "V"),
+ (0x11C37, "X"),
+ (0x11C38, "V"),
+ (0x11C46, "X"),
+ (0x11C50, "V"),
+ (0x11C6D, "X"),
+ (0x11C70, "V"),
+ (0x11C90, "X"),
+ (0x11C92, "V"),
+ (0x11CA8, "X"),
+ (0x11CA9, "V"),
+ (0x11CB7, "X"),
+ (0x11D00, "V"),
+ (0x11D07, "X"),
+ (0x11D08, "V"),
+ (0x11D0A, "X"),
+ (0x11D0B, "V"),
+ (0x11D37, "X"),
+ (0x11D3A, "V"),
+ (0x11D3B, "X"),
+ (0x11D3C, "V"),
+ (0x11D3E, "X"),
+ (0x11D3F, "V"),
+ (0x11D48, "X"),
+ (0x11D50, "V"),
+ (0x11D5A, "X"),
+ (0x11D60, "V"),
+ (0x11D66, "X"),
+ (0x11D67, "V"),
+ (0x11D69, "X"),
+ (0x11D6A, "V"),
+ (0x11D8F, "X"),
+ (0x11D90, "V"),
+ (0x11D92, "X"),
+ (0x11D93, "V"),
+ (0x11D99, "X"),
+ (0x11DA0, "V"),
+ (0x11DAA, "X"),
+ (0x11EE0, "V"),
+ (0x11EF9, "X"),
+ (0x11F00, "V"),
+ (0x11F11, "X"),
+ (0x11F12, "V"),
+ (0x11F3B, "X"),
+ (0x11F3E, "V"),
+ (0x11F5B, "X"),
+ ]
+
+
+def _seg_60() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x11FB0, "V"),
+ (0x11FB1, "X"),
+ (0x11FC0, "V"),
+ (0x11FF2, "X"),
+ (0x11FFF, "V"),
+ (0x1239A, "X"),
+ (0x12400, "V"),
+ (0x1246F, "X"),
+ (0x12470, "V"),
+ (0x12475, "X"),
+ (0x12480, "V"),
+ (0x12544, "X"),
+ (0x12F90, "V"),
+ (0x12FF3, "X"),
+ (0x13000, "V"),
+ (0x13430, "X"),
+ (0x13440, "V"),
+ (0x13456, "X"),
+ (0x13460, "V"),
+ (0x143FB, "X"),
+ (0x14400, "V"),
+ (0x14647, "X"),
+ (0x16100, "V"),
+ (0x1613A, "X"),
+ (0x16800, "V"),
+ (0x16A39, "X"),
+ (0x16A40, "V"),
+ (0x16A5F, "X"),
+ (0x16A60, "V"),
+ (0x16A6A, "X"),
+ (0x16A6E, "V"),
+ (0x16ABF, "X"),
+ (0x16AC0, "V"),
+ (0x16ACA, "X"),
+ (0x16AD0, "V"),
+ (0x16AEE, "X"),
+ (0x16AF0, "V"),
+ (0x16AF6, "X"),
+ (0x16B00, "V"),
+ (0x16B46, "X"),
+ (0x16B50, "V"),
+ (0x16B5A, "X"),
+ (0x16B5B, "V"),
+ (0x16B62, "X"),
+ (0x16B63, "V"),
+ (0x16B78, "X"),
+ (0x16B7D, "V"),
+ (0x16B90, "X"),
+ (0x16D40, "V"),
+ (0x16D7A, "X"),
+ (0x16E40, "M", "𖹠"),
+ (0x16E41, "M", "𖹡"),
+ (0x16E42, "M", "𖹢"),
+ (0x16E43, "M", "𖹣"),
+ (0x16E44, "M", "𖹤"),
+ (0x16E45, "M", "𖹥"),
+ (0x16E46, "M", "𖹦"),
+ (0x16E47, "M", "𖹧"),
+ (0x16E48, "M", "𖹨"),
+ (0x16E49, "M", "𖹩"),
+ (0x16E4A, "M", "𖹪"),
+ (0x16E4B, "M", "𖹫"),
+ (0x16E4C, "M", "𖹬"),
+ (0x16E4D, "M", "𖹭"),
+ (0x16E4E, "M", "𖹮"),
+ (0x16E4F, "M", "𖹯"),
+ (0x16E50, "M", "𖹰"),
+ (0x16E51, "M", "𖹱"),
+ (0x16E52, "M", "𖹲"),
+ (0x16E53, "M", "𖹳"),
+ (0x16E54, "M", "𖹴"),
+ (0x16E55, "M", "𖹵"),
+ (0x16E56, "M", "𖹶"),
+ (0x16E57, "M", "𖹷"),
+ (0x16E58, "M", "𖹸"),
+ (0x16E59, "M", "𖹹"),
+ (0x16E5A, "M", "𖹺"),
+ (0x16E5B, "M", "𖹻"),
+ (0x16E5C, "M", "𖹼"),
+ (0x16E5D, "M", "𖹽"),
+ (0x16E5E, "M", "𖹾"),
+ (0x16E5F, "M", "𖹿"),
+ (0x16E60, "V"),
+ (0x16E9B, "X"),
+ (0x16F00, "V"),
+ (0x16F4B, "X"),
+ (0x16F4F, "V"),
+ (0x16F88, "X"),
+ (0x16F8F, "V"),
+ (0x16FA0, "X"),
+ (0x16FE0, "V"),
+ (0x16FE5, "X"),
+ (0x16FF0, "V"),
+ (0x16FF2, "X"),
+ (0x17000, "V"),
+ (0x187F8, "X"),
+ (0x18800, "V"),
+ (0x18CD6, "X"),
+ (0x18CFF, "V"),
+ (0x18D09, "X"),
+ ]
+
+
+def _seg_61() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1AFF0, "V"),
+ (0x1AFF4, "X"),
+ (0x1AFF5, "V"),
+ (0x1AFFC, "X"),
+ (0x1AFFD, "V"),
+ (0x1AFFF, "X"),
+ (0x1B000, "V"),
+ (0x1B123, "X"),
+ (0x1B132, "V"),
+ (0x1B133, "X"),
+ (0x1B150, "V"),
+ (0x1B153, "X"),
+ (0x1B155, "V"),
+ (0x1B156, "X"),
+ (0x1B164, "V"),
+ (0x1B168, "X"),
+ (0x1B170, "V"),
+ (0x1B2FC, "X"),
+ (0x1BC00, "V"),
+ (0x1BC6B, "X"),
+ (0x1BC70, "V"),
+ (0x1BC7D, "X"),
+ (0x1BC80, "V"),
+ (0x1BC89, "X"),
+ (0x1BC90, "V"),
+ (0x1BC9A, "X"),
+ (0x1BC9C, "V"),
+ (0x1BCA0, "I"),
+ (0x1BCA4, "X"),
+ (0x1CC00, "V"),
+ (0x1CCD6, "M", "a"),
+ (0x1CCD7, "M", "b"),
+ (0x1CCD8, "M", "c"),
+ (0x1CCD9, "M", "d"),
+ (0x1CCDA, "M", "e"),
+ (0x1CCDB, "M", "f"),
+ (0x1CCDC, "M", "g"),
+ (0x1CCDD, "M", "h"),
+ (0x1CCDE, "M", "i"),
+ (0x1CCDF, "M", "j"),
+ (0x1CCE0, "M", "k"),
+ (0x1CCE1, "M", "l"),
+ (0x1CCE2, "M", "m"),
+ (0x1CCE3, "M", "n"),
+ (0x1CCE4, "M", "o"),
+ (0x1CCE5, "M", "p"),
+ (0x1CCE6, "M", "q"),
+ (0x1CCE7, "M", "r"),
+ (0x1CCE8, "M", "s"),
+ (0x1CCE9, "M", "t"),
+ (0x1CCEA, "M", "u"),
+ (0x1CCEB, "M", "v"),
+ (0x1CCEC, "M", "w"),
+ (0x1CCED, "M", "x"),
+ (0x1CCEE, "M", "y"),
+ (0x1CCEF, "M", "z"),
+ (0x1CCF0, "M", "0"),
+ (0x1CCF1, "M", "1"),
+ (0x1CCF2, "M", "2"),
+ (0x1CCF3, "M", "3"),
+ (0x1CCF4, "M", "4"),
+ (0x1CCF5, "M", "5"),
+ (0x1CCF6, "M", "6"),
+ (0x1CCF7, "M", "7"),
+ (0x1CCF8, "M", "8"),
+ (0x1CCF9, "M", "9"),
+ (0x1CCFA, "X"),
+ (0x1CD00, "V"),
+ (0x1CEB4, "X"),
+ (0x1CF00, "V"),
+ (0x1CF2E, "X"),
+ (0x1CF30, "V"),
+ (0x1CF47, "X"),
+ (0x1CF50, "V"),
+ (0x1CFC4, "X"),
+ (0x1D000, "V"),
+ (0x1D0F6, "X"),
+ (0x1D100, "V"),
+ (0x1D127, "X"),
+ (0x1D129, "V"),
+ (0x1D15E, "M", "𝅗𝅥"),
+ (0x1D15F, "M", "𝅘𝅥"),
+ (0x1D160, "M", "𝅘𝅥𝅮"),
+ (0x1D161, "M", "𝅘𝅥𝅯"),
+ (0x1D162, "M", "𝅘𝅥𝅰"),
+ (0x1D163, "M", "𝅘𝅥𝅱"),
+ (0x1D164, "M", "𝅘𝅥𝅲"),
+ (0x1D165, "V"),
+ (0x1D173, "I"),
+ (0x1D17B, "V"),
+ (0x1D1BB, "M", "𝆹𝅥"),
+ (0x1D1BC, "M", "𝆺𝅥"),
+ (0x1D1BD, "M", "𝆹𝅥𝅮"),
+ (0x1D1BE, "M", "𝆺𝅥𝅮"),
+ (0x1D1BF, "M", "𝆹𝅥𝅯"),
+ (0x1D1C0, "M", "𝆺𝅥𝅯"),
+ (0x1D1C1, "V"),
+ (0x1D1EB, "X"),
+ (0x1D200, "V"),
+ (0x1D246, "X"),
+ ]
+
+
+def _seg_62() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D2C0, "V"),
+ (0x1D2D4, "X"),
+ (0x1D2E0, "V"),
+ (0x1D2F4, "X"),
+ (0x1D300, "V"),
+ (0x1D357, "X"),
+ (0x1D360, "V"),
+ (0x1D379, "X"),
+ (0x1D400, "M", "a"),
+ (0x1D401, "M", "b"),
+ (0x1D402, "M", "c"),
+ (0x1D403, "M", "d"),
+ (0x1D404, "M", "e"),
+ (0x1D405, "M", "f"),
+ (0x1D406, "M", "g"),
+ (0x1D407, "M", "h"),
+ (0x1D408, "M", "i"),
+ (0x1D409, "M", "j"),
+ (0x1D40A, "M", "k"),
+ (0x1D40B, "M", "l"),
+ (0x1D40C, "M", "m"),
+ (0x1D40D, "M", "n"),
+ (0x1D40E, "M", "o"),
+ (0x1D40F, "M", "p"),
+ (0x1D410, "M", "q"),
+ (0x1D411, "M", "r"),
+ (0x1D412, "M", "s"),
+ (0x1D413, "M", "t"),
+ (0x1D414, "M", "u"),
+ (0x1D415, "M", "v"),
+ (0x1D416, "M", "w"),
+ (0x1D417, "M", "x"),
+ (0x1D418, "M", "y"),
+ (0x1D419, "M", "z"),
+ (0x1D41A, "M", "a"),
+ (0x1D41B, "M", "b"),
+ (0x1D41C, "M", "c"),
+ (0x1D41D, "M", "d"),
+ (0x1D41E, "M", "e"),
+ (0x1D41F, "M", "f"),
+ (0x1D420, "M", "g"),
+ (0x1D421, "M", "h"),
+ (0x1D422, "M", "i"),
+ (0x1D423, "M", "j"),
+ (0x1D424, "M", "k"),
+ (0x1D425, "M", "l"),
+ (0x1D426, "M", "m"),
+ (0x1D427, "M", "n"),
+ (0x1D428, "M", "o"),
+ (0x1D429, "M", "p"),
+ (0x1D42A, "M", "q"),
+ (0x1D42B, "M", "r"),
+ (0x1D42C, "M", "s"),
+ (0x1D42D, "M", "t"),
+ (0x1D42E, "M", "u"),
+ (0x1D42F, "M", "v"),
+ (0x1D430, "M", "w"),
+ (0x1D431, "M", "x"),
+ (0x1D432, "M", "y"),
+ (0x1D433, "M", "z"),
+ (0x1D434, "M", "a"),
+ (0x1D435, "M", "b"),
+ (0x1D436, "M", "c"),
+ (0x1D437, "M", "d"),
+ (0x1D438, "M", "e"),
+ (0x1D439, "M", "f"),
+ (0x1D43A, "M", "g"),
+ (0x1D43B, "M", "h"),
+ (0x1D43C, "M", "i"),
+ (0x1D43D, "M", "j"),
+ (0x1D43E, "M", "k"),
+ (0x1D43F, "M", "l"),
+ (0x1D440, "M", "m"),
+ (0x1D441, "M", "n"),
+ (0x1D442, "M", "o"),
+ (0x1D443, "M", "p"),
+ (0x1D444, "M", "q"),
+ (0x1D445, "M", "r"),
+ (0x1D446, "M", "s"),
+ (0x1D447, "M", "t"),
+ (0x1D448, "M", "u"),
+ (0x1D449, "M", "v"),
+ (0x1D44A, "M", "w"),
+ (0x1D44B, "M", "x"),
+ (0x1D44C, "M", "y"),
+ (0x1D44D, "M", "z"),
+ (0x1D44E, "M", "a"),
+ (0x1D44F, "M", "b"),
+ (0x1D450, "M", "c"),
+ (0x1D451, "M", "d"),
+ (0x1D452, "M", "e"),
+ (0x1D453, "M", "f"),
+ (0x1D454, "M", "g"),
+ (0x1D455, "X"),
+ (0x1D456, "M", "i"),
+ (0x1D457, "M", "j"),
+ (0x1D458, "M", "k"),
+ (0x1D459, "M", "l"),
+ (0x1D45A, "M", "m"),
+ (0x1D45B, "M", "n"),
+ ]
+
+
+def _seg_63() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D45C, "M", "o"),
+ (0x1D45D, "M", "p"),
+ (0x1D45E, "M", "q"),
+ (0x1D45F, "M", "r"),
+ (0x1D460, "M", "s"),
+ (0x1D461, "M", "t"),
+ (0x1D462, "M", "u"),
+ (0x1D463, "M", "v"),
+ (0x1D464, "M", "w"),
+ (0x1D465, "M", "x"),
+ (0x1D466, "M", "y"),
+ (0x1D467, "M", "z"),
+ (0x1D468, "M", "a"),
+ (0x1D469, "M", "b"),
+ (0x1D46A, "M", "c"),
+ (0x1D46B, "M", "d"),
+ (0x1D46C, "M", "e"),
+ (0x1D46D, "M", "f"),
+ (0x1D46E, "M", "g"),
+ (0x1D46F, "M", "h"),
+ (0x1D470, "M", "i"),
+ (0x1D471, "M", "j"),
+ (0x1D472, "M", "k"),
+ (0x1D473, "M", "l"),
+ (0x1D474, "M", "m"),
+ (0x1D475, "M", "n"),
+ (0x1D476, "M", "o"),
+ (0x1D477, "M", "p"),
+ (0x1D478, "M", "q"),
+ (0x1D479, "M", "r"),
+ (0x1D47A, "M", "s"),
+ (0x1D47B, "M", "t"),
+ (0x1D47C, "M", "u"),
+ (0x1D47D, "M", "v"),
+ (0x1D47E, "M", "w"),
+ (0x1D47F, "M", "x"),
+ (0x1D480, "M", "y"),
+ (0x1D481, "M", "z"),
+ (0x1D482, "M", "a"),
+ (0x1D483, "M", "b"),
+ (0x1D484, "M", "c"),
+ (0x1D485, "M", "d"),
+ (0x1D486, "M", "e"),
+ (0x1D487, "M", "f"),
+ (0x1D488, "M", "g"),
+ (0x1D489, "M", "h"),
+ (0x1D48A, "M", "i"),
+ (0x1D48B, "M", "j"),
+ (0x1D48C, "M", "k"),
+ (0x1D48D, "M", "l"),
+ (0x1D48E, "M", "m"),
+ (0x1D48F, "M", "n"),
+ (0x1D490, "M", "o"),
+ (0x1D491, "M", "p"),
+ (0x1D492, "M", "q"),
+ (0x1D493, "M", "r"),
+ (0x1D494, "M", "s"),
+ (0x1D495, "M", "t"),
+ (0x1D496, "M", "u"),
+ (0x1D497, "M", "v"),
+ (0x1D498, "M", "w"),
+ (0x1D499, "M", "x"),
+ (0x1D49A, "M", "y"),
+ (0x1D49B, "M", "z"),
+ (0x1D49C, "M", "a"),
+ (0x1D49D, "X"),
+ (0x1D49E, "M", "c"),
+ (0x1D49F, "M", "d"),
+ (0x1D4A0, "X"),
+ (0x1D4A2, "M", "g"),
+ (0x1D4A3, "X"),
+ (0x1D4A5, "M", "j"),
+ (0x1D4A6, "M", "k"),
+ (0x1D4A7, "X"),
+ (0x1D4A9, "M", "n"),
+ (0x1D4AA, "M", "o"),
+ (0x1D4AB, "M", "p"),
+ (0x1D4AC, "M", "q"),
+ (0x1D4AD, "X"),
+ (0x1D4AE, "M", "s"),
+ (0x1D4AF, "M", "t"),
+ (0x1D4B0, "M", "u"),
+ (0x1D4B1, "M", "v"),
+ (0x1D4B2, "M", "w"),
+ (0x1D4B3, "M", "x"),
+ (0x1D4B4, "M", "y"),
+ (0x1D4B5, "M", "z"),
+ (0x1D4B6, "M", "a"),
+ (0x1D4B7, "M", "b"),
+ (0x1D4B8, "M", "c"),
+ (0x1D4B9, "M", "d"),
+ (0x1D4BA, "X"),
+ (0x1D4BB, "M", "f"),
+ (0x1D4BC, "X"),
+ (0x1D4BD, "M", "h"),
+ (0x1D4BE, "M", "i"),
+ (0x1D4BF, "M", "j"),
+ (0x1D4C0, "M", "k"),
+ (0x1D4C1, "M", "l"),
+ (0x1D4C2, "M", "m"),
+ ]
+
+
+def _seg_64() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D4C3, "M", "n"),
+ (0x1D4C4, "X"),
+ (0x1D4C5, "M", "p"),
+ (0x1D4C6, "M", "q"),
+ (0x1D4C7, "M", "r"),
+ (0x1D4C8, "M", "s"),
+ (0x1D4C9, "M", "t"),
+ (0x1D4CA, "M", "u"),
+ (0x1D4CB, "M", "v"),
+ (0x1D4CC, "M", "w"),
+ (0x1D4CD, "M", "x"),
+ (0x1D4CE, "M", "y"),
+ (0x1D4CF, "M", "z"),
+ (0x1D4D0, "M", "a"),
+ (0x1D4D1, "M", "b"),
+ (0x1D4D2, "M", "c"),
+ (0x1D4D3, "M", "d"),
+ (0x1D4D4, "M", "e"),
+ (0x1D4D5, "M", "f"),
+ (0x1D4D6, "M", "g"),
+ (0x1D4D7, "M", "h"),
+ (0x1D4D8, "M", "i"),
+ (0x1D4D9, "M", "j"),
+ (0x1D4DA, "M", "k"),
+ (0x1D4DB, "M", "l"),
+ (0x1D4DC, "M", "m"),
+ (0x1D4DD, "M", "n"),
+ (0x1D4DE, "M", "o"),
+ (0x1D4DF, "M", "p"),
+ (0x1D4E0, "M", "q"),
+ (0x1D4E1, "M", "r"),
+ (0x1D4E2, "M", "s"),
+ (0x1D4E3, "M", "t"),
+ (0x1D4E4, "M", "u"),
+ (0x1D4E5, "M", "v"),
+ (0x1D4E6, "M", "w"),
+ (0x1D4E7, "M", "x"),
+ (0x1D4E8, "M", "y"),
+ (0x1D4E9, "M", "z"),
+ (0x1D4EA, "M", "a"),
+ (0x1D4EB, "M", "b"),
+ (0x1D4EC, "M", "c"),
+ (0x1D4ED, "M", "d"),
+ (0x1D4EE, "M", "e"),
+ (0x1D4EF, "M", "f"),
+ (0x1D4F0, "M", "g"),
+ (0x1D4F1, "M", "h"),
+ (0x1D4F2, "M", "i"),
+ (0x1D4F3, "M", "j"),
+ (0x1D4F4, "M", "k"),
+ (0x1D4F5, "M", "l"),
+ (0x1D4F6, "M", "m"),
+ (0x1D4F7, "M", "n"),
+ (0x1D4F8, "M", "o"),
+ (0x1D4F9, "M", "p"),
+ (0x1D4FA, "M", "q"),
+ (0x1D4FB, "M", "r"),
+ (0x1D4FC, "M", "s"),
+ (0x1D4FD, "M", "t"),
+ (0x1D4FE, "M", "u"),
+ (0x1D4FF, "M", "v"),
+ (0x1D500, "M", "w"),
+ (0x1D501, "M", "x"),
+ (0x1D502, "M", "y"),
+ (0x1D503, "M", "z"),
+ (0x1D504, "M", "a"),
+ (0x1D505, "M", "b"),
+ (0x1D506, "X"),
+ (0x1D507, "M", "d"),
+ (0x1D508, "M", "e"),
+ (0x1D509, "M", "f"),
+ (0x1D50A, "M", "g"),
+ (0x1D50B, "X"),
+ (0x1D50D, "M", "j"),
+ (0x1D50E, "M", "k"),
+ (0x1D50F, "M", "l"),
+ (0x1D510, "M", "m"),
+ (0x1D511, "M", "n"),
+ (0x1D512, "M", "o"),
+ (0x1D513, "M", "p"),
+ (0x1D514, "M", "q"),
+ (0x1D515, "X"),
+ (0x1D516, "M", "s"),
+ (0x1D517, "M", "t"),
+ (0x1D518, "M", "u"),
+ (0x1D519, "M", "v"),
+ (0x1D51A, "M", "w"),
+ (0x1D51B, "M", "x"),
+ (0x1D51C, "M", "y"),
+ (0x1D51D, "X"),
+ (0x1D51E, "M", "a"),
+ (0x1D51F, "M", "b"),
+ (0x1D520, "M", "c"),
+ (0x1D521, "M", "d"),
+ (0x1D522, "M", "e"),
+ (0x1D523, "M", "f"),
+ (0x1D524, "M", "g"),
+ (0x1D525, "M", "h"),
+ (0x1D526, "M", "i"),
+ (0x1D527, "M", "j"),
+ ]
+
+
+def _seg_65() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D528, "M", "k"),
+ (0x1D529, "M", "l"),
+ (0x1D52A, "M", "m"),
+ (0x1D52B, "M", "n"),
+ (0x1D52C, "M", "o"),
+ (0x1D52D, "M", "p"),
+ (0x1D52E, "M", "q"),
+ (0x1D52F, "M", "r"),
+ (0x1D530, "M", "s"),
+ (0x1D531, "M", "t"),
+ (0x1D532, "M", "u"),
+ (0x1D533, "M", "v"),
+ (0x1D534, "M", "w"),
+ (0x1D535, "M", "x"),
+ (0x1D536, "M", "y"),
+ (0x1D537, "M", "z"),
+ (0x1D538, "M", "a"),
+ (0x1D539, "M", "b"),
+ (0x1D53A, "X"),
+ (0x1D53B, "M", "d"),
+ (0x1D53C, "M", "e"),
+ (0x1D53D, "M", "f"),
+ (0x1D53E, "M", "g"),
+ (0x1D53F, "X"),
+ (0x1D540, "M", "i"),
+ (0x1D541, "M", "j"),
+ (0x1D542, "M", "k"),
+ (0x1D543, "M", "l"),
+ (0x1D544, "M", "m"),
+ (0x1D545, "X"),
+ (0x1D546, "M", "o"),
+ (0x1D547, "X"),
+ (0x1D54A, "M", "s"),
+ (0x1D54B, "M", "t"),
+ (0x1D54C, "M", "u"),
+ (0x1D54D, "M", "v"),
+ (0x1D54E, "M", "w"),
+ (0x1D54F, "M", "x"),
+ (0x1D550, "M", "y"),
+ (0x1D551, "X"),
+ (0x1D552, "M", "a"),
+ (0x1D553, "M", "b"),
+ (0x1D554, "M", "c"),
+ (0x1D555, "M", "d"),
+ (0x1D556, "M", "e"),
+ (0x1D557, "M", "f"),
+ (0x1D558, "M", "g"),
+ (0x1D559, "M", "h"),
+ (0x1D55A, "M", "i"),
+ (0x1D55B, "M", "j"),
+ (0x1D55C, "M", "k"),
+ (0x1D55D, "M", "l"),
+ (0x1D55E, "M", "m"),
+ (0x1D55F, "M", "n"),
+ (0x1D560, "M", "o"),
+ (0x1D561, "M", "p"),
+ (0x1D562, "M", "q"),
+ (0x1D563, "M", "r"),
+ (0x1D564, "M", "s"),
+ (0x1D565, "M", "t"),
+ (0x1D566, "M", "u"),
+ (0x1D567, "M", "v"),
+ (0x1D568, "M", "w"),
+ (0x1D569, "M", "x"),
+ (0x1D56A, "M", "y"),
+ (0x1D56B, "M", "z"),
+ (0x1D56C, "M", "a"),
+ (0x1D56D, "M", "b"),
+ (0x1D56E, "M", "c"),
+ (0x1D56F, "M", "d"),
+ (0x1D570, "M", "e"),
+ (0x1D571, "M", "f"),
+ (0x1D572, "M", "g"),
+ (0x1D573, "M", "h"),
+ (0x1D574, "M", "i"),
+ (0x1D575, "M", "j"),
+ (0x1D576, "M", "k"),
+ (0x1D577, "M", "l"),
+ (0x1D578, "M", "m"),
+ (0x1D579, "M", "n"),
+ (0x1D57A, "M", "o"),
+ (0x1D57B, "M", "p"),
+ (0x1D57C, "M", "q"),
+ (0x1D57D, "M", "r"),
+ (0x1D57E, "M", "s"),
+ (0x1D57F, "M", "t"),
+ (0x1D580, "M", "u"),
+ (0x1D581, "M", "v"),
+ (0x1D582, "M", "w"),
+ (0x1D583, "M", "x"),
+ (0x1D584, "M", "y"),
+ (0x1D585, "M", "z"),
+ (0x1D586, "M", "a"),
+ (0x1D587, "M", "b"),
+ (0x1D588, "M", "c"),
+ (0x1D589, "M", "d"),
+ (0x1D58A, "M", "e"),
+ (0x1D58B, "M", "f"),
+ (0x1D58C, "M", "g"),
+ (0x1D58D, "M", "h"),
+ ]
+
+
+def _seg_66() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D58E, "M", "i"),
+ (0x1D58F, "M", "j"),
+ (0x1D590, "M", "k"),
+ (0x1D591, "M", "l"),
+ (0x1D592, "M", "m"),
+ (0x1D593, "M", "n"),
+ (0x1D594, "M", "o"),
+ (0x1D595, "M", "p"),
+ (0x1D596, "M", "q"),
+ (0x1D597, "M", "r"),
+ (0x1D598, "M", "s"),
+ (0x1D599, "M", "t"),
+ (0x1D59A, "M", "u"),
+ (0x1D59B, "M", "v"),
+ (0x1D59C, "M", "w"),
+ (0x1D59D, "M", "x"),
+ (0x1D59E, "M", "y"),
+ (0x1D59F, "M", "z"),
+ (0x1D5A0, "M", "a"),
+ (0x1D5A1, "M", "b"),
+ (0x1D5A2, "M", "c"),
+ (0x1D5A3, "M", "d"),
+ (0x1D5A4, "M", "e"),
+ (0x1D5A5, "M", "f"),
+ (0x1D5A6, "M", "g"),
+ (0x1D5A7, "M", "h"),
+ (0x1D5A8, "M", "i"),
+ (0x1D5A9, "M", "j"),
+ (0x1D5AA, "M", "k"),
+ (0x1D5AB, "M", "l"),
+ (0x1D5AC, "M", "m"),
+ (0x1D5AD, "M", "n"),
+ (0x1D5AE, "M", "o"),
+ (0x1D5AF, "M", "p"),
+ (0x1D5B0, "M", "q"),
+ (0x1D5B1, "M", "r"),
+ (0x1D5B2, "M", "s"),
+ (0x1D5B3, "M", "t"),
+ (0x1D5B4, "M", "u"),
+ (0x1D5B5, "M", "v"),
+ (0x1D5B6, "M", "w"),
+ (0x1D5B7, "M", "x"),
+ (0x1D5B8, "M", "y"),
+ (0x1D5B9, "M", "z"),
+ (0x1D5BA, "M", "a"),
+ (0x1D5BB, "M", "b"),
+ (0x1D5BC, "M", "c"),
+ (0x1D5BD, "M", "d"),
+ (0x1D5BE, "M", "e"),
+ (0x1D5BF, "M", "f"),
+ (0x1D5C0, "M", "g"),
+ (0x1D5C1, "M", "h"),
+ (0x1D5C2, "M", "i"),
+ (0x1D5C3, "M", "j"),
+ (0x1D5C4, "M", "k"),
+ (0x1D5C5, "M", "l"),
+ (0x1D5C6, "M", "m"),
+ (0x1D5C7, "M", "n"),
+ (0x1D5C8, "M", "o"),
+ (0x1D5C9, "M", "p"),
+ (0x1D5CA, "M", "q"),
+ (0x1D5CB, "M", "r"),
+ (0x1D5CC, "M", "s"),
+ (0x1D5CD, "M", "t"),
+ (0x1D5CE, "M", "u"),
+ (0x1D5CF, "M", "v"),
+ (0x1D5D0, "M", "w"),
+ (0x1D5D1, "M", "x"),
+ (0x1D5D2, "M", "y"),
+ (0x1D5D3, "M", "z"),
+ (0x1D5D4, "M", "a"),
+ (0x1D5D5, "M", "b"),
+ (0x1D5D6, "M", "c"),
+ (0x1D5D7, "M", "d"),
+ (0x1D5D8, "M", "e"),
+ (0x1D5D9, "M", "f"),
+ (0x1D5DA, "M", "g"),
+ (0x1D5DB, "M", "h"),
+ (0x1D5DC, "M", "i"),
+ (0x1D5DD, "M", "j"),
+ (0x1D5DE, "M", "k"),
+ (0x1D5DF, "M", "l"),
+ (0x1D5E0, "M", "m"),
+ (0x1D5E1, "M", "n"),
+ (0x1D5E2, "M", "o"),
+ (0x1D5E3, "M", "p"),
+ (0x1D5E4, "M", "q"),
+ (0x1D5E5, "M", "r"),
+ (0x1D5E6, "M", "s"),
+ (0x1D5E7, "M", "t"),
+ (0x1D5E8, "M", "u"),
+ (0x1D5E9, "M", "v"),
+ (0x1D5EA, "M", "w"),
+ (0x1D5EB, "M", "x"),
+ (0x1D5EC, "M", "y"),
+ (0x1D5ED, "M", "z"),
+ (0x1D5EE, "M", "a"),
+ (0x1D5EF, "M", "b"),
+ (0x1D5F0, "M", "c"),
+ (0x1D5F1, "M", "d"),
+ ]
+
+
+def _seg_67() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D5F2, "M", "e"),
+ (0x1D5F3, "M", "f"),
+ (0x1D5F4, "M", "g"),
+ (0x1D5F5, "M", "h"),
+ (0x1D5F6, "M", "i"),
+ (0x1D5F7, "M", "j"),
+ (0x1D5F8, "M", "k"),
+ (0x1D5F9, "M", "l"),
+ (0x1D5FA, "M", "m"),
+ (0x1D5FB, "M", "n"),
+ (0x1D5FC, "M", "o"),
+ (0x1D5FD, "M", "p"),
+ (0x1D5FE, "M", "q"),
+ (0x1D5FF, "M", "r"),
+ (0x1D600, "M", "s"),
+ (0x1D601, "M", "t"),
+ (0x1D602, "M", "u"),
+ (0x1D603, "M", "v"),
+ (0x1D604, "M", "w"),
+ (0x1D605, "M", "x"),
+ (0x1D606, "M", "y"),
+ (0x1D607, "M", "z"),
+ (0x1D608, "M", "a"),
+ (0x1D609, "M", "b"),
+ (0x1D60A, "M", "c"),
+ (0x1D60B, "M", "d"),
+ (0x1D60C, "M", "e"),
+ (0x1D60D, "M", "f"),
+ (0x1D60E, "M", "g"),
+ (0x1D60F, "M", "h"),
+ (0x1D610, "M", "i"),
+ (0x1D611, "M", "j"),
+ (0x1D612, "M", "k"),
+ (0x1D613, "M", "l"),
+ (0x1D614, "M", "m"),
+ (0x1D615, "M", "n"),
+ (0x1D616, "M", "o"),
+ (0x1D617, "M", "p"),
+ (0x1D618, "M", "q"),
+ (0x1D619, "M", "r"),
+ (0x1D61A, "M", "s"),
+ (0x1D61B, "M", "t"),
+ (0x1D61C, "M", "u"),
+ (0x1D61D, "M", "v"),
+ (0x1D61E, "M", "w"),
+ (0x1D61F, "M", "x"),
+ (0x1D620, "M", "y"),
+ (0x1D621, "M", "z"),
+ (0x1D622, "M", "a"),
+ (0x1D623, "M", "b"),
+ (0x1D624, "M", "c"),
+ (0x1D625, "M", "d"),
+ (0x1D626, "M", "e"),
+ (0x1D627, "M", "f"),
+ (0x1D628, "M", "g"),
+ (0x1D629, "M", "h"),
+ (0x1D62A, "M", "i"),
+ (0x1D62B, "M", "j"),
+ (0x1D62C, "M", "k"),
+ (0x1D62D, "M", "l"),
+ (0x1D62E, "M", "m"),
+ (0x1D62F, "M", "n"),
+ (0x1D630, "M", "o"),
+ (0x1D631, "M", "p"),
+ (0x1D632, "M", "q"),
+ (0x1D633, "M", "r"),
+ (0x1D634, "M", "s"),
+ (0x1D635, "M", "t"),
+ (0x1D636, "M", "u"),
+ (0x1D637, "M", "v"),
+ (0x1D638, "M", "w"),
+ (0x1D639, "M", "x"),
+ (0x1D63A, "M", "y"),
+ (0x1D63B, "M", "z"),
+ (0x1D63C, "M", "a"),
+ (0x1D63D, "M", "b"),
+ (0x1D63E, "M", "c"),
+ (0x1D63F, "M", "d"),
+ (0x1D640, "M", "e"),
+ (0x1D641, "M", "f"),
+ (0x1D642, "M", "g"),
+ (0x1D643, "M", "h"),
+ (0x1D644, "M", "i"),
+ (0x1D645, "M", "j"),
+ (0x1D646, "M", "k"),
+ (0x1D647, "M", "l"),
+ (0x1D648, "M", "m"),
+ (0x1D649, "M", "n"),
+ (0x1D64A, "M", "o"),
+ (0x1D64B, "M", "p"),
+ (0x1D64C, "M", "q"),
+ (0x1D64D, "M", "r"),
+ (0x1D64E, "M", "s"),
+ (0x1D64F, "M", "t"),
+ (0x1D650, "M", "u"),
+ (0x1D651, "M", "v"),
+ (0x1D652, "M", "w"),
+ (0x1D653, "M", "x"),
+ (0x1D654, "M", "y"),
+ (0x1D655, "M", "z"),
+ ]
+
+
+def _seg_68() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D656, "M", "a"),
+ (0x1D657, "M", "b"),
+ (0x1D658, "M", "c"),
+ (0x1D659, "M", "d"),
+ (0x1D65A, "M", "e"),
+ (0x1D65B, "M", "f"),
+ (0x1D65C, "M", "g"),
+ (0x1D65D, "M", "h"),
+ (0x1D65E, "M", "i"),
+ (0x1D65F, "M", "j"),
+ (0x1D660, "M", "k"),
+ (0x1D661, "M", "l"),
+ (0x1D662, "M", "m"),
+ (0x1D663, "M", "n"),
+ (0x1D664, "M", "o"),
+ (0x1D665, "M", "p"),
+ (0x1D666, "M", "q"),
+ (0x1D667, "M", "r"),
+ (0x1D668, "M", "s"),
+ (0x1D669, "M", "t"),
+ (0x1D66A, "M", "u"),
+ (0x1D66B, "M", "v"),
+ (0x1D66C, "M", "w"),
+ (0x1D66D, "M", "x"),
+ (0x1D66E, "M", "y"),
+ (0x1D66F, "M", "z"),
+ (0x1D670, "M", "a"),
+ (0x1D671, "M", "b"),
+ (0x1D672, "M", "c"),
+ (0x1D673, "M", "d"),
+ (0x1D674, "M", "e"),
+ (0x1D675, "M", "f"),
+ (0x1D676, "M", "g"),
+ (0x1D677, "M", "h"),
+ (0x1D678, "M", "i"),
+ (0x1D679, "M", "j"),
+ (0x1D67A, "M", "k"),
+ (0x1D67B, "M", "l"),
+ (0x1D67C, "M", "m"),
+ (0x1D67D, "M", "n"),
+ (0x1D67E, "M", "o"),
+ (0x1D67F, "M", "p"),
+ (0x1D680, "M", "q"),
+ (0x1D681, "M", "r"),
+ (0x1D682, "M", "s"),
+ (0x1D683, "M", "t"),
+ (0x1D684, "M", "u"),
+ (0x1D685, "M", "v"),
+ (0x1D686, "M", "w"),
+ (0x1D687, "M", "x"),
+ (0x1D688, "M", "y"),
+ (0x1D689, "M", "z"),
+ (0x1D68A, "M", "a"),
+ (0x1D68B, "M", "b"),
+ (0x1D68C, "M", "c"),
+ (0x1D68D, "M", "d"),
+ (0x1D68E, "M", "e"),
+ (0x1D68F, "M", "f"),
+ (0x1D690, "M", "g"),
+ (0x1D691, "M", "h"),
+ (0x1D692, "M", "i"),
+ (0x1D693, "M", "j"),
+ (0x1D694, "M", "k"),
+ (0x1D695, "M", "l"),
+ (0x1D696, "M", "m"),
+ (0x1D697, "M", "n"),
+ (0x1D698, "M", "o"),
+ (0x1D699, "M", "p"),
+ (0x1D69A, "M", "q"),
+ (0x1D69B, "M", "r"),
+ (0x1D69C, "M", "s"),
+ (0x1D69D, "M", "t"),
+ (0x1D69E, "M", "u"),
+ (0x1D69F, "M", "v"),
+ (0x1D6A0, "M", "w"),
+ (0x1D6A1, "M", "x"),
+ (0x1D6A2, "M", "y"),
+ (0x1D6A3, "M", "z"),
+ (0x1D6A4, "M", "ı"),
+ (0x1D6A5, "M", "ȷ"),
+ (0x1D6A6, "X"),
+ (0x1D6A8, "M", "α"),
+ (0x1D6A9, "M", "β"),
+ (0x1D6AA, "M", "γ"),
+ (0x1D6AB, "M", "δ"),
+ (0x1D6AC, "M", "ε"),
+ (0x1D6AD, "M", "ζ"),
+ (0x1D6AE, "M", "η"),
+ (0x1D6AF, "M", "θ"),
+ (0x1D6B0, "M", "ι"),
+ (0x1D6B1, "M", "κ"),
+ (0x1D6B2, "M", "λ"),
+ (0x1D6B3, "M", "μ"),
+ (0x1D6B4, "M", "ν"),
+ (0x1D6B5, "M", "ξ"),
+ (0x1D6B6, "M", "ο"),
+ (0x1D6B7, "M", "π"),
+ (0x1D6B8, "M", "ρ"),
+ (0x1D6B9, "M", "θ"),
+ (0x1D6BA, "M", "σ"),
+ ]
+
+
+def _seg_69() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D6BB, "M", "τ"),
+ (0x1D6BC, "M", "υ"),
+ (0x1D6BD, "M", "φ"),
+ (0x1D6BE, "M", "χ"),
+ (0x1D6BF, "M", "ψ"),
+ (0x1D6C0, "M", "ω"),
+ (0x1D6C1, "M", "∇"),
+ (0x1D6C2, "M", "α"),
+ (0x1D6C3, "M", "β"),
+ (0x1D6C4, "M", "γ"),
+ (0x1D6C5, "M", "δ"),
+ (0x1D6C6, "M", "ε"),
+ (0x1D6C7, "M", "ζ"),
+ (0x1D6C8, "M", "η"),
+ (0x1D6C9, "M", "θ"),
+ (0x1D6CA, "M", "ι"),
+ (0x1D6CB, "M", "κ"),
+ (0x1D6CC, "M", "λ"),
+ (0x1D6CD, "M", "μ"),
+ (0x1D6CE, "M", "ν"),
+ (0x1D6CF, "M", "ξ"),
+ (0x1D6D0, "M", "ο"),
+ (0x1D6D1, "M", "π"),
+ (0x1D6D2, "M", "ρ"),
+ (0x1D6D3, "M", "σ"),
+ (0x1D6D5, "M", "τ"),
+ (0x1D6D6, "M", "υ"),
+ (0x1D6D7, "M", "φ"),
+ (0x1D6D8, "M", "χ"),
+ (0x1D6D9, "M", "ψ"),
+ (0x1D6DA, "M", "ω"),
+ (0x1D6DB, "M", "∂"),
+ (0x1D6DC, "M", "ε"),
+ (0x1D6DD, "M", "θ"),
+ (0x1D6DE, "M", "κ"),
+ (0x1D6DF, "M", "φ"),
+ (0x1D6E0, "M", "ρ"),
+ (0x1D6E1, "M", "π"),
+ (0x1D6E2, "M", "α"),
+ (0x1D6E3, "M", "β"),
+ (0x1D6E4, "M", "γ"),
+ (0x1D6E5, "M", "δ"),
+ (0x1D6E6, "M", "ε"),
+ (0x1D6E7, "M", "ζ"),
+ (0x1D6E8, "M", "η"),
+ (0x1D6E9, "M", "θ"),
+ (0x1D6EA, "M", "ι"),
+ (0x1D6EB, "M", "κ"),
+ (0x1D6EC, "M", "λ"),
+ (0x1D6ED, "M", "μ"),
+ (0x1D6EE, "M", "ν"),
+ (0x1D6EF, "M", "ξ"),
+ (0x1D6F0, "M", "ο"),
+ (0x1D6F1, "M", "π"),
+ (0x1D6F2, "M", "ρ"),
+ (0x1D6F3, "M", "θ"),
+ (0x1D6F4, "M", "σ"),
+ (0x1D6F5, "M", "τ"),
+ (0x1D6F6, "M", "υ"),
+ (0x1D6F7, "M", "φ"),
+ (0x1D6F8, "M", "χ"),
+ (0x1D6F9, "M", "ψ"),
+ (0x1D6FA, "M", "ω"),
+ (0x1D6FB, "M", "∇"),
+ (0x1D6FC, "M", "α"),
+ (0x1D6FD, "M", "β"),
+ (0x1D6FE, "M", "γ"),
+ (0x1D6FF, "M", "δ"),
+ (0x1D700, "M", "ε"),
+ (0x1D701, "M", "ζ"),
+ (0x1D702, "M", "η"),
+ (0x1D703, "M", "θ"),
+ (0x1D704, "M", "ι"),
+ (0x1D705, "M", "κ"),
+ (0x1D706, "M", "λ"),
+ (0x1D707, "M", "μ"),
+ (0x1D708, "M", "ν"),
+ (0x1D709, "M", "ξ"),
+ (0x1D70A, "M", "ο"),
+ (0x1D70B, "M", "π"),
+ (0x1D70C, "M", "ρ"),
+ (0x1D70D, "M", "σ"),
+ (0x1D70F, "M", "τ"),
+ (0x1D710, "M", "υ"),
+ (0x1D711, "M", "φ"),
+ (0x1D712, "M", "χ"),
+ (0x1D713, "M", "ψ"),
+ (0x1D714, "M", "ω"),
+ (0x1D715, "M", "∂"),
+ (0x1D716, "M", "ε"),
+ (0x1D717, "M", "θ"),
+ (0x1D718, "M", "κ"),
+ (0x1D719, "M", "φ"),
+ (0x1D71A, "M", "ρ"),
+ (0x1D71B, "M", "π"),
+ (0x1D71C, "M", "α"),
+ (0x1D71D, "M", "β"),
+ (0x1D71E, "M", "γ"),
+ (0x1D71F, "M", "δ"),
+ (0x1D720, "M", "ε"),
+ ]
+
+
+def _seg_70() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D721, "M", "ζ"),
+ (0x1D722, "M", "η"),
+ (0x1D723, "M", "θ"),
+ (0x1D724, "M", "ι"),
+ (0x1D725, "M", "κ"),
+ (0x1D726, "M", "λ"),
+ (0x1D727, "M", "μ"),
+ (0x1D728, "M", "ν"),
+ (0x1D729, "M", "ξ"),
+ (0x1D72A, "M", "ο"),
+ (0x1D72B, "M", "π"),
+ (0x1D72C, "M", "ρ"),
+ (0x1D72D, "M", "θ"),
+ (0x1D72E, "M", "σ"),
+ (0x1D72F, "M", "τ"),
+ (0x1D730, "M", "υ"),
+ (0x1D731, "M", "φ"),
+ (0x1D732, "M", "χ"),
+ (0x1D733, "M", "ψ"),
+ (0x1D734, "M", "ω"),
+ (0x1D735, "M", "∇"),
+ (0x1D736, "M", "α"),
+ (0x1D737, "M", "β"),
+ (0x1D738, "M", "γ"),
+ (0x1D739, "M", "δ"),
+ (0x1D73A, "M", "ε"),
+ (0x1D73B, "M", "ζ"),
+ (0x1D73C, "M", "η"),
+ (0x1D73D, "M", "θ"),
+ (0x1D73E, "M", "ι"),
+ (0x1D73F, "M", "κ"),
+ (0x1D740, "M", "λ"),
+ (0x1D741, "M", "μ"),
+ (0x1D742, "M", "ν"),
+ (0x1D743, "M", "ξ"),
+ (0x1D744, "M", "ο"),
+ (0x1D745, "M", "π"),
+ (0x1D746, "M", "ρ"),
+ (0x1D747, "M", "σ"),
+ (0x1D749, "M", "τ"),
+ (0x1D74A, "M", "υ"),
+ (0x1D74B, "M", "φ"),
+ (0x1D74C, "M", "χ"),
+ (0x1D74D, "M", "ψ"),
+ (0x1D74E, "M", "ω"),
+ (0x1D74F, "M", "∂"),
+ (0x1D750, "M", "ε"),
+ (0x1D751, "M", "θ"),
+ (0x1D752, "M", "κ"),
+ (0x1D753, "M", "φ"),
+ (0x1D754, "M", "ρ"),
+ (0x1D755, "M", "π"),
+ (0x1D756, "M", "α"),
+ (0x1D757, "M", "β"),
+ (0x1D758, "M", "γ"),
+ (0x1D759, "M", "δ"),
+ (0x1D75A, "M", "ε"),
+ (0x1D75B, "M", "ζ"),
+ (0x1D75C, "M", "η"),
+ (0x1D75D, "M", "θ"),
+ (0x1D75E, "M", "ι"),
+ (0x1D75F, "M", "κ"),
+ (0x1D760, "M", "λ"),
+ (0x1D761, "M", "μ"),
+ (0x1D762, "M", "ν"),
+ (0x1D763, "M", "ξ"),
+ (0x1D764, "M", "ο"),
+ (0x1D765, "M", "π"),
+ (0x1D766, "M", "ρ"),
+ (0x1D767, "M", "θ"),
+ (0x1D768, "M", "σ"),
+ (0x1D769, "M", "τ"),
+ (0x1D76A, "M", "υ"),
+ (0x1D76B, "M", "φ"),
+ (0x1D76C, "M", "χ"),
+ (0x1D76D, "M", "ψ"),
+ (0x1D76E, "M", "ω"),
+ (0x1D76F, "M", "∇"),
+ (0x1D770, "M", "α"),
+ (0x1D771, "M", "β"),
+ (0x1D772, "M", "γ"),
+ (0x1D773, "M", "δ"),
+ (0x1D774, "M", "ε"),
+ (0x1D775, "M", "ζ"),
+ (0x1D776, "M", "η"),
+ (0x1D777, "M", "θ"),
+ (0x1D778, "M", "ι"),
+ (0x1D779, "M", "κ"),
+ (0x1D77A, "M", "λ"),
+ (0x1D77B, "M", "μ"),
+ (0x1D77C, "M", "ν"),
+ (0x1D77D, "M", "ξ"),
+ (0x1D77E, "M", "ο"),
+ (0x1D77F, "M", "π"),
+ (0x1D780, "M", "ρ"),
+ (0x1D781, "M", "σ"),
+ (0x1D783, "M", "τ"),
+ (0x1D784, "M", "υ"),
+ (0x1D785, "M", "φ"),
+ (0x1D786, "M", "χ"),
+ ]
+
+
+def _seg_71() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D787, "M", "ψ"),
+ (0x1D788, "M", "ω"),
+ (0x1D789, "M", "∂"),
+ (0x1D78A, "M", "ε"),
+ (0x1D78B, "M", "θ"),
+ (0x1D78C, "M", "κ"),
+ (0x1D78D, "M", "φ"),
+ (0x1D78E, "M", "ρ"),
+ (0x1D78F, "M", "π"),
+ (0x1D790, "M", "α"),
+ (0x1D791, "M", "β"),
+ (0x1D792, "M", "γ"),
+ (0x1D793, "M", "δ"),
+ (0x1D794, "M", "ε"),
+ (0x1D795, "M", "ζ"),
+ (0x1D796, "M", "η"),
+ (0x1D797, "M", "θ"),
+ (0x1D798, "M", "ι"),
+ (0x1D799, "M", "κ"),
+ (0x1D79A, "M", "λ"),
+ (0x1D79B, "M", "μ"),
+ (0x1D79C, "M", "ν"),
+ (0x1D79D, "M", "ξ"),
+ (0x1D79E, "M", "ο"),
+ (0x1D79F, "M", "π"),
+ (0x1D7A0, "M", "ρ"),
+ (0x1D7A1, "M", "θ"),
+ (0x1D7A2, "M", "σ"),
+ (0x1D7A3, "M", "τ"),
+ (0x1D7A4, "M", "υ"),
+ (0x1D7A5, "M", "φ"),
+ (0x1D7A6, "M", "χ"),
+ (0x1D7A7, "M", "ψ"),
+ (0x1D7A8, "M", "ω"),
+ (0x1D7A9, "M", "∇"),
+ (0x1D7AA, "M", "α"),
+ (0x1D7AB, "M", "β"),
+ (0x1D7AC, "M", "γ"),
+ (0x1D7AD, "M", "δ"),
+ (0x1D7AE, "M", "ε"),
+ (0x1D7AF, "M", "ζ"),
+ (0x1D7B0, "M", "η"),
+ (0x1D7B1, "M", "θ"),
+ (0x1D7B2, "M", "ι"),
+ (0x1D7B3, "M", "κ"),
+ (0x1D7B4, "M", "λ"),
+ (0x1D7B5, "M", "μ"),
+ (0x1D7B6, "M", "ν"),
+ (0x1D7B7, "M", "ξ"),
+ (0x1D7B8, "M", "ο"),
+ (0x1D7B9, "M", "π"),
+ (0x1D7BA, "M", "ρ"),
+ (0x1D7BB, "M", "σ"),
+ (0x1D7BD, "M", "τ"),
+ (0x1D7BE, "M", "υ"),
+ (0x1D7BF, "M", "φ"),
+ (0x1D7C0, "M", "χ"),
+ (0x1D7C1, "M", "ψ"),
+ (0x1D7C2, "M", "ω"),
+ (0x1D7C3, "M", "∂"),
+ (0x1D7C4, "M", "ε"),
+ (0x1D7C5, "M", "θ"),
+ (0x1D7C6, "M", "κ"),
+ (0x1D7C7, "M", "φ"),
+ (0x1D7C8, "M", "ρ"),
+ (0x1D7C9, "M", "π"),
+ (0x1D7CA, "M", "ϝ"),
+ (0x1D7CC, "X"),
+ (0x1D7CE, "M", "0"),
+ (0x1D7CF, "M", "1"),
+ (0x1D7D0, "M", "2"),
+ (0x1D7D1, "M", "3"),
+ (0x1D7D2, "M", "4"),
+ (0x1D7D3, "M", "5"),
+ (0x1D7D4, "M", "6"),
+ (0x1D7D5, "M", "7"),
+ (0x1D7D6, "M", "8"),
+ (0x1D7D7, "M", "9"),
+ (0x1D7D8, "M", "0"),
+ (0x1D7D9, "M", "1"),
+ (0x1D7DA, "M", "2"),
+ (0x1D7DB, "M", "3"),
+ (0x1D7DC, "M", "4"),
+ (0x1D7DD, "M", "5"),
+ (0x1D7DE, "M", "6"),
+ (0x1D7DF, "M", "7"),
+ (0x1D7E0, "M", "8"),
+ (0x1D7E1, "M", "9"),
+ (0x1D7E2, "M", "0"),
+ (0x1D7E3, "M", "1"),
+ (0x1D7E4, "M", "2"),
+ (0x1D7E5, "M", "3"),
+ (0x1D7E6, "M", "4"),
+ (0x1D7E7, "M", "5"),
+ (0x1D7E8, "M", "6"),
+ (0x1D7E9, "M", "7"),
+ (0x1D7EA, "M", "8"),
+ (0x1D7EB, "M", "9"),
+ (0x1D7EC, "M", "0"),
+ (0x1D7ED, "M", "1"),
+ ]
+
+
+def _seg_72() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1D7EE, "M", "2"),
+ (0x1D7EF, "M", "3"),
+ (0x1D7F0, "M", "4"),
+ (0x1D7F1, "M", "5"),
+ (0x1D7F2, "M", "6"),
+ (0x1D7F3, "M", "7"),
+ (0x1D7F4, "M", "8"),
+ (0x1D7F5, "M", "9"),
+ (0x1D7F6, "M", "0"),
+ (0x1D7F7, "M", "1"),
+ (0x1D7F8, "M", "2"),
+ (0x1D7F9, "M", "3"),
+ (0x1D7FA, "M", "4"),
+ (0x1D7FB, "M", "5"),
+ (0x1D7FC, "M", "6"),
+ (0x1D7FD, "M", "7"),
+ (0x1D7FE, "M", "8"),
+ (0x1D7FF, "M", "9"),
+ (0x1D800, "V"),
+ (0x1DA8C, "X"),
+ (0x1DA9B, "V"),
+ (0x1DAA0, "X"),
+ (0x1DAA1, "V"),
+ (0x1DAB0, "X"),
+ (0x1DF00, "V"),
+ (0x1DF1F, "X"),
+ (0x1DF25, "V"),
+ (0x1DF2B, "X"),
+ (0x1E000, "V"),
+ (0x1E007, "X"),
+ (0x1E008, "V"),
+ (0x1E019, "X"),
+ (0x1E01B, "V"),
+ (0x1E022, "X"),
+ (0x1E023, "V"),
+ (0x1E025, "X"),
+ (0x1E026, "V"),
+ (0x1E02B, "X"),
+ (0x1E030, "M", "а"),
+ (0x1E031, "M", "б"),
+ (0x1E032, "M", "в"),
+ (0x1E033, "M", "г"),
+ (0x1E034, "M", "д"),
+ (0x1E035, "M", "е"),
+ (0x1E036, "M", "ж"),
+ (0x1E037, "M", "з"),
+ (0x1E038, "M", "и"),
+ (0x1E039, "M", "к"),
+ (0x1E03A, "M", "л"),
+ (0x1E03B, "M", "м"),
+ (0x1E03C, "M", "о"),
+ (0x1E03D, "M", "п"),
+ (0x1E03E, "M", "р"),
+ (0x1E03F, "M", "с"),
+ (0x1E040, "M", "т"),
+ (0x1E041, "M", "у"),
+ (0x1E042, "M", "ф"),
+ (0x1E043, "M", "х"),
+ (0x1E044, "M", "ц"),
+ (0x1E045, "M", "ч"),
+ (0x1E046, "M", "ш"),
+ (0x1E047, "M", "ы"),
+ (0x1E048, "M", "э"),
+ (0x1E049, "M", "ю"),
+ (0x1E04A, "M", "ꚉ"),
+ (0x1E04B, "M", "ә"),
+ (0x1E04C, "M", "і"),
+ (0x1E04D, "M", "ј"),
+ (0x1E04E, "M", "ө"),
+ (0x1E04F, "M", "ү"),
+ (0x1E050, "M", "ӏ"),
+ (0x1E051, "M", "а"),
+ (0x1E052, "M", "б"),
+ (0x1E053, "M", "в"),
+ (0x1E054, "M", "г"),
+ (0x1E055, "M", "д"),
+ (0x1E056, "M", "е"),
+ (0x1E057, "M", "ж"),
+ (0x1E058, "M", "з"),
+ (0x1E059, "M", "и"),
+ (0x1E05A, "M", "к"),
+ (0x1E05B, "M", "л"),
+ (0x1E05C, "M", "о"),
+ (0x1E05D, "M", "п"),
+ (0x1E05E, "M", "с"),
+ (0x1E05F, "M", "у"),
+ (0x1E060, "M", "ф"),
+ (0x1E061, "M", "х"),
+ (0x1E062, "M", "ц"),
+ (0x1E063, "M", "ч"),
+ (0x1E064, "M", "ш"),
+ (0x1E065, "M", "ъ"),
+ (0x1E066, "M", "ы"),
+ (0x1E067, "M", "ґ"),
+ (0x1E068, "M", "і"),
+ (0x1E069, "M", "ѕ"),
+ (0x1E06A, "M", "џ"),
+ (0x1E06B, "M", "ҫ"),
+ (0x1E06C, "M", "ꙑ"),
+ (0x1E06D, "M", "ұ"),
+ ]
+
+
+def _seg_73() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1E06E, "X"),
+ (0x1E08F, "V"),
+ (0x1E090, "X"),
+ (0x1E100, "V"),
+ (0x1E12D, "X"),
+ (0x1E130, "V"),
+ (0x1E13E, "X"),
+ (0x1E140, "V"),
+ (0x1E14A, "X"),
+ (0x1E14E, "V"),
+ (0x1E150, "X"),
+ (0x1E290, "V"),
+ (0x1E2AF, "X"),
+ (0x1E2C0, "V"),
+ (0x1E2FA, "X"),
+ (0x1E2FF, "V"),
+ (0x1E300, "X"),
+ (0x1E4D0, "V"),
+ (0x1E4FA, "X"),
+ (0x1E5D0, "V"),
+ (0x1E5FB, "X"),
+ (0x1E5FF, "V"),
+ (0x1E600, "X"),
+ (0x1E7E0, "V"),
+ (0x1E7E7, "X"),
+ (0x1E7E8, "V"),
+ (0x1E7EC, "X"),
+ (0x1E7ED, "V"),
+ (0x1E7EF, "X"),
+ (0x1E7F0, "V"),
+ (0x1E7FF, "X"),
+ (0x1E800, "V"),
+ (0x1E8C5, "X"),
+ (0x1E8C7, "V"),
+ (0x1E8D7, "X"),
+ (0x1E900, "M", "𞤢"),
+ (0x1E901, "M", "𞤣"),
+ (0x1E902, "M", "𞤤"),
+ (0x1E903, "M", "𞤥"),
+ (0x1E904, "M", "𞤦"),
+ (0x1E905, "M", "𞤧"),
+ (0x1E906, "M", "𞤨"),
+ (0x1E907, "M", "𞤩"),
+ (0x1E908, "M", "𞤪"),
+ (0x1E909, "M", "𞤫"),
+ (0x1E90A, "M", "𞤬"),
+ (0x1E90B, "M", "𞤭"),
+ (0x1E90C, "M", "𞤮"),
+ (0x1E90D, "M", "𞤯"),
+ (0x1E90E, "M", "𞤰"),
+ (0x1E90F, "M", "𞤱"),
+ (0x1E910, "M", "𞤲"),
+ (0x1E911, "M", "𞤳"),
+ (0x1E912, "M", "𞤴"),
+ (0x1E913, "M", "𞤵"),
+ (0x1E914, "M", "𞤶"),
+ (0x1E915, "M", "𞤷"),
+ (0x1E916, "M", "𞤸"),
+ (0x1E917, "M", "𞤹"),
+ (0x1E918, "M", "𞤺"),
+ (0x1E919, "M", "𞤻"),
+ (0x1E91A, "M", "𞤼"),
+ (0x1E91B, "M", "𞤽"),
+ (0x1E91C, "M", "𞤾"),
+ (0x1E91D, "M", "𞤿"),
+ (0x1E91E, "M", "𞥀"),
+ (0x1E91F, "M", "𞥁"),
+ (0x1E920, "M", "𞥂"),
+ (0x1E921, "M", "𞥃"),
+ (0x1E922, "V"),
+ (0x1E94C, "X"),
+ (0x1E950, "V"),
+ (0x1E95A, "X"),
+ (0x1E95E, "V"),
+ (0x1E960, "X"),
+ (0x1EC71, "V"),
+ (0x1ECB5, "X"),
+ (0x1ED01, "V"),
+ (0x1ED3E, "X"),
+ (0x1EE00, "M", "ا"),
+ (0x1EE01, "M", "ب"),
+ (0x1EE02, "M", "ج"),
+ (0x1EE03, "M", "د"),
+ (0x1EE04, "X"),
+ (0x1EE05, "M", "و"),
+ (0x1EE06, "M", "ز"),
+ (0x1EE07, "M", "ح"),
+ (0x1EE08, "M", "ط"),
+ (0x1EE09, "M", "ي"),
+ (0x1EE0A, "M", "ك"),
+ (0x1EE0B, "M", "ل"),
+ (0x1EE0C, "M", "م"),
+ (0x1EE0D, "M", "ن"),
+ (0x1EE0E, "M", "س"),
+ (0x1EE0F, "M", "ع"),
+ (0x1EE10, "M", "ف"),
+ (0x1EE11, "M", "ص"),
+ (0x1EE12, "M", "ق"),
+ (0x1EE13, "M", "ر"),
+ (0x1EE14, "M", "ش"),
+ ]
+
+
+def _seg_74() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1EE15, "M", "ت"),
+ (0x1EE16, "M", "ث"),
+ (0x1EE17, "M", "خ"),
+ (0x1EE18, "M", "ذ"),
+ (0x1EE19, "M", "ض"),
+ (0x1EE1A, "M", "ظ"),
+ (0x1EE1B, "M", "غ"),
+ (0x1EE1C, "M", "ٮ"),
+ (0x1EE1D, "M", "ں"),
+ (0x1EE1E, "M", "ڡ"),
+ (0x1EE1F, "M", "ٯ"),
+ (0x1EE20, "X"),
+ (0x1EE21, "M", "ب"),
+ (0x1EE22, "M", "ج"),
+ (0x1EE23, "X"),
+ (0x1EE24, "M", "ه"),
+ (0x1EE25, "X"),
+ (0x1EE27, "M", "ح"),
+ (0x1EE28, "X"),
+ (0x1EE29, "M", "ي"),
+ (0x1EE2A, "M", "ك"),
+ (0x1EE2B, "M", "ل"),
+ (0x1EE2C, "M", "م"),
+ (0x1EE2D, "M", "ن"),
+ (0x1EE2E, "M", "س"),
+ (0x1EE2F, "M", "ع"),
+ (0x1EE30, "M", "ف"),
+ (0x1EE31, "M", "ص"),
+ (0x1EE32, "M", "ق"),
+ (0x1EE33, "X"),
+ (0x1EE34, "M", "ش"),
+ (0x1EE35, "M", "ت"),
+ (0x1EE36, "M", "ث"),
+ (0x1EE37, "M", "خ"),
+ (0x1EE38, "X"),
+ (0x1EE39, "M", "ض"),
+ (0x1EE3A, "X"),
+ (0x1EE3B, "M", "غ"),
+ (0x1EE3C, "X"),
+ (0x1EE42, "M", "ج"),
+ (0x1EE43, "X"),
+ (0x1EE47, "M", "ح"),
+ (0x1EE48, "X"),
+ (0x1EE49, "M", "ي"),
+ (0x1EE4A, "X"),
+ (0x1EE4B, "M", "ل"),
+ (0x1EE4C, "X"),
+ (0x1EE4D, "M", "ن"),
+ (0x1EE4E, "M", "س"),
+ (0x1EE4F, "M", "ع"),
+ (0x1EE50, "X"),
+ (0x1EE51, "M", "ص"),
+ (0x1EE52, "M", "ق"),
+ (0x1EE53, "X"),
+ (0x1EE54, "M", "ش"),
+ (0x1EE55, "X"),
+ (0x1EE57, "M", "خ"),
+ (0x1EE58, "X"),
+ (0x1EE59, "M", "ض"),
+ (0x1EE5A, "X"),
+ (0x1EE5B, "M", "غ"),
+ (0x1EE5C, "X"),
+ (0x1EE5D, "M", "ں"),
+ (0x1EE5E, "X"),
+ (0x1EE5F, "M", "ٯ"),
+ (0x1EE60, "X"),
+ (0x1EE61, "M", "ب"),
+ (0x1EE62, "M", "ج"),
+ (0x1EE63, "X"),
+ (0x1EE64, "M", "ه"),
+ (0x1EE65, "X"),
+ (0x1EE67, "M", "ح"),
+ (0x1EE68, "M", "ط"),
+ (0x1EE69, "M", "ي"),
+ (0x1EE6A, "M", "ك"),
+ (0x1EE6B, "X"),
+ (0x1EE6C, "M", "م"),
+ (0x1EE6D, "M", "ن"),
+ (0x1EE6E, "M", "س"),
+ (0x1EE6F, "M", "ع"),
+ (0x1EE70, "M", "ف"),
+ (0x1EE71, "M", "ص"),
+ (0x1EE72, "M", "ق"),
+ (0x1EE73, "X"),
+ (0x1EE74, "M", "ش"),
+ (0x1EE75, "M", "ت"),
+ (0x1EE76, "M", "ث"),
+ (0x1EE77, "M", "خ"),
+ (0x1EE78, "X"),
+ (0x1EE79, "M", "ض"),
+ (0x1EE7A, "M", "ظ"),
+ (0x1EE7B, "M", "غ"),
+ (0x1EE7C, "M", "ٮ"),
+ (0x1EE7D, "X"),
+ (0x1EE7E, "M", "ڡ"),
+ (0x1EE7F, "X"),
+ (0x1EE80, "M", "ا"),
+ (0x1EE81, "M", "ب"),
+ (0x1EE82, "M", "ج"),
+ (0x1EE83, "M", "د"),
+ ]
+
+
+def _seg_75() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1EE84, "M", "ه"),
+ (0x1EE85, "M", "و"),
+ (0x1EE86, "M", "ز"),
+ (0x1EE87, "M", "ح"),
+ (0x1EE88, "M", "ط"),
+ (0x1EE89, "M", "ي"),
+ (0x1EE8A, "X"),
+ (0x1EE8B, "M", "ل"),
+ (0x1EE8C, "M", "م"),
+ (0x1EE8D, "M", "ن"),
+ (0x1EE8E, "M", "س"),
+ (0x1EE8F, "M", "ع"),
+ (0x1EE90, "M", "ف"),
+ (0x1EE91, "M", "ص"),
+ (0x1EE92, "M", "ق"),
+ (0x1EE93, "M", "ر"),
+ (0x1EE94, "M", "ش"),
+ (0x1EE95, "M", "ت"),
+ (0x1EE96, "M", "ث"),
+ (0x1EE97, "M", "خ"),
+ (0x1EE98, "M", "ذ"),
+ (0x1EE99, "M", "ض"),
+ (0x1EE9A, "M", "ظ"),
+ (0x1EE9B, "M", "غ"),
+ (0x1EE9C, "X"),
+ (0x1EEA1, "M", "ب"),
+ (0x1EEA2, "M", "ج"),
+ (0x1EEA3, "M", "د"),
+ (0x1EEA4, "X"),
+ (0x1EEA5, "M", "و"),
+ (0x1EEA6, "M", "ز"),
+ (0x1EEA7, "M", "ح"),
+ (0x1EEA8, "M", "ط"),
+ (0x1EEA9, "M", "ي"),
+ (0x1EEAA, "X"),
+ (0x1EEAB, "M", "ل"),
+ (0x1EEAC, "M", "م"),
+ (0x1EEAD, "M", "ن"),
+ (0x1EEAE, "M", "س"),
+ (0x1EEAF, "M", "ع"),
+ (0x1EEB0, "M", "ف"),
+ (0x1EEB1, "M", "ص"),
+ (0x1EEB2, "M", "ق"),
+ (0x1EEB3, "M", "ر"),
+ (0x1EEB4, "M", "ش"),
+ (0x1EEB5, "M", "ت"),
+ (0x1EEB6, "M", "ث"),
+ (0x1EEB7, "M", "خ"),
+ (0x1EEB8, "M", "ذ"),
+ (0x1EEB9, "M", "ض"),
+ (0x1EEBA, "M", "ظ"),
+ (0x1EEBB, "M", "غ"),
+ (0x1EEBC, "X"),
+ (0x1EEF0, "V"),
+ (0x1EEF2, "X"),
+ (0x1F000, "V"),
+ (0x1F02C, "X"),
+ (0x1F030, "V"),
+ (0x1F094, "X"),
+ (0x1F0A0, "V"),
+ (0x1F0AF, "X"),
+ (0x1F0B1, "V"),
+ (0x1F0C0, "X"),
+ (0x1F0C1, "V"),
+ (0x1F0D0, "X"),
+ (0x1F0D1, "V"),
+ (0x1F0F6, "X"),
+ (0x1F101, "M", "0,"),
+ (0x1F102, "M", "1,"),
+ (0x1F103, "M", "2,"),
+ (0x1F104, "M", "3,"),
+ (0x1F105, "M", "4,"),
+ (0x1F106, "M", "5,"),
+ (0x1F107, "M", "6,"),
+ (0x1F108, "M", "7,"),
+ (0x1F109, "M", "8,"),
+ (0x1F10A, "M", "9,"),
+ (0x1F10B, "V"),
+ (0x1F110, "M", "(a)"),
+ (0x1F111, "M", "(b)"),
+ (0x1F112, "M", "(c)"),
+ (0x1F113, "M", "(d)"),
+ (0x1F114, "M", "(e)"),
+ (0x1F115, "M", "(f)"),
+ (0x1F116, "M", "(g)"),
+ (0x1F117, "M", "(h)"),
+ (0x1F118, "M", "(i)"),
+ (0x1F119, "M", "(j)"),
+ (0x1F11A, "M", "(k)"),
+ (0x1F11B, "M", "(l)"),
+ (0x1F11C, "M", "(m)"),
+ (0x1F11D, "M", "(n)"),
+ (0x1F11E, "M", "(o)"),
+ (0x1F11F, "M", "(p)"),
+ (0x1F120, "M", "(q)"),
+ (0x1F121, "M", "(r)"),
+ (0x1F122, "M", "(s)"),
+ (0x1F123, "M", "(t)"),
+ (0x1F124, "M", "(u)"),
+ (0x1F125, "M", "(v)"),
+ ]
+
+
+def _seg_76() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1F126, "M", "(w)"),
+ (0x1F127, "M", "(x)"),
+ (0x1F128, "M", "(y)"),
+ (0x1F129, "M", "(z)"),
+ (0x1F12A, "M", "〔s〕"),
+ (0x1F12B, "M", "c"),
+ (0x1F12C, "M", "r"),
+ (0x1F12D, "M", "cd"),
+ (0x1F12E, "M", "wz"),
+ (0x1F12F, "V"),
+ (0x1F130, "M", "a"),
+ (0x1F131, "M", "b"),
+ (0x1F132, "M", "c"),
+ (0x1F133, "M", "d"),
+ (0x1F134, "M", "e"),
+ (0x1F135, "M", "f"),
+ (0x1F136, "M", "g"),
+ (0x1F137, "M", "h"),
+ (0x1F138, "M", "i"),
+ (0x1F139, "M", "j"),
+ (0x1F13A, "M", "k"),
+ (0x1F13B, "M", "l"),
+ (0x1F13C, "M", "m"),
+ (0x1F13D, "M", "n"),
+ (0x1F13E, "M", "o"),
+ (0x1F13F, "M", "p"),
+ (0x1F140, "M", "q"),
+ (0x1F141, "M", "r"),
+ (0x1F142, "M", "s"),
+ (0x1F143, "M", "t"),
+ (0x1F144, "M", "u"),
+ (0x1F145, "M", "v"),
+ (0x1F146, "M", "w"),
+ (0x1F147, "M", "x"),
+ (0x1F148, "M", "y"),
+ (0x1F149, "M", "z"),
+ (0x1F14A, "M", "hv"),
+ (0x1F14B, "M", "mv"),
+ (0x1F14C, "M", "sd"),
+ (0x1F14D, "M", "ss"),
+ (0x1F14E, "M", "ppv"),
+ (0x1F14F, "M", "wc"),
+ (0x1F150, "V"),
+ (0x1F16A, "M", "mc"),
+ (0x1F16B, "M", "md"),
+ (0x1F16C, "M", "mr"),
+ (0x1F16D, "V"),
+ (0x1F190, "M", "dj"),
+ (0x1F191, "V"),
+ (0x1F1AE, "X"),
+ (0x1F1E6, "V"),
+ (0x1F200, "M", "ほか"),
+ (0x1F201, "M", "ココ"),
+ (0x1F202, "M", "サ"),
+ (0x1F203, "X"),
+ (0x1F210, "M", "手"),
+ (0x1F211, "M", "字"),
+ (0x1F212, "M", "双"),
+ (0x1F213, "M", "デ"),
+ (0x1F214, "M", "二"),
+ (0x1F215, "M", "多"),
+ (0x1F216, "M", "解"),
+ (0x1F217, "M", "天"),
+ (0x1F218, "M", "交"),
+ (0x1F219, "M", "映"),
+ (0x1F21A, "M", "無"),
+ (0x1F21B, "M", "料"),
+ (0x1F21C, "M", "前"),
+ (0x1F21D, "M", "後"),
+ (0x1F21E, "M", "再"),
+ (0x1F21F, "M", "新"),
+ (0x1F220, "M", "初"),
+ (0x1F221, "M", "終"),
+ (0x1F222, "M", "生"),
+ (0x1F223, "M", "販"),
+ (0x1F224, "M", "声"),
+ (0x1F225, "M", "吹"),
+ (0x1F226, "M", "演"),
+ (0x1F227, "M", "投"),
+ (0x1F228, "M", "捕"),
+ (0x1F229, "M", "一"),
+ (0x1F22A, "M", "三"),
+ (0x1F22B, "M", "遊"),
+ (0x1F22C, "M", "左"),
+ (0x1F22D, "M", "中"),
+ (0x1F22E, "M", "右"),
+ (0x1F22F, "M", "指"),
+ (0x1F230, "M", "走"),
+ (0x1F231, "M", "打"),
+ (0x1F232, "M", "禁"),
+ (0x1F233, "M", "空"),
+ (0x1F234, "M", "合"),
+ (0x1F235, "M", "満"),
+ (0x1F236, "M", "有"),
+ (0x1F237, "M", "月"),
+ (0x1F238, "M", "申"),
+ (0x1F239, "M", "割"),
+ (0x1F23A, "M", "営"),
+ (0x1F23B, "M", "配"),
+ (0x1F23C, "X"),
+ ]
+
+
+def _seg_77() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x1F240, "M", "〔本〕"),
+ (0x1F241, "M", "〔三〕"),
+ (0x1F242, "M", "〔二〕"),
+ (0x1F243, "M", "〔安〕"),
+ (0x1F244, "M", "〔点〕"),
+ (0x1F245, "M", "〔打〕"),
+ (0x1F246, "M", "〔盗〕"),
+ (0x1F247, "M", "〔勝〕"),
+ (0x1F248, "M", "〔敗〕"),
+ (0x1F249, "X"),
+ (0x1F250, "M", "得"),
+ (0x1F251, "M", "可"),
+ (0x1F252, "X"),
+ (0x1F260, "V"),
+ (0x1F266, "X"),
+ (0x1F300, "V"),
+ (0x1F6D8, "X"),
+ (0x1F6DC, "V"),
+ (0x1F6ED, "X"),
+ (0x1F6F0, "V"),
+ (0x1F6FD, "X"),
+ (0x1F700, "V"),
+ (0x1F777, "X"),
+ (0x1F77B, "V"),
+ (0x1F7DA, "X"),
+ (0x1F7E0, "V"),
+ (0x1F7EC, "X"),
+ (0x1F7F0, "V"),
+ (0x1F7F1, "X"),
+ (0x1F800, "V"),
+ (0x1F80C, "X"),
+ (0x1F810, "V"),
+ (0x1F848, "X"),
+ (0x1F850, "V"),
+ (0x1F85A, "X"),
+ (0x1F860, "V"),
+ (0x1F888, "X"),
+ (0x1F890, "V"),
+ (0x1F8AE, "X"),
+ (0x1F8B0, "V"),
+ (0x1F8BC, "X"),
+ (0x1F8C0, "V"),
+ (0x1F8C2, "X"),
+ (0x1F900, "V"),
+ (0x1FA54, "X"),
+ (0x1FA60, "V"),
+ (0x1FA6E, "X"),
+ (0x1FA70, "V"),
+ (0x1FA7D, "X"),
+ (0x1FA80, "V"),
+ (0x1FA8A, "X"),
+ (0x1FA8F, "V"),
+ (0x1FAC7, "X"),
+ (0x1FACE, "V"),
+ (0x1FADD, "X"),
+ (0x1FADF, "V"),
+ (0x1FAEA, "X"),
+ (0x1FAF0, "V"),
+ (0x1FAF9, "X"),
+ (0x1FB00, "V"),
+ (0x1FB93, "X"),
+ (0x1FB94, "V"),
+ (0x1FBF0, "M", "0"),
+ (0x1FBF1, "M", "1"),
+ (0x1FBF2, "M", "2"),
+ (0x1FBF3, "M", "3"),
+ (0x1FBF4, "M", "4"),
+ (0x1FBF5, "M", "5"),
+ (0x1FBF6, "M", "6"),
+ (0x1FBF7, "M", "7"),
+ (0x1FBF8, "M", "8"),
+ (0x1FBF9, "M", "9"),
+ (0x1FBFA, "X"),
+ (0x20000, "V"),
+ (0x2A6E0, "X"),
+ (0x2A700, "V"),
+ (0x2B73A, "X"),
+ (0x2B740, "V"),
+ (0x2B81E, "X"),
+ (0x2B820, "V"),
+ (0x2CEA2, "X"),
+ (0x2CEB0, "V"),
+ (0x2EBE1, "X"),
+ (0x2EBF0, "V"),
+ (0x2EE5E, "X"),
+ (0x2F800, "M", "丽"),
+ (0x2F801, "M", "丸"),
+ (0x2F802, "M", "乁"),
+ (0x2F803, "M", "𠄢"),
+ (0x2F804, "M", "你"),
+ (0x2F805, "M", "侮"),
+ (0x2F806, "M", "侻"),
+ (0x2F807, "M", "倂"),
+ (0x2F808, "M", "偺"),
+ (0x2F809, "M", "備"),
+ (0x2F80A, "M", "僧"),
+ (0x2F80B, "M", "像"),
+ (0x2F80C, "M", "㒞"),
+ (0x2F80D, "M", "𠘺"),
+ (0x2F80E, "M", "免"),
+ ]
+
+
+def _seg_78() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F80F, "M", "兔"),
+ (0x2F810, "M", "兤"),
+ (0x2F811, "M", "具"),
+ (0x2F812, "M", "𠔜"),
+ (0x2F813, "M", "㒹"),
+ (0x2F814, "M", "內"),
+ (0x2F815, "M", "再"),
+ (0x2F816, "M", "𠕋"),
+ (0x2F817, "M", "冗"),
+ (0x2F818, "M", "冤"),
+ (0x2F819, "M", "仌"),
+ (0x2F81A, "M", "冬"),
+ (0x2F81B, "M", "况"),
+ (0x2F81C, "M", "𩇟"),
+ (0x2F81D, "M", "凵"),
+ (0x2F81E, "M", "刃"),
+ (0x2F81F, "M", "㓟"),
+ (0x2F820, "M", "刻"),
+ (0x2F821, "M", "剆"),
+ (0x2F822, "M", "割"),
+ (0x2F823, "M", "剷"),
+ (0x2F824, "M", "㔕"),
+ (0x2F825, "M", "勇"),
+ (0x2F826, "M", "勉"),
+ (0x2F827, "M", "勤"),
+ (0x2F828, "M", "勺"),
+ (0x2F829, "M", "包"),
+ (0x2F82A, "M", "匆"),
+ (0x2F82B, "M", "北"),
+ (0x2F82C, "M", "卉"),
+ (0x2F82D, "M", "卑"),
+ (0x2F82E, "M", "博"),
+ (0x2F82F, "M", "即"),
+ (0x2F830, "M", "卽"),
+ (0x2F831, "M", "卿"),
+ (0x2F834, "M", "𠨬"),
+ (0x2F835, "M", "灰"),
+ (0x2F836, "M", "及"),
+ (0x2F837, "M", "叟"),
+ (0x2F838, "M", "𠭣"),
+ (0x2F839, "M", "叫"),
+ (0x2F83A, "M", "叱"),
+ (0x2F83B, "M", "吆"),
+ (0x2F83C, "M", "咞"),
+ (0x2F83D, "M", "吸"),
+ (0x2F83E, "M", "呈"),
+ (0x2F83F, "M", "周"),
+ (0x2F840, "M", "咢"),
+ (0x2F841, "M", "哶"),
+ (0x2F842, "M", "唐"),
+ (0x2F843, "M", "啓"),
+ (0x2F844, "M", "啣"),
+ (0x2F845, "M", "善"),
+ (0x2F847, "M", "喙"),
+ (0x2F848, "M", "喫"),
+ (0x2F849, "M", "喳"),
+ (0x2F84A, "M", "嗂"),
+ (0x2F84B, "M", "圖"),
+ (0x2F84C, "M", "嘆"),
+ (0x2F84D, "M", "圗"),
+ (0x2F84E, "M", "噑"),
+ (0x2F84F, "M", "噴"),
+ (0x2F850, "M", "切"),
+ (0x2F851, "M", "壮"),
+ (0x2F852, "M", "城"),
+ (0x2F853, "M", "埴"),
+ (0x2F854, "M", "堍"),
+ (0x2F855, "M", "型"),
+ (0x2F856, "M", "堲"),
+ (0x2F857, "M", "報"),
+ (0x2F858, "M", "墬"),
+ (0x2F859, "M", "𡓤"),
+ (0x2F85A, "M", "売"),
+ (0x2F85B, "M", "壷"),
+ (0x2F85C, "M", "夆"),
+ (0x2F85D, "M", "多"),
+ (0x2F85E, "M", "夢"),
+ (0x2F85F, "M", "奢"),
+ (0x2F860, "M", "𡚨"),
+ (0x2F861, "M", "𡛪"),
+ (0x2F862, "M", "姬"),
+ (0x2F863, "M", "娛"),
+ (0x2F864, "M", "娧"),
+ (0x2F865, "M", "姘"),
+ (0x2F866, "M", "婦"),
+ (0x2F867, "M", "㛮"),
+ (0x2F868, "M", "㛼"),
+ (0x2F869, "M", "嬈"),
+ (0x2F86A, "M", "嬾"),
+ (0x2F86C, "M", "𡧈"),
+ (0x2F86D, "M", "寃"),
+ (0x2F86E, "M", "寘"),
+ (0x2F86F, "M", "寧"),
+ (0x2F870, "M", "寳"),
+ (0x2F871, "M", "𡬘"),
+ (0x2F872, "M", "寿"),
+ (0x2F873, "M", "将"),
+ (0x2F874, "M", "当"),
+ (0x2F875, "M", "尢"),
+ (0x2F876, "M", "㞁"),
+ ]
+
+
+def _seg_79() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F877, "M", "屠"),
+ (0x2F878, "M", "屮"),
+ (0x2F879, "M", "峀"),
+ (0x2F87A, "M", "岍"),
+ (0x2F87B, "M", "𡷤"),
+ (0x2F87C, "M", "嵃"),
+ (0x2F87D, "M", "𡷦"),
+ (0x2F87E, "M", "嵮"),
+ (0x2F87F, "M", "嵫"),
+ (0x2F880, "M", "嵼"),
+ (0x2F881, "M", "巡"),
+ (0x2F882, "M", "巢"),
+ (0x2F883, "M", "㠯"),
+ (0x2F884, "M", "巽"),
+ (0x2F885, "M", "帨"),
+ (0x2F886, "M", "帽"),
+ (0x2F887, "M", "幩"),
+ (0x2F888, "M", "㡢"),
+ (0x2F889, "M", "𢆃"),
+ (0x2F88A, "M", "㡼"),
+ (0x2F88B, "M", "庰"),
+ (0x2F88C, "M", "庳"),
+ (0x2F88D, "M", "庶"),
+ (0x2F88E, "M", "廊"),
+ (0x2F88F, "M", "𪎒"),
+ (0x2F890, "M", "廾"),
+ (0x2F891, "M", "𢌱"),
+ (0x2F893, "M", "舁"),
+ (0x2F894, "M", "弢"),
+ (0x2F896, "M", "㣇"),
+ (0x2F897, "M", "𣊸"),
+ (0x2F898, "M", "𦇚"),
+ (0x2F899, "M", "形"),
+ (0x2F89A, "M", "彫"),
+ (0x2F89B, "M", "㣣"),
+ (0x2F89C, "M", "徚"),
+ (0x2F89D, "M", "忍"),
+ (0x2F89E, "M", "志"),
+ (0x2F89F, "M", "忹"),
+ (0x2F8A0, "M", "悁"),
+ (0x2F8A1, "M", "㤺"),
+ (0x2F8A2, "M", "㤜"),
+ (0x2F8A3, "M", "悔"),
+ (0x2F8A4, "M", "𢛔"),
+ (0x2F8A5, "M", "惇"),
+ (0x2F8A6, "M", "慈"),
+ (0x2F8A7, "M", "慌"),
+ (0x2F8A8, "M", "慎"),
+ (0x2F8A9, "M", "慌"),
+ (0x2F8AA, "M", "慺"),
+ (0x2F8AB, "M", "憎"),
+ (0x2F8AC, "M", "憲"),
+ (0x2F8AD, "M", "憤"),
+ (0x2F8AE, "M", "憯"),
+ (0x2F8AF, "M", "懞"),
+ (0x2F8B0, "M", "懲"),
+ (0x2F8B1, "M", "懶"),
+ (0x2F8B2, "M", "成"),
+ (0x2F8B3, "M", "戛"),
+ (0x2F8B4, "M", "扝"),
+ (0x2F8B5, "M", "抱"),
+ (0x2F8B6, "M", "拔"),
+ (0x2F8B7, "M", "捐"),
+ (0x2F8B8, "M", "𢬌"),
+ (0x2F8B9, "M", "挽"),
+ (0x2F8BA, "M", "拼"),
+ (0x2F8BB, "M", "捨"),
+ (0x2F8BC, "M", "掃"),
+ (0x2F8BD, "M", "揤"),
+ (0x2F8BE, "M", "𢯱"),
+ (0x2F8BF, "M", "搢"),
+ (0x2F8C0, "M", "揅"),
+ (0x2F8C1, "M", "掩"),
+ (0x2F8C2, "M", "㨮"),
+ (0x2F8C3, "M", "摩"),
+ (0x2F8C4, "M", "摾"),
+ (0x2F8C5, "M", "撝"),
+ (0x2F8C6, "M", "摷"),
+ (0x2F8C7, "M", "㩬"),
+ (0x2F8C8, "M", "敏"),
+ (0x2F8C9, "M", "敬"),
+ (0x2F8CA, "M", "𣀊"),
+ (0x2F8CB, "M", "旣"),
+ (0x2F8CC, "M", "書"),
+ (0x2F8CD, "M", "晉"),
+ (0x2F8CE, "M", "㬙"),
+ (0x2F8CF, "M", "暑"),
+ (0x2F8D0, "M", "㬈"),
+ (0x2F8D1, "M", "㫤"),
+ (0x2F8D2, "M", "冒"),
+ (0x2F8D3, "M", "冕"),
+ (0x2F8D4, "M", "最"),
+ (0x2F8D5, "M", "暜"),
+ (0x2F8D6, "M", "肭"),
+ (0x2F8D7, "M", "䏙"),
+ (0x2F8D8, "M", "朗"),
+ (0x2F8D9, "M", "望"),
+ (0x2F8DA, "M", "朡"),
+ (0x2F8DB, "M", "杞"),
+ (0x2F8DC, "M", "杓"),
+ ]
+
+
+def _seg_80() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F8DD, "M", "𣏃"),
+ (0x2F8DE, "M", "㭉"),
+ (0x2F8DF, "M", "柺"),
+ (0x2F8E0, "M", "枅"),
+ (0x2F8E1, "M", "桒"),
+ (0x2F8E2, "M", "梅"),
+ (0x2F8E3, "M", "𣑭"),
+ (0x2F8E4, "M", "梎"),
+ (0x2F8E5, "M", "栟"),
+ (0x2F8E6, "M", "椔"),
+ (0x2F8E7, "M", "㮝"),
+ (0x2F8E8, "M", "楂"),
+ (0x2F8E9, "M", "榣"),
+ (0x2F8EA, "M", "槪"),
+ (0x2F8EB, "M", "檨"),
+ (0x2F8EC, "M", "𣚣"),
+ (0x2F8ED, "M", "櫛"),
+ (0x2F8EE, "M", "㰘"),
+ (0x2F8EF, "M", "次"),
+ (0x2F8F0, "M", "𣢧"),
+ (0x2F8F1, "M", "歔"),
+ (0x2F8F2, "M", "㱎"),
+ (0x2F8F3, "M", "歲"),
+ (0x2F8F4, "M", "殟"),
+ (0x2F8F5, "M", "殺"),
+ (0x2F8F6, "M", "殻"),
+ (0x2F8F7, "M", "𣪍"),
+ (0x2F8F8, "M", "𡴋"),
+ (0x2F8F9, "M", "𣫺"),
+ (0x2F8FA, "M", "汎"),
+ (0x2F8FB, "M", "𣲼"),
+ (0x2F8FC, "M", "沿"),
+ (0x2F8FD, "M", "泍"),
+ (0x2F8FE, "M", "汧"),
+ (0x2F8FF, "M", "洖"),
+ (0x2F900, "M", "派"),
+ (0x2F901, "M", "海"),
+ (0x2F902, "M", "流"),
+ (0x2F903, "M", "浩"),
+ (0x2F904, "M", "浸"),
+ (0x2F905, "M", "涅"),
+ (0x2F906, "M", "𣴞"),
+ (0x2F907, "M", "洴"),
+ (0x2F908, "M", "港"),
+ (0x2F909, "M", "湮"),
+ (0x2F90A, "M", "㴳"),
+ (0x2F90B, "M", "滋"),
+ (0x2F90C, "M", "滇"),
+ (0x2F90D, "M", "𣻑"),
+ (0x2F90E, "M", "淹"),
+ (0x2F90F, "M", "潮"),
+ (0x2F910, "M", "𣽞"),
+ (0x2F911, "M", "𣾎"),
+ (0x2F912, "M", "濆"),
+ (0x2F913, "M", "瀹"),
+ (0x2F914, "M", "瀞"),
+ (0x2F915, "M", "瀛"),
+ (0x2F916, "M", "㶖"),
+ (0x2F917, "M", "灊"),
+ (0x2F918, "M", "災"),
+ (0x2F919, "M", "灷"),
+ (0x2F91A, "M", "炭"),
+ (0x2F91B, "M", "𠔥"),
+ (0x2F91C, "M", "煅"),
+ (0x2F91D, "M", "𤉣"),
+ (0x2F91E, "M", "熜"),
+ (0x2F91F, "M", "𤎫"),
+ (0x2F920, "M", "爨"),
+ (0x2F921, "M", "爵"),
+ (0x2F922, "M", "牐"),
+ (0x2F923, "M", "𤘈"),
+ (0x2F924, "M", "犀"),
+ (0x2F925, "M", "犕"),
+ (0x2F926, "M", "𤜵"),
+ (0x2F927, "M", "𤠔"),
+ (0x2F928, "M", "獺"),
+ (0x2F929, "M", "王"),
+ (0x2F92A, "M", "㺬"),
+ (0x2F92B, "M", "玥"),
+ (0x2F92C, "M", "㺸"),
+ (0x2F92E, "M", "瑇"),
+ (0x2F92F, "M", "瑜"),
+ (0x2F930, "M", "瑱"),
+ (0x2F931, "M", "璅"),
+ (0x2F932, "M", "瓊"),
+ (0x2F933, "M", "㼛"),
+ (0x2F934, "M", "甤"),
+ (0x2F935, "M", "𤰶"),
+ (0x2F936, "M", "甾"),
+ (0x2F937, "M", "𤲒"),
+ (0x2F938, "M", "異"),
+ (0x2F939, "M", "𢆟"),
+ (0x2F93A, "M", "瘐"),
+ (0x2F93B, "M", "𤾡"),
+ (0x2F93C, "M", "𤾸"),
+ (0x2F93D, "M", "𥁄"),
+ (0x2F93E, "M", "㿼"),
+ (0x2F93F, "M", "䀈"),
+ (0x2F940, "M", "直"),
+ (0x2F941, "M", "𥃳"),
+ ]
+
+
+def _seg_81() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F942, "M", "𥃲"),
+ (0x2F943, "M", "𥄙"),
+ (0x2F944, "M", "𥄳"),
+ (0x2F945, "M", "眞"),
+ (0x2F946, "M", "真"),
+ (0x2F948, "M", "睊"),
+ (0x2F949, "M", "䀹"),
+ (0x2F94A, "M", "瞋"),
+ (0x2F94B, "M", "䁆"),
+ (0x2F94C, "M", "䂖"),
+ (0x2F94D, "M", "𥐝"),
+ (0x2F94E, "M", "硎"),
+ (0x2F94F, "M", "碌"),
+ (0x2F950, "M", "磌"),
+ (0x2F951, "M", "䃣"),
+ (0x2F952, "M", "𥘦"),
+ (0x2F953, "M", "祖"),
+ (0x2F954, "M", "𥚚"),
+ (0x2F955, "M", "𥛅"),
+ (0x2F956, "M", "福"),
+ (0x2F957, "M", "秫"),
+ (0x2F958, "M", "䄯"),
+ (0x2F959, "M", "穀"),
+ (0x2F95A, "M", "穊"),
+ (0x2F95B, "M", "穏"),
+ (0x2F95C, "M", "𥥼"),
+ (0x2F95D, "M", "𥪧"),
+ (0x2F95F, "M", "竮"),
+ (0x2F960, "M", "䈂"),
+ (0x2F961, "M", "𥮫"),
+ (0x2F962, "M", "篆"),
+ (0x2F963, "M", "築"),
+ (0x2F964, "M", "䈧"),
+ (0x2F965, "M", "𥲀"),
+ (0x2F966, "M", "糒"),
+ (0x2F967, "M", "䊠"),
+ (0x2F968, "M", "糨"),
+ (0x2F969, "M", "糣"),
+ (0x2F96A, "M", "紀"),
+ (0x2F96B, "M", "𥾆"),
+ (0x2F96C, "M", "絣"),
+ (0x2F96D, "M", "䌁"),
+ (0x2F96E, "M", "緇"),
+ (0x2F96F, "M", "縂"),
+ (0x2F970, "M", "繅"),
+ (0x2F971, "M", "䌴"),
+ (0x2F972, "M", "𦈨"),
+ (0x2F973, "M", "𦉇"),
+ (0x2F974, "M", "䍙"),
+ (0x2F975, "M", "𦋙"),
+ (0x2F976, "M", "罺"),
+ (0x2F977, "M", "𦌾"),
+ (0x2F978, "M", "羕"),
+ (0x2F979, "M", "翺"),
+ (0x2F97A, "M", "者"),
+ (0x2F97B, "M", "𦓚"),
+ (0x2F97C, "M", "𦔣"),
+ (0x2F97D, "M", "聠"),
+ (0x2F97E, "M", "𦖨"),
+ (0x2F97F, "M", "聰"),
+ (0x2F980, "M", "𣍟"),
+ (0x2F981, "M", "䏕"),
+ (0x2F982, "M", "育"),
+ (0x2F983, "M", "脃"),
+ (0x2F984, "M", "䐋"),
+ (0x2F985, "M", "脾"),
+ (0x2F986, "M", "媵"),
+ (0x2F987, "M", "𦞧"),
+ (0x2F988, "M", "𦞵"),
+ (0x2F989, "M", "𣎓"),
+ (0x2F98A, "M", "𣎜"),
+ (0x2F98B, "M", "舁"),
+ (0x2F98C, "M", "舄"),
+ (0x2F98D, "M", "辞"),
+ (0x2F98E, "M", "䑫"),
+ (0x2F98F, "M", "芑"),
+ (0x2F990, "M", "芋"),
+ (0x2F991, "M", "芝"),
+ (0x2F992, "M", "劳"),
+ (0x2F993, "M", "花"),
+ (0x2F994, "M", "芳"),
+ (0x2F995, "M", "芽"),
+ (0x2F996, "M", "苦"),
+ (0x2F997, "M", "𦬼"),
+ (0x2F998, "M", "若"),
+ (0x2F999, "M", "茝"),
+ (0x2F99A, "M", "荣"),
+ (0x2F99B, "M", "莭"),
+ (0x2F99C, "M", "茣"),
+ (0x2F99D, "M", "莽"),
+ (0x2F99E, "M", "菧"),
+ (0x2F99F, "M", "著"),
+ (0x2F9A0, "M", "荓"),
+ (0x2F9A1, "M", "菊"),
+ (0x2F9A2, "M", "菌"),
+ (0x2F9A3, "M", "菜"),
+ (0x2F9A4, "M", "𦰶"),
+ (0x2F9A5, "M", "𦵫"),
+ (0x2F9A6, "M", "𦳕"),
+ (0x2F9A7, "M", "䔫"),
+ ]
+
+
+def _seg_82() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2F9A8, "M", "蓱"),
+ (0x2F9A9, "M", "蓳"),
+ (0x2F9AA, "M", "蔖"),
+ (0x2F9AB, "M", "𧏊"),
+ (0x2F9AC, "M", "蕤"),
+ (0x2F9AD, "M", "𦼬"),
+ (0x2F9AE, "M", "䕝"),
+ (0x2F9AF, "M", "䕡"),
+ (0x2F9B0, "M", "𦾱"),
+ (0x2F9B1, "M", "𧃒"),
+ (0x2F9B2, "M", "䕫"),
+ (0x2F9B3, "M", "虐"),
+ (0x2F9B4, "M", "虜"),
+ (0x2F9B5, "M", "虧"),
+ (0x2F9B6, "M", "虩"),
+ (0x2F9B7, "M", "蚩"),
+ (0x2F9B8, "M", "蚈"),
+ (0x2F9B9, "M", "蜎"),
+ (0x2F9BA, "M", "蛢"),
+ (0x2F9BB, "M", "蝹"),
+ (0x2F9BC, "M", "蜨"),
+ (0x2F9BD, "M", "蝫"),
+ (0x2F9BE, "M", "螆"),
+ (0x2F9BF, "M", "䗗"),
+ (0x2F9C0, "M", "蟡"),
+ (0x2F9C1, "M", "蠁"),
+ (0x2F9C2, "M", "䗹"),
+ (0x2F9C3, "M", "衠"),
+ (0x2F9C4, "M", "衣"),
+ (0x2F9C5, "M", "𧙧"),
+ (0x2F9C6, "M", "裗"),
+ (0x2F9C7, "M", "裞"),
+ (0x2F9C8, "M", "䘵"),
+ (0x2F9C9, "M", "裺"),
+ (0x2F9CA, "M", "㒻"),
+ (0x2F9CB, "M", "𧢮"),
+ (0x2F9CC, "M", "𧥦"),
+ (0x2F9CD, "M", "䚾"),
+ (0x2F9CE, "M", "䛇"),
+ (0x2F9CF, "M", "誠"),
+ (0x2F9D0, "M", "諭"),
+ (0x2F9D1, "M", "變"),
+ (0x2F9D2, "M", "豕"),
+ (0x2F9D3, "M", "𧲨"),
+ (0x2F9D4, "M", "貫"),
+ (0x2F9D5, "M", "賁"),
+ (0x2F9D6, "M", "贛"),
+ (0x2F9D7, "M", "起"),
+ (0x2F9D8, "M", "𧼯"),
+ (0x2F9D9, "M", "𠠄"),
+ (0x2F9DA, "M", "跋"),
+ (0x2F9DB, "M", "趼"),
+ (0x2F9DC, "M", "跰"),
+ (0x2F9DD, "M", "𠣞"),
+ (0x2F9DE, "M", "軔"),
+ (0x2F9DF, "M", "輸"),
+ (0x2F9E0, "M", "𨗒"),
+ (0x2F9E1, "M", "𨗭"),
+ (0x2F9E2, "M", "邔"),
+ (0x2F9E3, "M", "郱"),
+ (0x2F9E4, "M", "鄑"),
+ (0x2F9E5, "M", "𨜮"),
+ (0x2F9E6, "M", "鄛"),
+ (0x2F9E7, "M", "鈸"),
+ (0x2F9E8, "M", "鋗"),
+ (0x2F9E9, "M", "鋘"),
+ (0x2F9EA, "M", "鉼"),
+ (0x2F9EB, "M", "鏹"),
+ (0x2F9EC, "M", "鐕"),
+ (0x2F9ED, "M", "𨯺"),
+ (0x2F9EE, "M", "開"),
+ (0x2F9EF, "M", "䦕"),
+ (0x2F9F0, "M", "閷"),
+ (0x2F9F1, "M", "𨵷"),
+ (0x2F9F2, "M", "䧦"),
+ (0x2F9F3, "M", "雃"),
+ (0x2F9F4, "M", "嶲"),
+ (0x2F9F5, "M", "霣"),
+ (0x2F9F6, "M", "𩅅"),
+ (0x2F9F7, "M", "𩈚"),
+ (0x2F9F8, "M", "䩮"),
+ (0x2F9F9, "M", "䩶"),
+ (0x2F9FA, "M", "韠"),
+ (0x2F9FB, "M", "𩐊"),
+ (0x2F9FC, "M", "䪲"),
+ (0x2F9FD, "M", "𩒖"),
+ (0x2F9FE, "M", "頋"),
+ (0x2FA00, "M", "頩"),
+ (0x2FA01, "M", "𩖶"),
+ (0x2FA02, "M", "飢"),
+ (0x2FA03, "M", "䬳"),
+ (0x2FA04, "M", "餩"),
+ (0x2FA05, "M", "馧"),
+ (0x2FA06, "M", "駂"),
+ (0x2FA07, "M", "駾"),
+ (0x2FA08, "M", "䯎"),
+ (0x2FA09, "M", "𩬰"),
+ (0x2FA0A, "M", "鬒"),
+ (0x2FA0B, "M", "鱀"),
+ (0x2FA0C, "M", "鳽"),
+ ]
+
+
+def _seg_83() -> List[Union[Tuple[int, str], Tuple[int, str, str]]]:
+ return [
+ (0x2FA0D, "M", "䳎"),
+ (0x2FA0E, "M", "䳭"),
+ (0x2FA0F, "M", "鵧"),
+ (0x2FA10, "M", "𪃎"),
+ (0x2FA11, "M", "䳸"),
+ (0x2FA12, "M", "𪄅"),
+ (0x2FA13, "M", "𪈎"),
+ (0x2FA14, "M", "𪊑"),
+ (0x2FA15, "M", "麻"),
+ (0x2FA16, "M", "䵖"),
+ (0x2FA17, "M", "黹"),
+ (0x2FA18, "M", "黾"),
+ (0x2FA19, "M", "鼅"),
+ (0x2FA1A, "M", "鼏"),
+ (0x2FA1B, "M", "鼖"),
+ (0x2FA1C, "M", "鼻"),
+ (0x2FA1D, "M", "𪘀"),
+ (0x2FA1E, "X"),
+ (0x30000, "V"),
+ (0x3134B, "X"),
+ (0x31350, "V"),
+ (0x323B0, "X"),
+ (0xE0100, "I"),
+ (0xE01F0, "X"),
+ ]
+
+
+uts46data = tuple(
+ _seg_0()
+ + _seg_1()
+ + _seg_2()
+ + _seg_3()
+ + _seg_4()
+ + _seg_5()
+ + _seg_6()
+ + _seg_7()
+ + _seg_8()
+ + _seg_9()
+ + _seg_10()
+ + _seg_11()
+ + _seg_12()
+ + _seg_13()
+ + _seg_14()
+ + _seg_15()
+ + _seg_16()
+ + _seg_17()
+ + _seg_18()
+ + _seg_19()
+ + _seg_20()
+ + _seg_21()
+ + _seg_22()
+ + _seg_23()
+ + _seg_24()
+ + _seg_25()
+ + _seg_26()
+ + _seg_27()
+ + _seg_28()
+ + _seg_29()
+ + _seg_30()
+ + _seg_31()
+ + _seg_32()
+ + _seg_33()
+ + _seg_34()
+ + _seg_35()
+ + _seg_36()
+ + _seg_37()
+ + _seg_38()
+ + _seg_39()
+ + _seg_40()
+ + _seg_41()
+ + _seg_42()
+ + _seg_43()
+ + _seg_44()
+ + _seg_45()
+ + _seg_46()
+ + _seg_47()
+ + _seg_48()
+ + _seg_49()
+ + _seg_50()
+ + _seg_51()
+ + _seg_52()
+ + _seg_53()
+ + _seg_54()
+ + _seg_55()
+ + _seg_56()
+ + _seg_57()
+ + _seg_58()
+ + _seg_59()
+ + _seg_60()
+ + _seg_61()
+ + _seg_62()
+ + _seg_63()
+ + _seg_64()
+ + _seg_65()
+ + _seg_66()
+ + _seg_67()
+ + _seg_68()
+ + _seg_69()
+ + _seg_70()
+ + _seg_71()
+ + _seg_72()
+ + _seg_73()
+ + _seg_74()
+ + _seg_75()
+ + _seg_76()
+ + _seg_77()
+ + _seg_78()
+ + _seg_79()
+ + _seg_80()
+ + _seg_81()
+ + _seg_82()
+ + _seg_83()
+) # type: Tuple[Union[Tuple[int, str], Tuple[int, str, str]], ...]
diff --git a/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/_parse.cpython-312.pyc b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/_parse.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f6432860a597de7bbf8a25f50e4aa907e07cd8f1
Binary files /dev/null and b/lib/python3.12/site-packages/ray/_private/runtime_env/agent/thirdparty_files/yarl/__pycache__/_parse.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/__init__.py b/lib/python3.12/site-packages/ray/llm/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b78d3ba4e1464bbeb6b99b2eb3811a0c5d7b60ae
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..1a304e84ded93babf08de6fd5d90e4e66d4203e0
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/__init__.py
@@ -0,0 +1,13 @@
+from ray.llm._internal.batch.processor import (
+ HttpRequestProcessorConfig,
+ Processor,
+ ProcessorBuilder,
+ ProcessorConfig,
+)
+
+__all__ = [
+ "Processor",
+ "ProcessorConfig",
+ "ProcessorBuilder",
+ "HttpRequestProcessorConfig",
+]
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..62cd19ecfa7089130dade7b9330694201f2a9502
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/__pycache__/utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/__pycache__/utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4ce2d10b5c1c732fcb1b8cf9408d70a458e9dba0
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/__pycache__/utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..3257786be13189e80c402b5892ef9cc444e4e87e
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__pycache__/benchmark_processor.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__pycache__/benchmark_processor.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..26f09573950801fa1f7f8172d6ce051ccf14f0c3
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__pycache__/benchmark_processor.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__pycache__/dataset.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__pycache__/dataset.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..69ee25a3ba6a766144192765ae3007645547baa0
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/__pycache__/dataset.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/benchmark_processor.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/benchmark_processor.py
new file mode 100644
index 0000000000000000000000000000000000000000..e0eee68e976a38a09d7fa5214a6e10e02929dfee
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/benchmark_processor.py
@@ -0,0 +1,556 @@
+#!/usr/bin/env python
+"""
+Benchmark Ray Data LLM offline batch inference throughput.
+
+Sample usage:
+python ray.llm._internal.batch.benchmark.benchmark_processor --mode vllm_engine --batch-size 64 --concurrency 1 --num-prompts 10000 --model facebook/opt-1.3b
+ --tensor-parallel-size 2 --pipeline-parallel-size 2 --distributed-executor-backend ray
+"""
+
+import argparse
+import sys
+from dataclasses import dataclass
+from enum import Enum
+from time import perf_counter, sleep
+
+import ray
+from .dataset import ShareGPTDataset
+from ray import data, serve
+from ray.data.llm import (
+ ServeDeploymentProcessorConfig,
+ build_llm_processor,
+ vLLMEngineProcessorConfig,
+)
+from ray.serve.llm import (
+ LLMConfig,
+ ModelLoadingConfig,
+ build_llm_deployment,
+)
+from ray.serve.llm.openai_api_models import CompletionRequest
+
+
+class Mode(Enum):
+ """Processor to benchmark."""
+
+ VLLM_ENGINE = "vllm_engine"
+ SHARED_VLLM_ENGINE = "shared_vllm_engine"
+ SERVE_DEPLOYMENT = "serve_deployment"
+ SHARED_SERVE_DEPLOYMENT = "shared_serve_deployment"
+
+
+# Default sampling parameters -- ensure a fair comparison by omitting sampling-induced variance
+VLLM_SAMPLING_PARAMS = {
+ "temperature": 1.0,
+ "max_tokens": 100,
+ "top_p": 1.0,
+ "ignore_eos": True,
+}
+
+# Default vLLM engine kwargs
+VLLM_ENGINE_KWARGS = {
+ "enable_prefix_caching": True,
+ "enable_chunked_prefill": True,
+ "max_num_batched_tokens": 4096,
+}
+
+
+def build_vllm_engine_kwargs(**kwargs) -> dict:
+ """Build vLLM engine kwargs from command line arguments."""
+ engine_kwargs = VLLM_ENGINE_KWARGS.copy()
+ engine_kwargs.update({k: v for k, v in kwargs.items() if v is not None})
+ return engine_kwargs
+
+
+def _build_vllm_engine_config(
+ model: str,
+ batch_size: int,
+ concurrency: int,
+ pipeline_parallel_size: int = None,
+ tensor_parallel_size: int = None,
+ distributed_executor_backend: str = None,
+) -> vLLMEngineProcessorConfig:
+ """Helper to create vLLMEngineProcessorConfig."""
+ return vLLMEngineProcessorConfig(
+ model_source=model,
+ batch_size=batch_size,
+ concurrency=concurrency,
+ apply_chat_template=False,
+ tokenize=False,
+ detokenize=False,
+ engine_kwargs=build_vllm_engine_kwargs(
+ pipeline_parallel_size=pipeline_parallel_size,
+ tensor_parallel_size=tensor_parallel_size,
+ distributed_executor_backend=distributed_executor_backend,
+ ),
+ )
+
+
+def _build_serve_deployment_config(
+ batch_size: int,
+ concurrency: int,
+ deployment_name: str = None,
+ app_name: str = None,
+) -> ServeDeploymentProcessorConfig:
+ """Helper to create ServeDeploymentProcessorConfig."""
+ return ServeDeploymentProcessorConfig(
+ deployment_name=deployment_name,
+ app_name=app_name,
+ dtype_mapping={
+ "CompletionRequest": CompletionRequest,
+ },
+ batch_size=batch_size,
+ concurrency=concurrency,
+ )
+
+
+@dataclass(slots=True)
+class BenchmarkResult:
+ mode: Mode
+ batch_size: int
+ concurrency: int
+ samples: int
+ elapsed_s: float
+
+ @property
+ def throughput(self) -> float:
+ return self.samples / self.elapsed_s if self.elapsed_s else 0.0
+
+ def show(self) -> None:
+ print("\n" + "=" * 60)
+ print(f"BENCHMARK - {self.mode}")
+ print("=" * 60)
+ print(f"Samples : {self.samples}")
+ print(f"Batch size : {self.batch_size}")
+ print(f"Concurrency : {self.concurrency}")
+ print(f"Time (s) : {self.elapsed_s:.2f}")
+ print(f"Throughput : {self.throughput:.2f} req/s")
+ print("=" * 60)
+
+
+def build_single_vllm_engine_processor(
+ batch_size: int,
+ concurrency: int,
+ model: str,
+ sampling_params: dict = VLLM_SAMPLING_PARAMS,
+ pipeline_parallel_size: int = None,
+ tensor_parallel_size: int = None,
+ distributed_executor_backend: str = None,
+):
+ """Build vLLM engine processor for single-turn benchmark."""
+ config = _build_vllm_engine_config(
+ model,
+ batch_size,
+ concurrency,
+ pipeline_parallel_size,
+ tensor_parallel_size,
+ distributed_executor_backend,
+ )
+ return build_llm_processor(
+ config,
+ preprocess=lambda row: dict(
+ prompt=row["prompt"],
+ sampling_params=sampling_params,
+ ),
+ postprocess=lambda row: row,
+ )
+
+
+def build_shared_vllm_engine_processor(
+ batch_size: int,
+ concurrency: int,
+ model: str,
+ sampling_params: dict = VLLM_SAMPLING_PARAMS,
+ pipeline_parallel_size: int = None,
+ tensor_parallel_size: int = None,
+ distributed_executor_backend: str = None,
+):
+ """Build vLLM engine processor for multi-turn benchmark."""
+ config = _build_vllm_engine_config(
+ model,
+ batch_size,
+ concurrency,
+ pipeline_parallel_size,
+ tensor_parallel_size,
+ distributed_executor_backend,
+ )
+
+ processor1 = build_llm_processor(
+ config,
+ preprocess=lambda row: dict(
+ prompt=row["prompt"],
+ sampling_params=sampling_params,
+ ),
+ postprocess=lambda row: {
+ "prompt": row["generated_text"]
+ if str(row.get("generated_text", "")).strip()
+ else row["prompt"]
+ },
+ )
+
+ processor2 = build_llm_processor(
+ config,
+ preprocess=lambda row: dict(
+ prompt=row["prompt"],
+ sampling_params=sampling_params,
+ ),
+ postprocess=lambda row: row,
+ )
+
+ def multi_turn_processor(dataset):
+ return processor2(processor1(dataset))
+
+ return multi_turn_processor
+
+
+def setup_serve_deployment(model: str, concurrency: int) -> tuple[str, str]:
+ """Set up Ray Serve deployment for hosting the LLM model."""
+ deployment_name = "benchmark_deployment"
+ app_name = "benchmark_app"
+
+ llm_config = LLMConfig(
+ model_loading_config=ModelLoadingConfig(
+ model_id=model,
+ model_source=model,
+ ),
+ deployment_config=dict(
+ name=deployment_name,
+ # To fairly compare with vLLM engine processor, fix the number of replicas to the concurrency level
+ autoscaling_config=dict(
+ min_replicas=concurrency,
+ max_replicas=concurrency,
+ ),
+ ),
+ engine_kwargs=dict(
+ enable_prefix_caching=True,
+ enable_chunked_prefill=True,
+ max_num_batched_tokens=4096,
+ ),
+ )
+
+ override_serve_options = dict(name=deployment_name)
+ llm_app = build_llm_deployment(
+ llm_config, override_serve_options=override_serve_options
+ )
+ serve.run(llm_app, name=app_name)
+
+ print("Waiting for Serve deployment to be ready...")
+ max_wait_time = 120 # seconds
+ wait_time = 0
+ while not _is_app_ready(app_name) and wait_time < max_wait_time:
+ sleep(5)
+ wait_time += 5
+
+ if wait_time >= max_wait_time:
+ raise TimeoutError("Deployment failed to become ready within timeout")
+
+ print("Deployment is ready!")
+ return deployment_name, app_name
+
+
+def _is_app_ready(app_name: str) -> bool:
+ try:
+ serve_status = serve.status()
+
+ if app_name in serve_status.applications:
+ app_status = serve_status.applications[app_name]
+ if app_status.status == "RUNNING":
+ print(f"Application '{app_name}' is RUNNING.")
+ return True
+ else:
+ print(f"Application '{app_name}' status: {app_status.status}")
+ return False
+ else:
+ print(f"Application '{app_name}' not found in Serve status.")
+ return False
+ except Exception as e:
+ print(f"Error checking app status: {e}")
+ return False
+
+
+def build_single_serve_deployment_processor(
+ batch_size: int,
+ concurrency: int,
+ model: str,
+ sampling_params: dict = VLLM_SAMPLING_PARAMS,
+ deployment_name: str = None,
+ app_name: str = None,
+ **kwargs,
+):
+ """Build Serve deployment processor for single-turn benchmark."""
+ config = _build_serve_deployment_config(
+ batch_size,
+ concurrency,
+ deployment_name,
+ app_name,
+ )
+ return build_llm_processor(
+ config,
+ preprocess=lambda row: dict(
+ method="completions",
+ dtype="CompletionRequest",
+ request_kwargs=dict(
+ model=model,
+ prompt=row["prompt"],
+ **sampling_params,
+ ),
+ ),
+ postprocess=lambda row: row,
+ )
+
+
+def build_shared_serve_deployment_processor(
+ batch_size: int,
+ concurrency: int,
+ model: str,
+ sampling_params: dict = VLLM_SAMPLING_PARAMS,
+ deployment_name: str = None,
+ app_name: str = None,
+ **kwargs,
+):
+ """Build Serve deployment processor for multi-turn benchmark."""
+ config = _build_serve_deployment_config(
+ batch_size,
+ concurrency,
+ deployment_name,
+ app_name,
+ )
+
+ processor1 = build_llm_processor(
+ config,
+ preprocess=lambda row: dict(
+ method="completions",
+ dtype="CompletionRequest",
+ request_kwargs=dict(
+ model=model,
+ prompt=row["prompt"],
+ stream=False,
+ ),
+ ),
+ postprocess=lambda row: {
+ # Fall back to original prompt if generated text is empty
+ "prompt": (
+ row["choices"][0]["text"]
+ if row.get("choices") and str(row["choices"][0].get("text", "")).strip()
+ else row["prompt"]
+ )
+ },
+ )
+
+ processor2 = build_llm_processor(
+ config,
+ preprocess=lambda row: dict(
+ method="completions",
+ dtype="CompletionRequest",
+ request_kwargs=dict(
+ model=model,
+ prompt=row["prompt"],
+ stream=False,
+ ),
+ ),
+ postprocess=lambda row: row,
+ )
+
+ def multi_turn_processor(dataset):
+ return processor2(processor1(dataset))
+
+ return multi_turn_processor
+
+
+# -----------------------------------------------------------------------------
+# Benchmark execution
+# -----------------------------------------------------------------------------
+def run_processor(
+ mode: Mode,
+ dataset: data.Dataset,
+ builder,
+ **kwargs,
+) -> BenchmarkResult:
+ processor = builder(**kwargs)
+
+ total_samples = dataset.count()
+
+ start = perf_counter()
+ processor(dataset).materialize()
+ elapsed = perf_counter() - start
+
+ return BenchmarkResult(
+ mode=mode,
+ batch_size=kwargs.get("batch_size"),
+ concurrency=kwargs.get("concurrency"),
+ samples=total_samples,
+ elapsed_s=elapsed,
+ )
+
+
+def benchmark(
+ mode: Mode,
+ dataset: data.Dataset,
+ *,
+ batch_size: int,
+ concurrency: int,
+ model: str,
+ sampling_params: dict = VLLM_SAMPLING_PARAMS,
+ pipeline_parallel_size: int = None,
+ tensor_parallel_size: int = None,
+ distributed_executor_backend: str = None,
+) -> BenchmarkResult:
+ mode_to_builder = {
+ Mode.VLLM_ENGINE: build_single_vllm_engine_processor,
+ Mode.SHARED_VLLM_ENGINE: build_shared_vllm_engine_processor,
+ Mode.SERVE_DEPLOYMENT: build_single_serve_deployment_processor,
+ Mode.SHARED_SERVE_DEPLOYMENT: build_shared_serve_deployment_processor,
+ }
+
+ if mode not in mode_to_builder:
+ raise ValueError(f"Unknown benchmark mode: {mode}")
+
+ builder = mode_to_builder[mode]
+
+ if mode in [Mode.SERVE_DEPLOYMENT, Mode.SHARED_SERVE_DEPLOYMENT]:
+ deployment_name, app_name = setup_serve_deployment(model, concurrency)
+ try:
+ return run_processor(
+ mode,
+ dataset,
+ builder,
+ batch_size=batch_size,
+ concurrency=concurrency,
+ model=model,
+ sampling_params=sampling_params,
+ deployment_name=deployment_name,
+ app_name=app_name,
+ )
+ finally:
+ serve.delete(app_name)
+ else:
+ return run_processor(
+ mode,
+ dataset,
+ builder,
+ batch_size=batch_size,
+ concurrency=concurrency,
+ model=model,
+ sampling_params=sampling_params,
+ pipeline_parallel_size=pipeline_parallel_size,
+ tensor_parallel_size=tensor_parallel_size,
+ distributed_executor_backend=distributed_executor_backend,
+ )
+
+
+# -----------------------------------------------------------------------------
+# CLI
+# -----------------------------------------------------------------------------
+def parse_args(argv: list[str]) -> argparse.Namespace:
+ parser = argparse.ArgumentParser(description="vLLM throughput benchmark")
+ parser.add_argument(
+ "--mode",
+ choices=[mode.value for mode in Mode],
+ default=Mode.VLLM_ENGINE.value,
+ help="Ray Data LLM processor to run benchmarks for",
+ )
+ # Dataset configuration
+ parser.add_argument(
+ "--dataset-path",
+ type=str,
+ default="/home/ubuntu/datasets/Code-feedback-sharegpt-renamed",
+ help="Path to dataset on disk",
+ )
+ parser.add_argument(
+ "--num-prompts", type=int, default=1000, help="Number of prompts to process"
+ )
+ parser.add_argument(
+ "--hf-dataset-id",
+ type=str,
+ default="Crystalcareai/Code-feedback-sharegpt-renamed",
+ help="Hugging Face dataset ID to download",
+ )
+ parser.add_argument(
+ "--hf-split",
+ type=str,
+ default="train",
+ help="Hugging Face dataset split to load",
+ )
+ parser.add_argument(
+ "--seed",
+ type=int,
+ default=0,
+ help="Random seed for dataset sampling",
+ )
+ parser.add_argument(
+ "--truncate-prompt",
+ type=int,
+ default=2048,
+ help="Maximum prompt length",
+ )
+ # Engine configuration
+ parser.add_argument(
+ "--model",
+ type=str,
+ required=True,
+ help="LLM model to use",
+ )
+ parser.add_argument(
+ "--pipeline-parallel-size",
+ type=int,
+ default=1,
+ help="Pipeline parallel size for vLLM engine",
+ )
+ parser.add_argument(
+ "--tensor-parallel-size",
+ type=int,
+ default=1,
+ help="Tensor parallel size for vLLM engine",
+ )
+ parser.add_argument(
+ "--distributed-executor-backend",
+ type=str,
+ default="mp",
+ choices=["ray", "mp"],
+ help="Distributed executor backend for vLLM engine",
+ )
+ # Ray Data worker configuration
+ parser.add_argument(
+ "--batch-size",
+ type=int,
+ required=True,
+ help="Ray Data batch size for processing",
+ )
+ parser.add_argument(
+ "--concurrency", type=int, required=True, help="Ray Data concurrency level"
+ )
+ return parser.parse_args(argv)
+
+
+def main() -> None:
+ args = parse_args(sys.argv[1:])
+
+ ray.init()
+ try:
+ dataset = ShareGPTDataset(
+ dataset_path=args.dataset_path,
+ seed=args.seed,
+ hf_dataset_id=args.hf_dataset_id,
+ hf_split=args.hf_split,
+ truncate_prompt=args.truncate_prompt,
+ )
+ prompts = dataset.sample(args.num_prompts)
+
+ dataset = data.from_items(prompts)
+ result = benchmark(
+ Mode(args.mode),
+ dataset,
+ batch_size=args.batch_size,
+ concurrency=args.concurrency,
+ model=args.model,
+ sampling_params=VLLM_SAMPLING_PARAMS,
+ pipeline_parallel_size=args.pipeline_parallel_size,
+ tensor_parallel_size=args.tensor_parallel_size,
+ distributed_executor_backend=args.distributed_executor_backend,
+ )
+ result.show()
+ finally:
+ ray.shutdown()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/dataset.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/dataset.py
new file mode 100644
index 0000000000000000000000000000000000000000..d58027cffefc0d565aa8d66d6d6a801454d2d26d
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/benchmark/dataset.py
@@ -0,0 +1,169 @@
+"""
+This module defines a dataset framework for sampling benchmark requests.
+"""
+
+from abc import ABC, abstractmethod
+from pathlib import Path
+from typing import Dict, List, Optional
+
+from datasets import load_dataset, load_from_disk
+
+
+class BenchmarkDataset(ABC):
+ DEFAULT_RANDOM_SEED = 0
+
+ def __init__(
+ self,
+ dataset_path: Optional[str] = None,
+ random_seed: int = DEFAULT_RANDOM_SEED,
+ ) -> None:
+ """
+ Abstract base class for benchmark datasets.
+
+ All benchmark datasets should inherit from this class and implement
+ the required abstract methods.
+
+ Args:
+ dataset_path: The path to the dataset on disk.
+ random_seed: The seed for the random number generator.
+ """
+ self._dataset_path = dataset_path
+ self._random_seed = random_seed
+
+ @abstractmethod
+ def load_data(self) -> None:
+ """
+ Load data from the dataset source into memory.
+
+ Raises:
+ NotImplementedError: If the method is not implemented in subclasses.
+ """
+ raise NotImplementedError("load_data must be implemented in subclasses.")
+
+ @abstractmethod
+ def sample(self, num_requests: int) -> List[Dict]:
+ """
+ Sample prompts from the loaded dataset.
+
+ Args:
+ num_requests: The number of prompts to sample from the dataset.
+
+ Returns:
+ A list of sampled request dictionaries.
+
+ Raises:
+ NotImplementedError: If the method is not implemented in subclasses.
+ """
+ raise NotImplementedError("sample must be implemented in subclasses.")
+
+
+class ShareGPTDataset(BenchmarkDataset):
+ """Implements the ShareGPT dataset. The first human message of each conversation is used to build a prompt."""
+
+ def __init__(
+ self,
+ dataset_path: str,
+ seed: int,
+ hf_dataset_id: str = "Crystalcareai/Code-feedback-sharegpt-renamed",
+ hf_split: str = "train",
+ truncate_prompt: Optional[int] = None,
+ ) -> None:
+ """
+ Initializes the ShareGPTDataset.
+
+ Args:
+ dataset_path: The path to the dataset on disk.
+ seed: The seed for the random number generator.
+ hf_dataset_id: The Hugging Face dataset ID to download if the dataset is not found on disk.
+ hf_split: The Hugging Face split to load from the dataset.
+ truncate_prompt: Maximum prompt length so that the prompt fits in the model's context window.
+ """
+ super().__init__(dataset_path, seed)
+ self._seed = seed
+
+ self._hf_dataset_id = hf_dataset_id
+ self._hf_split = hf_split
+ self._truncate_prompt = truncate_prompt
+
+ self._data: list[Dict] | None = None
+
+ def load_data(self) -> None:
+ """Load data from the dataset path into memory."""
+ if self._data is None:
+ self._data = self._load_dataset_data()
+
+ def sample(self, num_requests: int) -> List[Dict]:
+ """Sample prompts from the loaded dataset."""
+ if self._data is None:
+ self.load_data()
+
+ prompts = []
+ for item in self._data:
+ if len(prompts) >= num_requests:
+ break
+
+ prompt_data = self._extract_prompt(item)
+ if prompt_data is not None:
+ prompts.append(prompt_data)
+
+ if not prompts:
+ raise ValueError("ShareGPT dataset yielded no usable prompts")
+ return prompts
+
+ def _load_dataset(self):
+ """Load dataset from disk or Hugging Face."""
+ path = Path(self._dataset_path)
+ print(f"Attempting to load dataset from {path}")
+ print(f"Dataset exists on disk: {path.exists()}")
+
+ try:
+ if path.exists():
+ dataset = load_from_disk(str(path))
+ else:
+ print(
+ f"Dataset not found on disk, downloading from Hugging Face: {self._hf_dataset_id}"
+ )
+
+ path.parent.mkdir(parents=True, exist_ok=True)
+ dataset = load_dataset(self._hf_dataset_id, split=self._hf_split)
+ dataset.save_to_disk(str(path))
+ return dataset
+
+ except Exception as e:
+ raise RuntimeError(f"Error loading ShareGPT dataset: {e}")
+
+ def _load_dataset_data(self) -> List[Dict]:
+ """Load and process dataset data into a list of dictionaries."""
+ ds = self._load_dataset().shuffle(seed=self._seed)
+ data = []
+
+ for i, row in enumerate(ds):
+ data.append(row)
+
+ print(f"Loaded {len(data)} samples from dataset")
+ return data
+
+ def _extract_prompt(self, item: Dict) -> Dict | None:
+ """
+ Extracts the first human message of a conversation or None.
+
+ The ShareGPT schema uses {"role": "human", "value": ...} for user
+ turns.
+ """
+ messages = item.get("messages") or item.get("conversations") or []
+ prompt = next(
+ (
+ str(msg.get("value", "")).strip()
+ for msg in messages
+ if msg.get("role") in {"human", "user"}
+ ),
+ None,
+ )
+
+ # Only return a valid prompt if it's not empty
+ if prompt and prompt.strip():
+ if self._truncate_prompt:
+ prompt = prompt[: self._truncate_prompt]
+ return {"prompt": prompt}
+
+ return None
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a6b16daae6757e4368cec4063decb3a92a90d923
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/__init__.py
@@ -0,0 +1,23 @@
+from ray.llm._internal.batch.observability.logging.setup import (
+ setup_logging,
+)
+from ray.llm._internal.common.observability.logging_utils import (
+ disable_datasets_logger,
+ disable_vllm_custom_ops_logger_on_cpu_nodes,
+)
+from ray.llm._internal.common.observability.telemetry_utils import Once
+
+_setup_observability_once = Once()
+
+
+def _setup_observability():
+ setup_logging()
+ disable_datasets_logger()
+ disable_vllm_custom_ops_logger_on_cpu_nodes()
+
+
+def setup_observability():
+ _setup_observability_once.do_once(_setup_observability)
+
+
+__all__ = ["setup_observability"]
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7e8eb69e3beaa20bc885d9bf05b58358735e3c2f
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..04cd4d26101f34f3d4dcd116a9d56c7ef945ddf7
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/__init__.py
@@ -0,0 +1,40 @@
+import logging
+from typing import Optional
+
+from ray._common.filters import CoreContextFilter
+
+
+def _setup_logger(logger_name: str):
+ """Setup logger given the logger name.
+
+ This function is idempotent and won't set up the same logger multiple times. It will
+ also skip the setup if Data logger is already setup and has handlers.
+
+ Args:
+ logger_name: logger name used to get the logger.
+ """
+ logger = logging.getLogger(logger_name)
+ llm_logger = logging.getLogger("ray.data")
+
+ # Skip setup if the logger already has handlers setup or if the parent (Data
+ # logger) has handlers.
+ if logger.handlers or llm_logger.handlers:
+ return
+
+ # Set up stream handler, which logs to console as plaintext.
+ stream_handler = logging.StreamHandler()
+ stream_handler.addFilter(CoreContextFilter())
+ logger.addHandler(stream_handler)
+ logger.setLevel(logging.INFO)
+ logger.propagate = False
+
+
+def get_logger(name: Optional[str] = None):
+ """Get a structured logger inherited from the Ray Data logger.
+
+ Loggers by default are logging to stdout, and are expected to be scraped by an
+ external process.
+ """
+ logger_name = f"ray.data.{name}"
+ _setup_logger(logger_name)
+ return logging.getLogger(logger_name)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1185f72d93f6d2cbe4bd5262f5640563c3e1f24c
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/__pycache__/setup.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/__pycache__/setup.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..453379522d02e8f2ca16176e7936c5ee5be69417
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/__pycache__/setup.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/setup.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..75edff6649399cd45349e113129f7b4fecf5c3df
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/logging/setup.py
@@ -0,0 +1,26 @@
+import logging
+
+from ray._common.filters import CoreContextFilter
+from ray._common.formatters import JSONFormatter
+
+
+def _configure_stdlib_logging():
+ """Configures stdlib root logger to make sure stdlib loggers (created as
+ `logging.getLogger(...)`) are using Ray's `JSONFormatter` with Core and Serve
+ context filters.
+ """
+
+ handler = logging.StreamHandler()
+ handler.addFilter(CoreContextFilter())
+ handler.setFormatter(JSONFormatter())
+
+ root_logger = logging.getLogger()
+ # NOTE: It's crucial we reset all the handlers of the root logger,
+ # to make sure that logs aren't emitted twice
+ root_logger.handlers = []
+ root_logger.addHandler(handler)
+ root_logger.setLevel(logging.INFO)
+
+
+def setup_logging():
+ _configure_stdlib_logging()
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..47d41bbb1f3028640eb1396749d34586266027a5
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/__pycache__/usage.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/__pycache__/usage.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..abe6ee2befdbaa004a7c9bd0ce5d23392937811b
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/__pycache__/usage.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/usage.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/usage.py
new file mode 100644
index 0000000000000000000000000000000000000000..32d72a87a3574a77bd4bb3032b275b20caf43af5
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/observability/usage_telemetry/usage.py
@@ -0,0 +1,131 @@
+from enum import Enum
+from typing import Callable, Dict, List, Tuple, Union
+
+import ray
+from ray._common.usage.usage_lib import record_extra_usage_tag
+from ray.llm._internal.batch.observability.logging import get_logger
+from ray.llm._internal.common.base_pydantic import BaseModelExtended
+
+LLM_BATCH_TELEMETRY_NAMESPACE = "llm_batch_telemetry"
+LLM_BATCH_TELEMETRY_ACTOR_NAME = "llm_batch_telemetry"
+
+logger = get_logger(__name__)
+
+
+class BatchModelTelemetry(BaseModelExtended):
+ processor_config_name: str = ""
+ model_architecture: str = ""
+ batch_size: int = 0
+ accelerator_type: str = ""
+ concurrency: Union[int, Tuple[int, int]] = 0
+ task_type: str = ""
+
+ # For the parallel size, 0 means not supported.
+ pipeline_parallel_size: int = 0
+ tensor_parallel_size: int = 0
+ data_parallel_size: int = 0
+
+
+class BatchTelemetryTags(str, Enum):
+ """Telemetry tags for RayLLM Batch."""
+
+ LLM_BATCH_PROCESSOR_CONFIG_NAME = "LLM_BATCH_PROCESSOR_CONFIG_NAME"
+ LLM_BATCH_MODEL_ARCHITECTURE = "LLM_BATCH_MODEL_ARCHITECTURE"
+ LLM_BATCH_SIZE = "LLM_BATCH_SIZE"
+ LLM_BATCH_ACCELERATOR_TYPE = "LLM_BATCH_ACCELERATOR_TYPE"
+ LLM_BATCH_CONCURRENCY = "LLM_BATCH_CONCURRENCY"
+ LLM_BATCH_TASK_TYPE = "LLM_BATCH_TASK_TYPE"
+ LLM_BATCH_PIPELINE_PARALLEL_SIZE = "LLM_BATCH_PIPELINE_PARALLEL_SIZE"
+ LLM_BATCH_TENSOR_PARALLEL_SIZE = "LLM_BATCH_TENSOR_PARALLEL_SIZE"
+ LLM_BATCH_DATA_PARALLEL_SIZE = "LLM_BATCH_DATA_PARALLEL_SIZE"
+
+
+@ray.remote(
+ name=LLM_BATCH_TELEMETRY_ACTOR_NAME,
+ namespace=LLM_BATCH_TELEMETRY_NAMESPACE,
+ num_cpus=0,
+ lifetime="detached",
+)
+class _TelemetryAgent:
+ """Named Actor to keep the state of all deployed models and record telemetry."""
+
+ def __init__(self):
+ self._tracking_telemetries: List[BatchModelTelemetry] = []
+ self._record_tag_func = record_extra_usage_tag
+
+ def _update_record_tag_func(self, record_tag_func: Callable) -> None:
+ self._record_tag_func = record_tag_func
+
+ def generate_report(self) -> Dict[str, str]:
+ return {
+ BatchTelemetryTags.LLM_BATCH_PROCESSOR_CONFIG_NAME: ",".join(
+ [t.processor_config_name for t in self._tracking_telemetries]
+ ),
+ BatchTelemetryTags.LLM_BATCH_MODEL_ARCHITECTURE: ",".join(
+ [t.model_architecture for t in self._tracking_telemetries]
+ ),
+ BatchTelemetryTags.LLM_BATCH_SIZE: ",".join(
+ [str(t.batch_size) for t in self._tracking_telemetries]
+ ),
+ BatchTelemetryTags.LLM_BATCH_ACCELERATOR_TYPE: ",".join(
+ [t.accelerator_type for t in self._tracking_telemetries]
+ ),
+ BatchTelemetryTags.LLM_BATCH_CONCURRENCY: ",".join(
+ [str(t.concurrency) for t in self._tracking_telemetries]
+ ),
+ BatchTelemetryTags.LLM_BATCH_TASK_TYPE: ",".join(
+ [t.task_type for t in self._tracking_telemetries]
+ ),
+ BatchTelemetryTags.LLM_BATCH_PIPELINE_PARALLEL_SIZE: ",".join(
+ [str(t.pipeline_parallel_size) for t in self._tracking_telemetries]
+ ),
+ BatchTelemetryTags.LLM_BATCH_TENSOR_PARALLEL_SIZE: ",".join(
+ [str(t.tensor_parallel_size) for t in self._tracking_telemetries]
+ ),
+ BatchTelemetryTags.LLM_BATCH_DATA_PARALLEL_SIZE: ",".join(
+ [str(t.data_parallel_size) for t in self._tracking_telemetries]
+ ),
+ }
+
+ def record(self, telemetry: BatchModelTelemetry) -> None:
+ """Append and record telemetries."""
+ from ray._common.usage.usage_lib import TagKey
+
+ self._tracking_telemetries.append(telemetry)
+ telemetry_dict = self.generate_report()
+ for key, value in telemetry_dict.items():
+ self._record_tag_func(TagKey.Value(key), value)
+
+
+class TelemetryAgent:
+ """Wrapper around the telemetry agent that calls the remote method until
+ push_telemetry_report is called."""
+
+ def __init__(self):
+ try:
+ self.remote_telemetry_agent = ray.get_actor(
+ LLM_BATCH_TELEMETRY_ACTOR_NAME, namespace=LLM_BATCH_TELEMETRY_NAMESPACE
+ )
+ except ValueError:
+ from ray._common.constants import HEAD_NODE_RESOURCE_NAME
+ from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy
+
+ self.remote_telemetry_agent = _TelemetryAgent.options(
+ # Ensure the actor is created on the head node.
+ resources={HEAD_NODE_RESOURCE_NAME: 0.001},
+ # Ensure the actor is not scheduled with the existing placement group.
+ scheduling_strategy=PlacementGroupSchedulingStrategy(
+ placement_group=None
+ ),
+ ).remote()
+
+ def _update_record_tag_func(self, record_tag_func: Callable):
+ self.remote_telemetry_agent._update_record_tag_func.remote(record_tag_func)
+
+ def push_telemetry_report(self, telemetry: BatchModelTelemetry):
+ ray.get(self.remote_telemetry_agent.record.remote(telemetry))
+
+
+def get_or_create_telemetry_agent() -> TelemetryAgent:
+ """Helper to get or create the telemetry agent."""
+ return TelemetryAgent()
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..fed7d021fe8e185933985870590889f18a5c786e
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__init__.py
@@ -0,0 +1,15 @@
+from .base import Processor, ProcessorBuilder, ProcessorConfig
+from .http_request_proc import HttpRequestProcessorConfig
+from .serve_deployment_proc import ServeDeploymentProcessorConfig
+from .sglang_engine_proc import SGLangEngineProcessorConfig
+from .vllm_engine_proc import vLLMEngineProcessorConfig
+
+__all__ = [
+ "ProcessorConfig",
+ "ProcessorBuilder",
+ "HttpRequestProcessorConfig",
+ "vLLMEngineProcessorConfig",
+ "SGLangEngineProcessorConfig",
+ "ServeDeploymentProcessorConfig",
+ "Processor",
+]
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..02ca7c765debd1a1d4012a305a55c3c16ed6c716
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/base.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/base.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..007706921621aa4ea42abf879fe821e5fe0c27f9
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/base.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/http_request_proc.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/http_request_proc.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..25d17e8b96140a18c1fc307f0114d59ec9071613
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/http_request_proc.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/serve_deployment_proc.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/serve_deployment_proc.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..de2614cfb036894b9b8c95254433f542fa6b2928
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/serve_deployment_proc.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/sglang_engine_proc.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/sglang_engine_proc.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..eca27593f72296d5ce7e12d87b139f6f62c1d660
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/sglang_engine_proc.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/vllm_engine_proc.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/vllm_engine_proc.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..129cc535257e54412eb6e39118998222cdbb118c
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/__pycache__/vllm_engine_proc.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/base.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..8babcf629b5b5c7d227b6ee34cb7e2c7b05a5966
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/base.py
@@ -0,0 +1,414 @@
+import logging
+from collections import OrderedDict
+from typing import Any, Callable, Dict, List, Optional, Tuple, Type, Union
+
+from pydantic import Field, field_validator
+
+import ray
+from ray.data import Dataset
+from ray.data.block import UserDefinedFunction
+from ray.llm._internal.batch.stages import (
+ StatefulStage,
+ wrap_postprocess,
+ wrap_preprocess,
+)
+from ray.llm._internal.common.base_pydantic import BaseModelExtended
+from ray.util.annotations import DeveloperAPI, PublicAPI
+
+logger = logging.getLogger(__name__)
+
+
+# Higher values here are better for prefetching and locality. It's ok for this to be
+# fairly high since streaming backpressure prevents us from overloading actors.
+DEFAULT_MAX_TASKS_IN_FLIGHT = 4
+
+
+class ProcessorConfig(BaseModelExtended):
+ """The processor configuration."""
+
+ batch_size: int = Field(
+ default=32,
+ description="Large batch sizes are likely to saturate the compute resources "
+ "and could achieve higher throughput. On the other hand, small batch sizes "
+ "are more fault-tolerant and could reduce bubbles in the data pipeline. "
+ "You can tune the batch size to balance the throughput and fault-tolerance "
+ "based on your use case. Defaults to 32.",
+ )
+ resources_per_bundle: Optional[Dict[str, float]] = Field(
+ default=None,
+ description="[DEPRECATED] This parameter is deprecated and will be removed in a future version. ",
+ deprecated=True,
+ )
+ accelerator_type: Optional[str] = Field(
+ default=None,
+ description="The accelerator type used by the LLM stage in a processor. "
+ "Default to None, meaning that only the CPU will be used.",
+ )
+ concurrency: Union[int, Tuple[int, int]] = Field(
+ default=1,
+ description="The number of workers for data parallelism. Default to 1. "
+ "If ``concurrency`` is a ``tuple`` ``(m, n)``, Ray creates an autoscaling "
+ "actor pool that scales between ``m`` and ``n`` workers (``1 <= m <= n``). "
+ "If ``concurrency`` is an ``int`` ``n``, Ray uses either a fixed pool of ``n`` "
+ "workers or an autoscaling pool from ``1`` to ``n`` workers, depending on "
+ "the processor and stage.",
+ )
+
+ experimental: Dict[str, Any] = Field(
+ default_factory=dict,
+ description="[Experimental] Experimental configurations."
+ "Supported keys:\n"
+ "`max_tasks_in_flight_per_actor`: The maximum number of tasks in flight per actor. Default to 4.",
+ )
+
+ @field_validator("concurrency")
+ def validate_concurrency(
+ cls, concurrency: Union[int, Tuple[int, int]]
+ ) -> Union[int, Tuple[int, int]]:
+ """Validate that `concurrency` is either:
+ - a positive int, or
+ - a 2-tuple `(min, max)` of positive ints with `min <= max`.
+ """
+
+ def require(condition: bool, message: str) -> None:
+ if not condition:
+ raise ValueError(message)
+
+ if isinstance(concurrency, int):
+ require(
+ concurrency > 0,
+ f"A positive integer for `concurrency` is expected! Got: `{concurrency}`.",
+ )
+ elif isinstance(concurrency, tuple):
+ require(
+ all(c > 0 for c in concurrency),
+ f"`concurrency` tuple items must be positive integers! Got: `{concurrency}`.",
+ )
+
+ min_concurrency, max_concurrency = concurrency
+ require(
+ min_concurrency <= max_concurrency,
+ f"min > max in the concurrency tuple `{concurrency}`!",
+ )
+ return concurrency
+
+ def get_concurrency(self, autoscaling_enabled: bool = True) -> Tuple[int, int]:
+ """Return a normalized `(min, max)` worker range from `self.concurrency`.
+
+ Behavior:
+ - If `concurrency` is an int `n`:
+ - `autoscaling_enabled` is True -> return `(1, n)` (autoscaling).
+ - `autoscaling_enabled` is False -> return `(n, n)` (fixed-size pool).
+ - If `concurrency` is a 2-tuple `(m, n)`, return it unchanged
+ (the `autoscaling_enabled` flag is ignored).
+
+ Args:
+ autoscaling_enabled: When False, treat an integer `concurrency` as fixed `(n, n)`;
+ otherwise treat it as a range `(1, n)`. Defaults to True.
+
+ Returns:
+ tuple[int, int]: The allowed worker range `(min, max)`.
+
+ Examples:
+ >>> self.concurrency = (2, 4)
+ >>> self.get_concurrency()
+ (2, 4)
+ >>> self.concurrency = 4
+ >>> self.get_concurrency()
+ (1, 4)
+ >>> self.get_concurrency(autoscaling_enabled=False)
+ (4, 4)
+ """
+ if isinstance(self.concurrency, int):
+ if autoscaling_enabled:
+ return 1, self.concurrency
+ else:
+ return self.concurrency, self.concurrency
+ return self.concurrency
+
+ class Config:
+ validate_assignment = True
+ arbitrary_types_allowed = True
+
+
+class OfflineProcessorConfig(ProcessorConfig):
+ """The processor configuration for offline processing."""
+
+ model_source: str = Field(
+ description="The model source to use for the offline processing.",
+ )
+ runtime_env: Optional[Dict[str, Any]] = Field(
+ default=None,
+ description="The runtime environment to use for the offline processing.",
+ )
+ max_pending_requests: Optional[int] = Field(
+ default=None,
+ description="The maximum number of pending requests. If not specified, "
+ "will use the default value from the backend engine.",
+ )
+ max_concurrent_batches: int = Field(
+ default=8,
+ description="The maximum number of concurrent batches in the engine. "
+ "This is to overlap the batch processing to avoid the tail latency of "
+ "each batch. The default value may not be optimal when the batch size "
+ "or the batch processing latency is too small, but it should be good "
+ "enough for batch size >= 32.",
+ )
+
+ # Processor stage configurations.
+ apply_chat_template: bool = Field(
+ default=True, description="Whether to apply chat template."
+ )
+ chat_template: Optional[str] = Field(
+ default=None,
+ description="The chat template to use. This is usually not needed if the "
+ "model checkpoint already contains the chat template.",
+ )
+ tokenize: bool = Field(
+ default=True,
+ description="Whether to tokenize the input before passing it to the "
+ "backend engine. If not, the backend engine will tokenize the prompt.",
+ )
+ detokenize: bool = Field(
+ default=True,
+ description="Whether to detokenize the output.",
+ )
+ has_image: bool = Field(
+ default=False,
+ description="Whether the input messages have images.",
+ )
+
+
+@PublicAPI(stability="alpha")
+class Processor:
+ """A processor is composed of a preprocess stage, followed by one or more
+ processing stages, and finally a postprocess stage. We use processor as a
+ paradigm for processing data using LLMs.
+
+ Args:
+ config: The processor config.
+ stages: List of processing stages.
+ preprocess: An optional lambda function that takes a row (dict) as input
+ and returns a preprocessed row (dict). The output row must contain the
+ required fields for the following processing stages.
+ postprocess: An optional lambda function that takes a row (dict) as input
+ and returns a postprocessed row (dict).
+ preprocess_map_kwargs: Optional kwargs to pass to Dataset.map() for the
+ preprocess stage (e.g., num_cpus, memory, concurrency).
+ postprocess_map_kwargs: Optional kwargs to pass to Dataset.map() for the
+ postprocess stage (e.g., num_cpus, memory, concurrency).
+ """
+
+ # The internal used data column name ("__data"). Your input
+ # dataset should not contain this column. If you want to use this column
+ # in your input dataset, you have to derive and customize Processor.
+ DATA_COLUMN: str = "__data"
+
+ def __init__(
+ self,
+ config: ProcessorConfig,
+ stages: List[StatefulStage],
+ preprocess: Optional[UserDefinedFunction] = None,
+ postprocess: Optional[UserDefinedFunction] = None,
+ preprocess_map_kwargs: Optional[Dict[str, Any]] = None,
+ postprocess_map_kwargs: Optional[Dict[str, Any]] = None,
+ ):
+ self.config = config
+ self.preprocess = None
+ self.postprocess = None
+ self.preprocess_map_kwargs = preprocess_map_kwargs or {}
+ self.postprocess_map_kwargs = postprocess_map_kwargs or {}
+ self.stages: OrderedDict[str, StatefulStage] = OrderedDict()
+
+ # FIXES: https://github.com/ray-project/ray/issues/53124
+ # TODO (Kourosh): Remove this once the issue is fixed
+ data_context = ray.data.DataContext.get_current()
+ data_context.wait_for_min_actors_s = 600
+ # TODO: Remove this when https://github.com/ray-project/ray/issues/53169
+ # is fixed.
+ data_context._enable_actor_pool_on_exit_hook = True
+
+ # NOTE (Kourosh): If pre/postprocess is not provided, use the identity function.
+ # Wrapping is required even if they are identity functions, b/c data_column
+ # gets inserted/removed via wrap_preprocess/wrap_postprocess.
+ preprocess = preprocess or (lambda row: row)
+ postprocess = postprocess or (lambda row: row)
+
+ self.preprocess = wrap_preprocess(
+ preprocess,
+ self.DATA_COLUMN,
+ )
+
+ self.postprocess = wrap_postprocess(
+ postprocess,
+ self.DATA_COLUMN,
+ )
+
+ for stage in stages:
+ self._append_stage(stage)
+
+ def __call__(self, dataset: Dataset) -> Dataset:
+ """Execute the processor:
+ preprocess -> stages -> postprocess.
+ Note that the dataset won't be materialized during the execution.
+
+ Args:
+ dataset: The input dataset.
+
+ Returns:
+ The output dataset.
+ """
+ if self.preprocess is not None:
+ dataset = dataset.map(self.preprocess, **self.preprocess_map_kwargs)
+
+ # Apply stages.
+ for stage in self.stages.values():
+ kwargs = stage.get_dataset_map_batches_kwargs(
+ batch_size=self.config.batch_size,
+ data_column=self.DATA_COLUMN,
+ )
+ dataset = dataset.map_batches(stage.fn, **kwargs)
+
+ if self.postprocess is not None:
+ dataset = dataset.map(self.postprocess, **self.postprocess_map_kwargs)
+ return dataset
+
+ def _append_stage(self, stage: StatefulStage) -> None:
+ """Append a stage before postprocess. The stage class name will be used as
+ the stage name. If there are multiple stages with the same type, a suffix
+ will be added to the stage name to avoid conflicts.
+
+ Args:
+ stage: The stage to append.
+ """
+ stage_name = type(stage).__name__
+
+ # When a processor has multiple stages with the same type,
+ # append a index suffix to the stage name to avoid conflicts.
+ if stage_name in self.stages:
+ num_same_type_stage = len([s for s in self.stages.values() if s is stage])
+ stage_name = f"{stage_name}_{num_same_type_stage + 1}"
+ self.stages[stage_name] = stage
+
+ def list_stage_names(self) -> List[str]:
+ """List the stage names of this processor in order. Preprocess and postprocess
+ are not included.
+
+ Returns:
+ A list of stage names.
+ """
+ return list(self.stages.keys())
+
+ def get_stage_by_name(self, name: str) -> StatefulStage:
+ """Get a particular stage by its name. If the stage is not found,
+ a ValueError will be raised.
+
+ Args:
+ name: The stage name.
+
+ Returns:
+ The pipeline stage.
+ """
+ if name in self.stages:
+ return self.stages[name]
+ raise ValueError(f"Stage {name} not found")
+
+ def log_input_column_names(self):
+ """Log.info the input stage and column names of this processor.
+ If the input dataset does not contain these columns, you have to
+ provide a preprocess function to bridge the gap.
+ """
+ name, stage = list(self.stages.items())[0]
+ expected_input_keys = stage.get_required_input_keys()
+ optional_input_keys = stage.get_optional_input_keys()
+
+ message = f"The first stage of the processor is {name}."
+ if expected_input_keys:
+ message += "\nRequired input columns:\n"
+ message += "\n".join(f"\t{k}: {v}" for k, v in expected_input_keys.items())
+ if optional_input_keys:
+ message += "\nOptional input columns:\n"
+ message += "\n".join(f"\t{k}: {v}" for k, v in optional_input_keys.items())
+
+ logger.info(message)
+
+
+@DeveloperAPI
+class ProcessorBuilder:
+ """Build a processor based on the configuration."""
+
+ _registry: Dict[str, Callable] = {}
+
+ @classmethod
+ def register(cls, config_type: Type[ProcessorConfig], builder: Callable) -> None:
+ """A decorator to associate a particular pipeline config
+ with its build function.
+ """
+ type_name = config_type.__name__
+ if type_name in cls._registry:
+ raise ValueError(f"Processor config type {type_name} already registered.")
+ cls._registry[type_name] = builder
+
+ @classmethod
+ def clear_registry(cls) -> None:
+ """Clear the processor builder registry."""
+ cls._registry.clear()
+
+ @classmethod
+ def validate_builder_kwargs(cls, builder_kwargs: Optional[Dict[str, Any]]) -> None:
+ """Validate builder kwargs for conflicts with reserved keys.
+
+ Args:
+ builder_kwargs: Optional additional kwargs to pass to the processor builder
+ function.
+
+ Raises:
+ ValueError: If builder_kwargs contains reserved keys that conflict with
+ explicit arguments.
+ """
+ if builder_kwargs is not None:
+ # Check for conflicts with explicitly passed arguments
+ reserved_keys = {
+ "preprocess",
+ "postprocess",
+ "preprocess_map_kwargs",
+ "postprocess_map_kwargs",
+ }
+ conflicting_keys = reserved_keys & builder_kwargs.keys()
+ if conflicting_keys:
+ raise ValueError(
+ f"builder_kwargs cannot contain {conflicting_keys} as these are "
+ "passed as explicit arguments to build_llm_processor. "
+ "Please pass these directly instead of in builder_kwargs."
+ )
+
+ @classmethod
+ def build(
+ cls,
+ config: ProcessorConfig,
+ override_stage_config_fn: Optional[Callable] = None,
+ **kwargs,
+ ) -> Processor:
+ """Build a processor.
+
+ Args:
+ config: The processor config.
+ override_stage_config_fn: Custom stages configurations.
+ **kwargs: Additional keyword arguments to pass through to the
+ registered builder function. The builder function must accept
+ these kwargs in its signature, otherwise a TypeError will be raised.
+
+ Returns:
+ The built processor.
+ """
+ type_name = type(config).__name__
+ if type_name not in cls._registry:
+ raise ValueError(
+ f"Processor config type {type_name} not registered. "
+ f"Available types: {cls._registry.keys()}"
+ )
+ processor = cls._registry[type_name](config, **kwargs)
+ if override_stage_config_fn is not None:
+ for name, stage in processor.stages.items():
+ override_stage_config_fn(name, stage)
+ return processor
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/http_request_proc.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/http_request_proc.py
new file mode 100644
index 0000000000000000000000000000000000000000..c48ccfb2cd068f63a1f7b04dcd8993401a0f0162
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/http_request_proc.py
@@ -0,0 +1,115 @@
+"""The HTTP request processor."""
+
+from typing import Any, Dict, Optional
+
+from pydantic import Field
+
+from ray.data.block import UserDefinedFunction
+from ray.llm._internal.batch.observability.usage_telemetry.usage import (
+ BatchModelTelemetry,
+ get_or_create_telemetry_agent,
+)
+from ray.llm._internal.batch.processor.base import (
+ Processor,
+ ProcessorBuilder,
+ ProcessorConfig,
+)
+from ray.llm._internal.batch.stages import HttpRequestStage
+
+
+class HttpRequestProcessorConfig(ProcessorConfig):
+ """The configuration for the HTTP request processor."""
+
+ batch_size: int = Field(
+ default=64,
+ description="The batch size.",
+ )
+ url: str = Field(
+ description="The URL to query.",
+ )
+ headers: Optional[Dict[str, Any]] = Field(
+ default=None,
+ description="The query header. Note that we will add "
+ "'Content-Type: application/json' to be the header for sure "
+ "because we only deal with requests body in JSON.",
+ )
+ qps: Optional[int] = Field(
+ default=None,
+ description="The maximum number of requests per second to avoid rate limit. "
+ "If None, the request will be sent sequentially.",
+ )
+ max_retries: int = Field(
+ default=0,
+ description="The maximum number of retries per request in the event of failures.",
+ )
+ base_retry_wait_time_in_s: float = Field(
+ default=1,
+ description="The base wait time for a retry during exponential backoff.",
+ )
+ # Since `session_factory` is a callable, we use type Any to avoid pydantic serialization issues
+ session_factory: Optional[Any] = Field(
+ default=None,
+ description="Optional session factory to be used for initializing a client session. Type: Callable[[], ClientSession]",
+ # exclude from JSON serialization since `session_factory` is a callable
+ exclude=True,
+ )
+
+
+def build_http_request_processor(
+ config: HttpRequestProcessorConfig,
+ preprocess: Optional[UserDefinedFunction] = None,
+ postprocess: Optional[UserDefinedFunction] = None,
+ preprocess_map_kwargs: Optional[Dict[str, Any]] = None,
+ postprocess_map_kwargs: Optional[Dict[str, Any]] = None,
+) -> Processor:
+ """Construct a Processor and configure stages.
+
+ Args:
+ config: The configuration for the processor.
+ preprocess: An optional lambda function that takes a row (dict) as input
+ and returns a preprocessed row (dict). The output row must contain the
+ required fields for the following processing stages.
+ postprocess: An optional lambda function that takes a row (dict) as input
+ and returns a postprocessed row (dict).
+ preprocess_map_kwargs: Optional kwargs to pass to Dataset.map() for the
+ preprocess stage (e.g., num_cpus, memory, concurrency).
+ postprocess_map_kwargs: Optional kwargs to pass to Dataset.map() for the
+ postprocess stage (e.g., num_cpus, memory, concurrency).
+
+ Returns:
+ The constructed processor.
+ """
+ stages = [
+ HttpRequestStage(
+ fn_constructor_kwargs=dict(
+ url=config.url,
+ additional_header=config.headers,
+ qps=config.qps,
+ max_retries=config.max_retries,
+ base_retry_wait_time_in_s=config.base_retry_wait_time_in_s,
+ session_factory=config.session_factory,
+ ),
+ map_batches_kwargs=dict(
+ concurrency=config.concurrency,
+ ),
+ )
+ ]
+ telemetry_agent = get_or_create_telemetry_agent()
+ telemetry_agent.push_telemetry_report(
+ BatchModelTelemetry(
+ processor_config_name=type(config).__name__,
+ concurrency=config.concurrency,
+ )
+ )
+ processor = Processor(
+ config,
+ stages,
+ preprocess=preprocess,
+ postprocess=postprocess,
+ preprocess_map_kwargs=preprocess_map_kwargs,
+ postprocess_map_kwargs=postprocess_map_kwargs,
+ )
+ return processor
+
+
+ProcessorBuilder.register(HttpRequestProcessorConfig, build_http_request_processor)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/serve_deployment_proc.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/serve_deployment_proc.py
new file mode 100644
index 0000000000000000000000000000000000000000..78ccd084f4d65eb48ce2a43c25abe7ec96e6262c
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/serve_deployment_proc.py
@@ -0,0 +1,85 @@
+"""The processor that runs serve deployment."""
+
+from typing import Any, Dict, Optional, Type
+
+from pydantic import Field
+
+from ray.data.block import UserDefinedFunction
+from ray.llm._internal.batch.processor.base import (
+ Processor,
+ ProcessorBuilder,
+ ProcessorConfig,
+)
+from ray.llm._internal.batch.stages import (
+ ServeDeploymentStage,
+)
+
+
+class ServeDeploymentProcessorConfig(ProcessorConfig):
+ """The configuration for the serve deployment processor."""
+
+ # Configurations used to build the serve deployment
+ deployment_name: str = Field(
+ description="The name of the serve deployment to use.",
+ )
+ app_name: str = Field(
+ description="The name of the serve application to use.",
+ default="default",
+ )
+ dtype_mapping: Dict[str, Type[Any]] = Field(
+ description="A dictionary mapping data type names to their corresponding request classes for the serve deployment.",
+ default=None,
+ )
+
+
+def build_serve_deployment_processor(
+ config: ServeDeploymentProcessorConfig,
+ preprocess: Optional[UserDefinedFunction] = None,
+ postprocess: Optional[UserDefinedFunction] = None,
+ preprocess_map_kwargs: Optional[Dict[str, Any]] = None,
+ postprocess_map_kwargs: Optional[Dict[str, Any]] = None,
+) -> Processor:
+ """Construct a processor that runs a serve deployment.
+
+ Args:
+ config: The configuration for the processor.
+ preprocess: An optional lambda function that takes a row (dict) as input
+ and returns a preprocessed row (dict). The output row must contain the
+ required fields for the following processing stages.
+ postprocess: An optional lambda function that takes a row (dict) as input
+ and returns a postprocessed row (dict).
+ preprocess_map_kwargs: Optional kwargs to pass to Dataset.map() for the
+ preprocess stage (e.g., num_cpus, memory, concurrency).
+ postprocess_map_kwargs: Optional kwargs to pass to Dataset.map() for the
+ postprocess stage (e.g., num_cpus, memory, concurrency).
+
+ Returns:
+ The constructed processor.
+ """
+ stages = [
+ ServeDeploymentStage(
+ fn_constructor_kwargs=dict(
+ deployment_name=config.deployment_name,
+ app_name=config.app_name,
+ dtype_mapping=config.dtype_mapping,
+ ),
+ map_batches_kwargs=dict(
+ concurrency=config.concurrency,
+ ),
+ )
+ ]
+ # TODO (Kourosh): Add telemetry for ServeDeploymentStage
+ processor = Processor(
+ config,
+ stages,
+ preprocess=preprocess,
+ postprocess=postprocess,
+ preprocess_map_kwargs=preprocess_map_kwargs,
+ postprocess_map_kwargs=postprocess_map_kwargs,
+ )
+ return processor
+
+
+ProcessorBuilder.register(
+ ServeDeploymentProcessorConfig, build_serve_deployment_processor
+)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/sglang_engine_proc.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/sglang_engine_proc.py
new file mode 100644
index 0000000000000000000000000000000000000000..c7b0aa46e1515bd78944be86f5067ea57170be68
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/sglang_engine_proc.py
@@ -0,0 +1,195 @@
+"""The SGLang engine processor."""
+
+from typing import Any, Dict, Optional
+
+import transformers
+from pydantic import Field, root_validator
+
+import ray
+from ray.data.block import UserDefinedFunction
+from ray.llm._internal.batch.observability.usage_telemetry.usage import (
+ BatchModelTelemetry,
+ TelemetryAgent,
+ get_or_create_telemetry_agent,
+)
+from ray.llm._internal.batch.processor.base import (
+ DEFAULT_MAX_TASKS_IN_FLIGHT,
+ OfflineProcessorConfig,
+ Processor,
+ ProcessorBuilder,
+)
+from ray.llm._internal.batch.stages import (
+ ChatTemplateStage,
+ DetokenizeStage,
+ SGLangEngineStage,
+ TokenizeStage,
+)
+from ray.llm._internal.batch.stages.sglang_engine_stage import SGLangTaskType
+from ray.llm._internal.common.observability.telemetry_utils import DEFAULT_GPU_TYPE
+
+DEFAULT_MODEL_ARCHITECTURE = "UNKNOWN_MODEL_ARCHITECTURE"
+
+
+class SGLangEngineProcessorConfig(OfflineProcessorConfig):
+ """The configuration for the SGLang engine processor."""
+
+ # SGLang stage configurations.
+ engine_kwargs: Dict[str, Any] = Field(
+ default_factory=dict,
+ description="The kwargs to pass to the SGLang engine. See "
+ "https://docs.sglang.ai/backend/server_arguments.html "
+ "for more details.",
+ )
+ task_type: SGLangTaskType = Field(
+ default=SGLangTaskType.GENERATE,
+ description="The task type to use. If not specified, will use "
+ "'generate' by default.",
+ )
+
+ @root_validator(pre=True)
+ def validate_task_type(cls, values):
+ task_type_str = values.get("task_type", "generate")
+ values["task_type"] = SGLangTaskType(task_type_str)
+ return values
+
+
+def build_sglang_engine_processor(
+ config: SGLangEngineProcessorConfig,
+ chat_template_kwargs: Optional[Dict[str, Any]] = None,
+ preprocess: Optional[UserDefinedFunction] = None,
+ postprocess: Optional[UserDefinedFunction] = None,
+ preprocess_map_kwargs: Optional[Dict[str, Any]] = None,
+ postprocess_map_kwargs: Optional[Dict[str, Any]] = None,
+ telemetry_agent: Optional[TelemetryAgent] = None,
+) -> Processor:
+ """Construct a Processor and configure stages.
+
+ Args:
+ config: The configuration for the processor.
+ chat_template_kwargs: The optional kwargs to pass to apply_chat_template.
+ preprocess: An optional lambda function that takes a row (dict) as input
+ and returns a preprocessed row (dict). The output row must contain the
+ required fields for the following processing stages.
+ postprocess: An optional lambda function that takes a row (dict) as input
+ and returns a postprocessed row (dict).
+ preprocess_map_kwargs: Optional kwargs to pass to Dataset.map() for the
+ preprocess stage (e.g., num_cpus, memory, concurrency).
+ postprocess_map_kwargs: Optional kwargs to pass to Dataset.map() for the
+ postprocess stage (e.g., num_cpus, memory, concurrency).
+ telemetry_agent: An optional telemetry agent for collecting usage telemetry.
+
+ Returns:
+ The constructed processor.
+ """
+ ray.init(runtime_env=config.runtime_env, ignore_reinit_error=True)
+
+ stages = []
+
+ if config.apply_chat_template:
+ stages.append(
+ ChatTemplateStage(
+ fn_constructor_kwargs=dict(
+ model=config.model_source,
+ chat_template=config.chat_template,
+ chat_template_kwargs=chat_template_kwargs,
+ ),
+ map_batches_kwargs=dict(
+ zero_copy_batch=True,
+ concurrency=config.get_concurrency(),
+ batch_size=config.batch_size,
+ runtime_env=config.runtime_env,
+ ),
+ )
+ )
+
+ if config.tokenize:
+ stages.append(
+ TokenizeStage(
+ fn_constructor_kwargs=dict(
+ model=config.model_source,
+ ),
+ map_batches_kwargs=dict(
+ zero_copy_batch=True,
+ concurrency=config.get_concurrency(),
+ batch_size=config.batch_size,
+ runtime_env=config.runtime_env,
+ ),
+ )
+ )
+
+ # Core stage -- the SGLang engine.
+ stages.append(
+ SGLangEngineStage(
+ fn_constructor_kwargs=dict(
+ model=config.model_source,
+ engine_kwargs=config.engine_kwargs,
+ task_type=config.task_type,
+ max_pending_requests=config.max_pending_requests,
+ ),
+ map_batches_kwargs=dict(
+ zero_copy_batch=True,
+ # The number of running replicas. This is a deprecated field, but
+ # we need to set `max_tasks_in_flight_per_actor` through `compute`,
+ # which initiates enough many overlapping UDF calls per actor, to
+ # saturate `max_concurrency`.
+ compute=ray.data.ActorPoolStrategy(
+ min_size=config.get_concurrency(autoscaling_enabled=False)[0],
+ max_size=config.get_concurrency(autoscaling_enabled=False)[1],
+ max_tasks_in_flight_per_actor=config.experimental.get(
+ "max_tasks_in_flight_per_actor", DEFAULT_MAX_TASKS_IN_FLIGHT
+ ),
+ ),
+ # The number of running batches "per actor" in Ray Core level.
+ # This is used to make sure we overlap batches to avoid the tail
+ # latency of each batch.
+ max_concurrency=config.max_concurrent_batches,
+ accelerator_type=config.accelerator_type,
+ runtime_env=config.runtime_env,
+ ),
+ )
+ )
+
+ if config.detokenize:
+ stages.append(
+ DetokenizeStage(
+ fn_constructor_kwargs=dict(
+ model=config.model_source,
+ ),
+ map_batches_kwargs=dict(
+ zero_copy_batch=True,
+ concurrency=config.get_concurrency(),
+ batch_size=config.batch_size,
+ runtime_env=config.runtime_env,
+ ),
+ )
+ )
+
+ hf_config = transformers.AutoConfig.from_pretrained(config.model_source)
+ architecture = getattr(hf_config, "architectures", [DEFAULT_MODEL_ARCHITECTURE])[0]
+
+ telemetry_agent = get_or_create_telemetry_agent()
+ telemetry_agent.push_telemetry_report(
+ BatchModelTelemetry(
+ processor_config_name=type(config).__name__,
+ model_architecture=architecture,
+ batch_size=config.batch_size,
+ accelerator_type=config.accelerator_type or DEFAULT_GPU_TYPE,
+ concurrency=config.concurrency,
+ task_type=SGLangTaskType(config.task_type),
+ tensor_parallel_size=config.engine_kwargs.get("tp_size", 1),
+ data_parallel_size=config.engine_kwargs.get("dp_size", 1),
+ )
+ )
+
+ processor = Processor(
+ config,
+ stages,
+ preprocess=preprocess,
+ postprocess=postprocess,
+ preprocess_map_kwargs=preprocess_map_kwargs,
+ postprocess_map_kwargs=postprocess_map_kwargs,
+ )
+ return processor
+
+
+ProcessorBuilder.register(SGLangEngineProcessorConfig, build_sglang_engine_processor)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/vllm_engine_proc.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/vllm_engine_proc.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4316f7cf98b999ea9bba282c9bdc7064197feb5
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/processor/vllm_engine_proc.py
@@ -0,0 +1,277 @@
+"""The vLLM engine processor."""
+
+from typing import Any, Dict, List, Literal, Optional
+
+import transformers
+from pydantic import ConfigDict, Field, root_validator
+
+import ray
+from ray.data.block import UserDefinedFunction
+from ray.llm._internal.batch.observability.usage_telemetry.usage import (
+ BatchModelTelemetry,
+ TelemetryAgent,
+ get_or_create_telemetry_agent,
+)
+from ray.llm._internal.batch.processor.base import (
+ DEFAULT_MAX_TASKS_IN_FLIGHT,
+ OfflineProcessorConfig,
+ Processor,
+ ProcessorBuilder,
+)
+from ray.llm._internal.batch.stages import (
+ ChatTemplateStage,
+ DetokenizeStage,
+ PrepareImageStage,
+ TokenizeStage,
+ vLLMEngineStage,
+)
+from ray.llm._internal.batch.stages.vllm_engine_stage import vLLMTaskType
+from ray.llm._internal.common.base_pydantic import BaseModelExtended
+from ray.llm._internal.common.observability.telemetry_utils import DEFAULT_GPU_TYPE
+from ray.llm._internal.common.utils.download_utils import (
+ STREAMING_LOAD_FORMATS,
+ NodeModelDownloadable,
+ download_model_files,
+)
+
+DEFAULT_MODEL_ARCHITECTURE = "UNKNOWN_MODEL_ARCHITECTURE"
+
+
+class BundleSchema(BaseModelExtended):
+ model_config = ConfigDict(extra="allow")
+ CPU: Optional[int] = Field(default=1, description="The number of CPUs per bundle.")
+ GPU: Optional[int] = Field(default=1, description="The number of GPUs per bundle.")
+
+
+class PlacementGroupSchema(BaseModelExtended):
+ bundles: List[BundleSchema] = Field(
+ default_factory=list, description="The bundles for the placement group."
+ )
+ strategy: Literal["PACK", "STRICT_PACK", "SPREAD", "STRICT_SPREAD"] = Field(
+ default="PACK", description="The strategy for the placement group."
+ )
+
+
+class vLLMEngineProcessorConfig(OfflineProcessorConfig):
+ """The configuration for the vLLM engine processor."""
+
+ # vLLM stage configurations.
+ engine_kwargs: Dict[str, Any] = Field(
+ default_factory=dict,
+ description="The kwargs to pass to the vLLM engine. See "
+ "https://docs.vllm.ai/en/latest/serving/engine_args.html "
+ "for more details.",
+ )
+ task_type: vLLMTaskType = Field(
+ default=vLLMTaskType.GENERATE,
+ description="The task type to use. If not specified, will use "
+ "'generate' by default.",
+ )
+ # LoRA configurations.
+ dynamic_lora_loading_path: Optional[str] = Field(
+ default=None,
+ description="The path to the dynamic LoRA adapter. It is expected "
+ "to hold subfolders each for a different lora checkpoint. If not "
+ "specified and LoRA is enabled, then the 'model' in LoRA "
+ "requests will be interpreted as model ID used by HF transformers.",
+ )
+ # Custom placement group config for TP/PP.
+ placement_group_config: Optional[Dict[str, Any]] = Field(
+ default=None,
+ description="Ray placement group configuration for scheduling vLLM engine workers. "
+ "Should be a dictionary with 'bundles' (list of resource dicts, e.g., {'CPU': 1, 'GPU': 1}) "
+ "and an optional 'strategy' key ('PACK', 'STRICT_PACK', 'SPREAD', or 'STRICT_SPREAD'). "
+ "For ray distributed executor backend, each bundle must specify at most one GPU. "
+ "For mp backend, the 'strategy' field is ignored.",
+ )
+
+ @root_validator(pre=True)
+ def validate_task_type(cls, values):
+ task_type_str = values.get("task_type", "generate")
+ values["task_type"] = vLLMTaskType(task_type_str)
+ return values
+
+ @root_validator(pre=True)
+ def validate_placement_group_config(cls, values):
+ placement_group_config = values.get("placement_group_config")
+ if placement_group_config is not None:
+ values["placement_group_config"] = PlacementGroupSchema(
+ **placement_group_config
+ ).model_dump()
+ return values
+
+
+def build_vllm_engine_processor(
+ config: vLLMEngineProcessorConfig,
+ chat_template_kwargs: Optional[Dict[str, Any]] = None,
+ preprocess: Optional[UserDefinedFunction] = None,
+ postprocess: Optional[UserDefinedFunction] = None,
+ preprocess_map_kwargs: Optional[Dict[str, Any]] = None,
+ postprocess_map_kwargs: Optional[Dict[str, Any]] = None,
+ telemetry_agent: Optional[TelemetryAgent] = None,
+) -> Processor:
+ """Construct a Processor and configure stages.
+
+ Args:
+ config: The configuration for the processor.
+ chat_template_kwargs: The optional kwargs to pass to apply_chat_template.
+ preprocess: An optional lambda function that takes a row (dict) as input
+ and returns a preprocessed row (dict). The output row must contain the
+ required fields for the following processing stages.
+ postprocess: An optional lambda function that takes a row (dict) as input
+ and returns a postprocessed row (dict).
+ preprocess_map_kwargs: Optional kwargs to pass to Dataset.map() for the
+ preprocess stage (e.g., num_cpus, memory, concurrency).
+ postprocess_map_kwargs: Optional kwargs to pass to Dataset.map() for the
+ postprocess stage (e.g., num_cpus, memory, concurrency).
+ telemetry_agent: An optional telemetry agent for collecting usage telemetry.
+
+ Returns:
+ The constructed processor.
+ """
+ ray.init(runtime_env=config.runtime_env, ignore_reinit_error=True)
+
+ stages = []
+
+ if config.has_image:
+ stages.append(
+ PrepareImageStage(
+ map_batches_kwargs=dict(
+ zero_copy_batch=True,
+ concurrency=config.get_concurrency(),
+ batch_size=config.batch_size,
+ ),
+ )
+ )
+ if config.apply_chat_template:
+ stages.append(
+ ChatTemplateStage(
+ fn_constructor_kwargs=dict(
+ model=config.model_source,
+ chat_template=config.chat_template,
+ chat_template_kwargs=chat_template_kwargs,
+ ),
+ map_batches_kwargs=dict(
+ zero_copy_batch=True,
+ concurrency=config.get_concurrency(),
+ batch_size=config.batch_size,
+ runtime_env=config.runtime_env,
+ ),
+ )
+ )
+
+ if config.tokenize:
+ stages.append(
+ TokenizeStage(
+ fn_constructor_kwargs=dict(
+ model=config.model_source,
+ ),
+ map_batches_kwargs=dict(
+ zero_copy_batch=True,
+ concurrency=config.get_concurrency(),
+ batch_size=config.batch_size,
+ runtime_env=config.runtime_env,
+ ),
+ )
+ )
+
+ # Core stage -- the vLLM engine.
+
+ stages.append(
+ vLLMEngineStage(
+ fn_constructor_kwargs=dict(
+ batch_size=config.batch_size,
+ max_concurrent_batches=config.max_concurrent_batches,
+ model=config.model_source,
+ engine_kwargs=config.engine_kwargs,
+ task_type=config.task_type,
+ max_pending_requests=config.max_pending_requests,
+ dynamic_lora_loading_path=config.dynamic_lora_loading_path,
+ placement_group_config=config.placement_group_config,
+ ),
+ map_batches_kwargs=dict(
+ zero_copy_batch=True,
+ # The number of running replicas. This is a deprecated field, but
+ # we need to set `max_tasks_in_flight_per_actor` through `compute`,
+ # which initiates enough many overlapping UDF calls per actor, to
+ # saturate `max_concurrency`.
+ compute=ray.data.ActorPoolStrategy(
+ min_size=config.get_concurrency(autoscaling_enabled=False)[0],
+ max_size=config.get_concurrency(autoscaling_enabled=False)[1],
+ max_tasks_in_flight_per_actor=config.experimental.get(
+ "max_tasks_in_flight_per_actor", DEFAULT_MAX_TASKS_IN_FLIGHT
+ ),
+ ),
+ # The number of running batches "per actor" in Ray Core level.
+ # This is used to make sure we overlap batches to avoid the tail
+ # latency of each batch.
+ max_concurrency=config.max_concurrent_batches,
+ accelerator_type=config.accelerator_type,
+ runtime_env=config.runtime_env,
+ ),
+ )
+ )
+
+ if config.detokenize:
+ stages.append(
+ DetokenizeStage(
+ fn_constructor_kwargs=dict(
+ model=config.model_source,
+ ),
+ map_batches_kwargs=dict(
+ zero_copy_batch=True,
+ concurrency=config.get_concurrency(),
+ batch_size=config.batch_size,
+ runtime_env=config.runtime_env,
+ ),
+ )
+ )
+
+ # We download the config files here so that we can report the underlying architecture to the telemetry system.
+ # This should be a lightweight operation.
+ if config.engine_kwargs.get("load_format", None) in STREAMING_LOAD_FORMATS:
+ download_model_mode = NodeModelDownloadable.EXCLUDE_SAFETENSORS
+ else:
+ download_model_mode = NodeModelDownloadable.TOKENIZER_ONLY
+ model_path = download_model_files(
+ model_id=config.model_source,
+ mirror_config=None,
+ download_model=download_model_mode,
+ download_extra_files=False,
+ )
+ hf_config = transformers.AutoConfig.from_pretrained(
+ model_path,
+ trust_remote_code=config.engine_kwargs.get("trust_remote_code", False),
+ )
+
+ architectures = getattr(hf_config, "architectures", [])
+ architecture = architectures[0] if architectures else DEFAULT_MODEL_ARCHITECTURE
+
+ telemetry_agent = get_or_create_telemetry_agent()
+ telemetry_agent.push_telemetry_report(
+ BatchModelTelemetry(
+ processor_config_name=type(config).__name__,
+ model_architecture=architecture,
+ batch_size=config.batch_size,
+ accelerator_type=config.accelerator_type or DEFAULT_GPU_TYPE,
+ concurrency=config.concurrency,
+ task_type=vLLMTaskType(config.task_type),
+ pipeline_parallel_size=config.engine_kwargs.get(
+ "pipeline_parallel_size", 1
+ ),
+ tensor_parallel_size=config.engine_kwargs.get("tensor_parallel_size", 1),
+ )
+ )
+
+ processor = Processor(
+ config,
+ stages,
+ preprocess=preprocess,
+ postprocess=postprocess,
+ preprocess_map_kwargs=preprocess_map_kwargs,
+ postprocess_map_kwargs=postprocess_map_kwargs,
+ )
+ return processor
+
+
+ProcessorBuilder.register(vLLMEngineProcessorConfig, build_vllm_engine_processor)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..a45d21fc76707b77ed2b16377e3ff59c721fef5d
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__init__.py
@@ -0,0 +1,26 @@
+from ray.llm._internal.batch.stages.base import (
+ StatefulStage,
+ wrap_postprocess,
+ wrap_preprocess,
+)
+from ray.llm._internal.batch.stages.chat_template_stage import ChatTemplateStage
+from ray.llm._internal.batch.stages.http_request_stage import HttpRequestStage
+from ray.llm._internal.batch.stages.prepare_image_stage import PrepareImageStage
+from ray.llm._internal.batch.stages.serve_deployment_stage import ServeDeploymentStage
+from ray.llm._internal.batch.stages.sglang_engine_stage import SGLangEngineStage
+from ray.llm._internal.batch.stages.tokenize_stage import DetokenizeStage, TokenizeStage
+from ray.llm._internal.batch.stages.vllm_engine_stage import vLLMEngineStage
+
+__all__ = [
+ "StatefulStage",
+ "HttpRequestStage",
+ "ChatTemplateStage",
+ "TokenizeStage",
+ "DetokenizeStage",
+ "vLLMEngineStage",
+ "SGLangEngineStage",
+ "ServeDeploymentStage",
+ "wrap_preprocess",
+ "wrap_postprocess",
+ "PrepareImageStage",
+]
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..6c8f59d9ff0289a6e482270104198521f469a7e4
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/base.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/base.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..77c38387bcee964c3f5828e07788c31ea1e0652c
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/base.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/chat_template_stage.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/chat_template_stage.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..658c31ab13030362714fb74bc524847e1bfbd2f2
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/chat_template_stage.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/common.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/common.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..7a8a5523067fad6f7cba7292011d23b3b58091f0
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/common.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/http_request_stage.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/http_request_stage.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0879dc9ebc0d72dc84697d6431f47a8660339f69
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/http_request_stage.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/prepare_image_stage.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/prepare_image_stage.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..05d5244b7c6f851dbbd6ff06cd407abeda31ee69
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/prepare_image_stage.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/serve_deployment_stage.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/serve_deployment_stage.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..db900a923c7b1935aafc2ed12d22bd59470b7a64
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/serve_deployment_stage.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/sglang_engine_stage.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/sglang_engine_stage.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f88d575758c4decb1392e1eaf1066541ce73f919
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/sglang_engine_stage.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/tokenize_stage.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/tokenize_stage.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..866eebc220fbb17946d8b758277d1a318676c3c8
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/tokenize_stage.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/vllm_engine_stage.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/vllm_engine_stage.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f76b506b21dbe8a2e98a1b4a4baa286eb53e6b28
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/__pycache__/vllm_engine_stage.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/base.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..8e57f173883892223a647a2200ef83bf73c52e90
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/base.py
@@ -0,0 +1,287 @@
+"""The base class for all stages."""
+import logging
+from typing import Any, AsyncIterator, Callable, Dict, List, Optional, Type
+
+import pyarrow
+from pydantic import BaseModel, Field
+
+from ray.data.block import UserDefinedFunction
+
+logger = logging.getLogger(__name__)
+
+
+def wrap_preprocess(
+ fn: UserDefinedFunction,
+ processor_data_column: str,
+) -> Callable:
+ """Wrap the preprocess function, so that the output schema of the
+ preprocess is normalized to {processor_data_column: fn(row), other input columns}.
+
+ Args:
+ fn: The function to be applied.
+ processor_data_column: The internal data column name of the processor.
+
+ Returns:
+ The wrapped function.
+ """
+
+ def _preprocess(row: dict[str, Any]) -> dict[str, Any]:
+ # First put everything into processor_data_column.
+ outputs = {processor_data_column: row}
+
+ # Then apply the preprocess function and add its outputs.
+ preprocess_output = fn(row)
+ outputs[processor_data_column].update(preprocess_output)
+ return outputs
+
+ return _preprocess
+
+
+def wrap_postprocess(
+ fn: UserDefinedFunction,
+ processor_data_column: str,
+) -> Callable:
+ """Wrap the postprocess function to remove the processor_data_column.
+ Note that we fully rely on users to determine which columns to carry over.
+
+ Args:
+ fn: The function to be applied.
+ processor_data_column: The internal data column name of the processor.
+
+ Returns:
+ The wrapped function.
+ """
+
+ def _postprocess(row: dict[str, Any]) -> dict[str, Any]:
+ if processor_data_column not in row:
+ raise ValueError(
+ f"[Internal] {processor_data_column} not found in row {row}"
+ )
+
+ return fn(row[processor_data_column])
+
+ return _postprocess
+
+
+class StatefulStageUDF:
+ """A stage UDF wrapper that processes the input and output columns
+ before and after the UDF.
+
+ Args:
+ data_column: The internal data column name of the processor. The
+ __call__ method takes the data column as the input of the UDF
+ method, and encapsulates the output of the UDF method into the data
+ column for the next stage.
+ expected_input_keys: The expected input keys of the stage.
+ """
+
+ # The internal column name for the index of the row in the batch.
+ # This is used to align the output of the UDF with the input batch.
+ IDX_IN_BATCH_COLUMN: str = "__idx_in_batch"
+
+ def __init__(
+ self, data_column: str, expected_input_keys: Optional[List[str]] = None
+ ):
+ self.data_column = data_column
+ self.expected_input_keys = set(expected_input_keys or [])
+
+ async def __call__(self, batch: Dict[str, Any]) -> AsyncIterator[Dict[str, Any]]:
+ """A stage UDF wrapper that processes the input and output columns
+ before and after the UDF. The expected schema of "batch" is:
+ {data_column: {
+ dataset columns,
+ other intermediate columns
+ },
+ ...other metadata columns...,
+ }.
+
+ The input of the UDF will then [dataset columns and other intermediate columns].
+ In addition, while the output of the UDF depends on the UDF implementation,
+ the output schema is expected to be
+ {data_column: {
+ dataset columns,
+ other intermediate columns,
+ UDF output columns (will override above columns if they have the same name)
+ },
+ ...other metadata columns...,
+ }.
+ And this will become the input of the next stage.
+
+ Examples:
+ Input dataset columns: {A, B, C}
+ Preprocess: (lambda row: {"D": row["A"] + 1})
+ Input:
+ UDF input: {A, B, C}
+ UDF output: {D}
+ Output: {__data: {A, B, C, D}}
+ Stage 1:
+ Input: {__data: {A, B, C, D}}
+ UDF input: {A, B, C, D}
+ UDF output: {E}
+ Output: {__data: {A, B, C, D, E}}
+ Stage 2:
+ Input: {__data: {A, B, C, D, E}}
+ UDF input: {A, B, C, D, E}
+ UDF output: {F, E} # E is in-place updated.
+ Output: {__data: {A, B, C, D, E, F}}
+ Postprocess: (lambda row: {"G": row["F"], "A": row["A"], "E": row["E"]})
+ Input: {__data: {A, B, C, D, E, F}}
+ UDF input: {A, B, C, D, E, F}
+ UDF output: {G, A, E}
+ Output: {G, A, E} # User chooses to keep G, A, E.
+
+ Args:
+ batch: The input batch.
+
+ Returns:
+ An async iterator of the outputs.
+ """
+ # Handle the case where the batch is empty.
+ # FIXME: This should not happen.
+ if isinstance(batch, pyarrow.lib.Table) and batch.num_rows == 0:
+ yield {}
+ return
+
+ if self.data_column not in batch:
+ raise ValueError(
+ f"[Internal] {self.data_column} not found in batch {batch}"
+ )
+
+ inputs = batch.pop(self.data_column)
+ if hasattr(inputs, "tolist"):
+ inputs = inputs.tolist()
+ self.validate_inputs(inputs)
+
+ # Assign the index of the row in the batch to the idx_in_batch_column.
+ # This is beacuse the UDF output may be out-of-order (if asyncio.as_completed
+ # is used interanlly for example), and we need to carry over unused input
+ # columns to the next stage. Thus, we use the row index in batch to match
+ # the output of the UDF with the input.
+ for idx, row in enumerate(inputs):
+ row[self.IDX_IN_BATCH_COLUMN] = idx
+
+ # Collect all outputs first, then return them in the original order
+ # This is a requirement set by https://github.com/ray-project/ray/pull/54190/
+ not_outputed_rows = set(range(len(inputs)))
+ async for output in self.udf(inputs):
+ if self.IDX_IN_BATCH_COLUMN not in output:
+ raise ValueError(
+ "The output of the UDF must contain the column "
+ f"{self.IDX_IN_BATCH_COLUMN}."
+ )
+ idx_in_batch = output.pop(self.IDX_IN_BATCH_COLUMN)
+ if idx_in_batch not in not_outputed_rows:
+ raise ValueError(
+ f"The row {idx_in_batch} is outputed twice. "
+ "This is likely due to the UDF is not one-to-one."
+ )
+ not_outputed_rows.remove(idx_in_batch)
+
+ # Add stage outputs to the data column of the row.
+ inputs[idx_in_batch].pop(self.IDX_IN_BATCH_COLUMN)
+ inputs[idx_in_batch].update(output)
+
+ if not_outputed_rows:
+ raise ValueError(f"The rows {not_outputed_rows} are not outputed.")
+
+ # Return all updated inputs in the original order
+ yield {self.data_column: inputs}
+
+ def validate_inputs(self, inputs: List[Dict[str, Any]]):
+ """Validate the inputs to make sure the required keys are present.
+
+ Args:
+ inputs: The inputs.
+
+ Raises:
+ ValueError: If the required keys are not found.
+ """
+ for inp in inputs:
+ input_keys = set(inp.keys())
+
+ if self.IDX_IN_BATCH_COLUMN in input_keys:
+ raise ValueError(
+ f"The input column {self.IDX_IN_BATCH_COLUMN} is reserved "
+ "for internal use."
+ )
+
+ if not self.expected_input_keys:
+ continue
+
+ missing_required = self.expected_input_keys - input_keys
+ if missing_required:
+ raise ValueError(
+ f"Required input keys {missing_required} not found at the input of "
+ f"{self.__class__.__name__}. Input keys: {input_keys}"
+ )
+
+ async def udf(self, rows: List[Dict[str, Any]]) -> AsyncIterator[Dict[str, Any]]:
+ raise NotImplementedError("StageUDF must implement the udf method")
+
+
+class StatefulStage(BaseModel):
+ """
+ A basic building block to compose a Processor.
+ """
+
+ fn: Type[StatefulStageUDF] = Field(
+ description="The well-optimized stateful UDF for this stage."
+ )
+ fn_constructor_kwargs: Dict[str, Any] = Field(
+ default_factory=dict,
+ description="The keyword arguments of the UDF constructor.",
+ )
+ map_batches_kwargs: Dict[str, Any] = Field(
+ default_factory=lambda: {"concurrency": 1},
+ description="The arguments of .map_batches(). Default {'concurrency': 1}.",
+ )
+
+ def get_required_input_keys(self) -> Dict[str, str]:
+ """The required input keys of the stage and their descriptions."""
+ return {}
+
+ def get_optional_input_keys(self) -> Dict[str, str]:
+ """The optional input keys of the stage and their descriptions."""
+ return {}
+
+ def get_dataset_map_batches_kwargs(
+ self,
+ batch_size: int,
+ data_column: str,
+ ) -> Dict[str, Any]:
+ """We separate fn and fn_constructor_kwargs in Stage for better UX,
+ so we combine them with other map_batches_kwargs together in this method.
+
+ Args:
+ batch_size: The batch size set by the processor config.
+ data_column: The data column name set by the processor.
+
+ Returns:
+ The dataset map_batches kwargs.
+ """
+ kwargs = self.map_batches_kwargs.copy()
+ batch_size_in_kwargs = kwargs.get("batch_size", batch_size)
+ if batch_size_in_kwargs != batch_size:
+ logger.warning(
+ "batch_size is set to %d in map_batches_kwargs, but it will be "
+ "overridden by the batch size configured by the processor %d.",
+ batch_size_in_kwargs,
+ batch_size,
+ )
+ kwargs["batch_size"] = batch_size
+
+ kwargs.update({"fn_constructor_kwargs": self.fn_constructor_kwargs.copy()})
+ if "data_column" in kwargs["fn_constructor_kwargs"]:
+ raise ValueError(
+ "'data_column' cannot be used as in fn_constructor_kwargs."
+ )
+
+ kwargs["fn_constructor_kwargs"]["data_column"] = data_column
+ kwargs["fn_constructor_kwargs"]["expected_input_keys"] = list(
+ self.get_required_input_keys().keys()
+ )
+ return kwargs
+
+ class Config:
+ arbitrary_types_allowed = True
+ validate_assignment = True
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/chat_template_stage.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/chat_template_stage.py
new file mode 100644
index 0000000000000000000000000000000000000000..92d453dbb10336708077d0f2956e90e34873e3a9
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/chat_template_stage.py
@@ -0,0 +1,130 @@
+"""Apply chat template stage"""
+
+from typing import TYPE_CHECKING, Any, AsyncIterator, Dict, List, Optional, Type, Union
+
+from ray.llm._internal.batch.stages.base import (
+ StatefulStage,
+ StatefulStageUDF,
+)
+from ray.llm._internal.common.utils.download_utils import (
+ NodeModelDownloadable,
+ download_model_files,
+)
+
+
+class ChatTemplateUDF(StatefulStageUDF):
+ def __init__(
+ self,
+ data_column: str,
+ expected_input_keys: List[str],
+ model: str,
+ chat_template: Optional[str] = None,
+ chat_template_kwargs: Optional[Dict[str, Any]] = None,
+ ):
+ """
+ Initialize the ChatTemplateUDF.
+
+ Args:
+ data_column: The data column name.
+ expected_input_keys: The expected input keys of the stage.
+ model: The model to use for the chat template.
+ chat_template: The chat template in Jinja template format. This is
+ usually not needed if the model checkpoint already contains the
+ chat template.
+ chat_template_kwargs: The optional kwargs to pass apply_chat_template.
+ """
+ from transformers import AutoProcessor
+
+ super().__init__(data_column, expected_input_keys)
+
+ # NOTE: We always use processor instead of tokenizer in this stage,
+ # because tokenizers of VLM models may not have chat template attribute.
+ # However, this may not be a reliable solution, because processors and
+ # tokenizers are not standardized across different models.
+ model_path = download_model_files(
+ model_id=model,
+ mirror_config=None,
+ download_model=NodeModelDownloadable.TOKENIZER_ONLY,
+ download_extra_files=False,
+ )
+ if TYPE_CHECKING:
+ from transformers.processing_utils import ProcessorMixin
+ from transformers.tokenization_utils_base import PreTrainedTokenizerBase
+
+ self.processor: Union[
+ "PreTrainedTokenizerBase", "ProcessorMixin"
+ ] = AutoProcessor.from_pretrained(model_path, trust_remote_code=True)
+ self.chat_template = chat_template
+ self.chat_template_kwargs = chat_template_kwargs
+
+ async def udf(self, batch: List[Dict[str, Any]]) -> AsyncIterator[Dict[str, Any]]:
+ """
+ Apply chat template to the given batch.
+
+ Args:
+ batch: A list of rows to send.
+
+ Yields:
+ A generator of rows with the chat template applied.
+ """
+ prompts = []
+ for row in batch:
+ # PyArrow cannot handle the messages with images, so Ray Data
+ # will fallback to use pickle for serialization. In this case,
+ # the "messages" column is already a list of dicts and does not
+ # have .tolist() method.
+ if hasattr(row["messages"], "tolist"):
+ conversation = row["messages"].tolist()
+ else:
+ conversation = row["messages"]
+ add_generation_prompt = self._should_add_generation_prompt(conversation)
+ # If we don't add a generation prompt, we should continue the final message.
+ continue_final_message = not add_generation_prompt
+ prompts.append(
+ self.processor.apply_chat_template(
+ conversation,
+ tokenize=False,
+ chat_template=self.chat_template,
+ add_generation_prompt=add_generation_prompt,
+ continue_final_message=continue_final_message,
+ **(self.chat_template_kwargs or {}),
+ )
+ )
+ assert len(batch) == len(prompts)
+
+ for row, prompt in zip(batch, prompts):
+ yield {
+ self.IDX_IN_BATCH_COLUMN: row[self.IDX_IN_BATCH_COLUMN],
+ "prompt": prompt,
+ }
+
+ def _should_add_generation_prompt(self, conversation: List[Dict[str, Any]]) -> bool:
+ """Determines if the generation prompt should be added for the given conversation.
+
+ Adds the generation prompt only if the last message is from the user.
+ This is useful in cases where the user provides an assistant prefill
+ message.
+
+ Args:
+ conversation: The conversation to check.
+
+ Returns:
+ True if the generation prompt should be added, False otherwise.
+ """
+ return conversation[-1]["role"] == "user"
+
+
+class ChatTemplateStage(StatefulStage):
+ """
+ A stage that applies chat template.
+ """
+
+ fn: Type[StatefulStageUDF] = ChatTemplateUDF
+
+ def get_required_input_keys(self) -> Dict[str, str]:
+ """The required input keys of the stage and their descriptions."""
+ return {
+ "messages": "A list of messages in OpenAI chat format. "
+ "See https://platform.openai.com/docs/api-reference/chat/create "
+ "for details."
+ }
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/common.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/common.py
new file mode 100644
index 0000000000000000000000000000000000000000..fb0869642991a93c1bba932ec831f1550886cf8c
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/common.py
@@ -0,0 +1,29 @@
+"""
+Shared utilities for stages.
+"""
+
+from typing import Any, Dict, List, Union
+
+import numpy as np
+
+
+def maybe_convert_ndarray_to_list(
+ params: Union[np.ndarray, List[Any], Dict[str, Any]]
+) -> Union[List[Any], Dict[str, Any]]:
+ """Convert all ndarray to list in the params. This is because Ray Data
+ by default converts all lists to ndarrays when passing data around, but
+ vLLM expects lists.
+
+ Args:
+ params: The parameters to convert.
+
+ Returns:
+ The converted parameters.
+ """
+ if isinstance(params, dict):
+ return {k: maybe_convert_ndarray_to_list(v) for k, v in params.items()}
+ elif isinstance(params, list):
+ return [maybe_convert_ndarray_to_list(v) for v in params]
+ elif isinstance(params, np.ndarray):
+ return params.tolist()
+ return params
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/http_request_stage.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/http_request_stage.py
new file mode 100644
index 0000000000000000000000000000000000000000..25240fbee89f33f38515342b8d91ceaacc29e1cc
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/http_request_stage.py
@@ -0,0 +1,168 @@
+"""HTTP Request Stage"""
+
+import asyncio
+import time
+import traceback
+from typing import Any, AsyncIterator, Callable, Dict, List, Optional, Type
+
+import aiohttp
+import aiohttp.web_exceptions
+import numpy as np
+from aiohttp.client_exceptions import ClientPayloadError
+
+from ray.llm._internal.batch.stages.base import StatefulStage, StatefulStageUDF
+
+
+class HttpRequestUDF(StatefulStageUDF):
+ RETRYABLE_STATUS_CODES = [429, 408, 504, 502, 503]
+
+ def __init__(
+ self,
+ data_column: str,
+ expected_input_keys: List[str],
+ url: str,
+ additional_header: Optional[Dict[str, Any]] = None,
+ qps: Optional[int] = None,
+ max_retries: int = 0,
+ base_retry_wait_time_in_s: float = 1.0,
+ session_factory: Optional[Callable[[], aiohttp.ClientSession]] = None,
+ ):
+ """
+ Initialize the HttpRequestUDF.
+
+ Args:
+ data_column: The data column name.
+ expected_input_keys: The expected input keys of the stage.
+ url: The URL to send the HTTP request to.
+ additional_header: The additional headers to send with the HTTP request.
+ qps: The maximum number of requests per second.
+ max_retries: The maximum number of retries per request in the event of failures. We retry with exponential backoff upto this specific maximum retries.
+ base_retry_wait_time_in_s: The base retry wait time during exponential backoff.
+ session_factory: Optional session factory to be used for initializing a client session.
+ """
+ super().__init__(data_column, expected_input_keys)
+ self.url = url
+ self.additional_header = additional_header or {}
+ self.qps = qps
+ self.max_retries = max_retries
+ self.base_retry_wait_time_in_s = base_retry_wait_time_in_s
+ self.session_factory = session_factory or aiohttp.ClientSession
+
+ async def udf(self, batch: List[Dict[str, Any]]) -> AsyncIterator[Dict[str, Any]]:
+ """
+ Send HTTP requests to the given URL.
+
+ Args:
+ batch: A list of rows to send.
+
+ Yields:
+ A generator of rows of the response of the HTTP request.
+ """
+ # preprocess to get request body for the given batch
+ request_bodies = [None] * len(batch)
+ for row in batch:
+ # Normalize the row to a JSON body.
+ json_body = {}
+ for key, value in row["payload"].items():
+ if isinstance(value, np.ndarray):
+ json_body[key] = value.tolist()
+ else:
+ json_body[key] = value
+ request_bodies[row[self.IDX_IN_BATCH_COLUMN]] = json_body
+
+ async with self.session_factory() as session:
+ start_time = time.time()
+ request_count = 0
+ pending_requests = []
+ headers = {
+ "Content-Type": "application/json",
+ **self.additional_header,
+ }
+
+ # First send all requests based on QPS
+ for row in batch:
+ # Rate limit based on qps if specified
+ if self.qps is not None:
+ request_count += 1
+ expected_time = request_count / self.qps
+ elapsed = time.time() - start_time
+ if elapsed < expected_time:
+ await asyncio.sleep(expected_time - elapsed)
+
+ # self.IDX_IN_BATCH_COLUMN is the index of row in the batch
+ json_body = request_bodies[row[self.IDX_IN_BATCH_COLUMN]]
+ # Create request but don't await it yet
+ request = session.post(
+ self.url,
+ headers=headers,
+ json=json_body,
+ )
+ pending_requests.append((row[self.IDX_IN_BATCH_COLUMN], request))
+
+ # Now receive all responses
+ for idx_in_batch_column, request in pending_requests:
+ resp_json = None
+ last_exception = None
+ last_exception_traceback = None
+ for retry_count in range(self.max_retries + 1):
+ if retry_count > 0:
+ json_body = request_bodies[idx_in_batch_column]
+ request = session.post(
+ self.url,
+ headers=headers,
+ json=json_body,
+ )
+ try:
+ async with await request as response:
+ status_code = response.status
+ # check status and see if it's retry worthy
+ if status_code in self.RETRYABLE_STATUS_CODES:
+ last_exception = aiohttp.web_exceptions.HTTPException(
+ reason=response.reason
+ )
+ last_exception.status_code = status_code
+ wait_time = self.base_retry_wait_time_in_s * (
+ 2**retry_count
+ )
+ await asyncio.sleep(wait_time)
+ continue
+ resp_json = await response.json()
+ if self.IDX_IN_BATCH_COLUMN in resp_json:
+ raise ValueError(
+ "The response of the HTTP request must not contain "
+ f"the column {self.IDX_IN_BATCH_COLUMN}."
+ )
+ break
+ except (
+ asyncio.TimeoutError,
+ aiohttp.ClientConnectionError,
+ ClientPayloadError,
+ ) as e:
+ last_exception_traceback = traceback.format_exc()
+ last_exception = type(e).__name__
+ wait_time = self.base_retry_wait_time_in_s * (2**retry_count)
+ await asyncio.sleep(wait_time)
+ continue
+ if not resp_json:
+ raise RuntimeError(
+ f"Reached maximum retries of {self.max_retries} for input row {batch[idx_in_batch_column]}. Previous Exception: {last_exception}. Full Traceback: \n{last_exception_traceback}"
+ )
+ yield {
+ self.IDX_IN_BATCH_COLUMN: idx_in_batch_column,
+ "http_response": resp_json,
+ }
+
+
+class HttpRequestStage(StatefulStage):
+ """
+ A stage that sends HTTP requests.
+ """
+
+ fn: Type[StatefulStageUDF] = HttpRequestUDF
+
+ def get_required_input_keys(self) -> Dict[str, str]:
+ """The required input keys of the stage and their descriptions."""
+ return {
+ "payload": "The payload to send to the HTTP request. "
+ "It should be in JSON format."
+ }
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/prepare_image_stage.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/prepare_image_stage.py
new file mode 100644
index 0000000000000000000000000000000000000000..dc20379a9bcb09bea638a71396c5e766b6498f8a
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/prepare_image_stage.py
@@ -0,0 +1,408 @@
+"""Prepare Image Stage"""
+
+import asyncio
+import base64
+import importlib
+import logging
+from io import BytesIO
+from pathlib import Path
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ AsyncIterator,
+ Dict,
+ List,
+ Mapping,
+ MutableMapping,
+ Optional,
+ Union,
+)
+from urllib.parse import urlparse
+
+import aiohttp
+import requests
+
+from ray.llm._internal.batch.stages.base import (
+ StatefulStage,
+ StatefulStageUDF,
+)
+
+# TODO: Remove the guard once Pillow is added into the dependencies.
+if TYPE_CHECKING:
+ from PIL import Image
+
+logger = logging.getLogger(__name__)
+
+_ImageType = Union[str, "Image.Image"]
+
+
+class HTTPConnection:
+ """Adapted from vllm.connections.HTTPConnection.
+ Helper class to send HTTP requests.
+ """
+
+ def __init__(self, *, reuse_client: bool = True) -> None:
+ super().__init__()
+
+ self.reuse_client = reuse_client
+
+ self._sync_client: Optional[requests.Session] = None
+ self._async_client: Optional[aiohttp.ClientSession] = None
+
+ def get_sync_client(self) -> requests.Session:
+ if self._sync_client is None or not self.reuse_client:
+ self._sync_client = requests.Session()
+
+ return self._sync_client
+
+ # NOTE: We intentionally use an async function even though it is not
+ # required, so that the client is only accessible inside async event loop
+ async def get_async_client(self) -> aiohttp.ClientSession:
+ if self._async_client is None or not self.reuse_client:
+ self._async_client = aiohttp.ClientSession()
+
+ return self._async_client
+
+ def _validate_http_url(self, url: str):
+ parsed_url = urlparse(url)
+
+ if parsed_url.scheme not in ("http", "https"):
+ raise ValueError(
+ "Invalid HTTP URL: A valid HTTP URL "
+ "must have scheme 'http' or 'https'."
+ )
+
+ def _headers(self, **extras: str) -> MutableMapping[str, str]:
+ return {"User-Agent": "RayLLM-Batch", **extras}
+
+ def get_response(
+ self,
+ url: str,
+ *,
+ stream: bool = False,
+ timeout: Optional[float] = None,
+ extra_headers: Optional[Mapping[str, str]] = None,
+ ):
+ self._validate_http_url(url)
+
+ client = self.get_sync_client()
+ extra_headers = extra_headers or {}
+
+ return client.get(
+ url, headers=self._headers(**extra_headers), stream=stream, timeout=timeout
+ )
+
+ async def get_async_response(
+ self,
+ url: str,
+ *,
+ timeout: Optional[float] = None,
+ extra_headers: Optional[Mapping[str, str]] = None,
+ ):
+ self._validate_http_url(url)
+
+ client = await self.get_async_client()
+ extra_headers = extra_headers or {}
+
+ return client.get(url, headers=self._headers(**extra_headers), timeout=timeout)
+
+ def get_bytes(self, url: str, *, timeout: Optional[float] = None) -> bytes:
+ with self.get_response(url, timeout=timeout) as r:
+ r.raise_for_status()
+
+ return r.content
+
+ async def async_get_bytes(
+ self,
+ url: str,
+ *,
+ timeout: Optional[float] = None,
+ ) -> bytes:
+ async with await self.get_async_response(url, timeout=timeout) as r:
+ r.raise_for_status()
+
+ return await r.read()
+
+ def get_text(self, url: str, *, timeout: Optional[float] = None) -> str:
+ with self.get_response(url, timeout=timeout) as r:
+ r.raise_for_status()
+
+ return r.text
+
+ async def async_get_text(
+ self,
+ url: str,
+ *,
+ timeout: Optional[float] = None,
+ ) -> str:
+ async with await self.get_async_response(url, timeout=timeout) as r:
+ r.raise_for_status()
+
+ return await r.text()
+
+ def get_json(self, url: str, *, timeout: Optional[float] = None) -> str:
+ with self.get_response(url, timeout=timeout) as r:
+ r.raise_for_status()
+
+ return r.json()
+
+ async def async_get_json(
+ self,
+ url: str,
+ *,
+ timeout: Optional[float] = None,
+ ) -> str:
+ async with await self.get_async_response(url, timeout=timeout) as r:
+ r.raise_for_status()
+
+ return await r.json()
+
+ def download_file(
+ self,
+ url: str,
+ save_path: Path,
+ *,
+ timeout: Optional[float] = None,
+ chunk_size: int = 128,
+ ) -> Path:
+ with self.get_response(url, timeout=timeout) as r:
+ r.raise_for_status()
+
+ with save_path.open("wb") as f:
+ for chunk in r.iter_content(chunk_size):
+ f.write(chunk)
+
+ return save_path
+
+ async def async_download_file(
+ self,
+ url: str,
+ save_path: Path,
+ *,
+ timeout: Optional[float] = None,
+ chunk_size: int = 128,
+ ) -> Path:
+ async with await self.get_async_response(url, timeout=timeout) as r:
+ r.raise_for_status()
+
+ with save_path.open("wb") as f:
+ async for chunk in r.content.iter_chunked(chunk_size):
+ f.write(chunk)
+
+ return save_path
+
+
+class ImageProcessor:
+ """Download and load images."""
+
+ def __init__(self):
+ self.Image = importlib.import_module("PIL.Image")
+ self.http_connection = HTTPConnection()
+
+ async def download_image_from_url(self, image_url: str) -> Optional[bytes]:
+ """Download the image from the Internet with up to 3 retries.
+
+ Args:
+ image_url: The image URL to download.
+
+ Returns:
+ The image bytes (None if failed to download).
+ """
+ for _ in range(3):
+ try:
+ image_raw = await self.http_connection.async_get_bytes(
+ image_url, timeout=5
+ )
+ return image_raw
+ except Exception:
+ await asyncio.sleep(1)
+ return None
+
+ async def load_image_bytes_from_url(self, image_urls: List[str]) -> List[bytes]:
+ """Load an image from a URL.
+
+ Args:
+ image_urls: The image URLs to load.
+
+ Returns:
+ The image bytes.
+ """
+ return await asyncio.gather(
+ *[self.download_image_from_url(image_url) for image_url in image_urls]
+ )
+
+ async def fetch_images(
+ self, image_urls: List[str], *, image_mode: Optional[str] = None
+ ) -> List["Image.Image"]:
+ """
+ Adapted from vllm.multimodal.utils.fetch_image.
+ Load a PIL image from a HTTP or base64 data URL.
+
+ Args:
+ image_urls: A list of URLs of the images.
+ image_mode: The mode of the image. If None, the image is not converted.
+
+ Returns:
+ A list of loaded images.
+ """
+
+ def _load_image_from_bytes(b: bytes):
+ image = self.Image.open(BytesIO(b))
+ image.load()
+ return image
+
+ def _load_image_from_data_url(image_url: str):
+ # Only split once and assume the second part is the base64 encoded image
+ _, image_base64 = image_url.split(",", 1)
+ return _load_image_from_bytes(base64.b64decode(image_base64))
+
+ # Check if all image URLs are of the same type.
+ if image_urls[0].startswith("http"):
+ image_url_prefix = "http"
+ elif image_urls[0].startswith("data:image"):
+ image_url_prefix = "data:image"
+ else:
+ raise ValueError(f"Invalid image URL prefix: {image_urls[0]}")
+
+ if not all(url.startswith(image_url_prefix) for url in image_urls):
+ raise ValueError(
+ f"All image URLs must have the same prefix, got {image_url_prefix=}"
+ )
+
+ if image_url_prefix == "http":
+ image_raws = await self.load_image_bytes_from_url(image_urls)
+ images = [_load_image_from_bytes(image_raw) for image_raw in image_raws]
+ elif image_url_prefix == "data:image":
+ images = [_load_image_from_data_url(image_url) for image_url in image_urls]
+ else:
+ raise ValueError(
+ "Invalid 'image_url': A valid 'image_url' must start "
+ "with either 'data:image' or 'http'."
+ )
+
+ if image_mode is not None and images[0].mode != image_mode:
+ images = [image.convert(image_mode) for image in images]
+ return images
+
+ async def process(self, images: List[_ImageType]) -> List["Image.Image"]:
+ """Load and resize an image for the model.
+ Args:
+ image: A list of images.
+
+ Returns:
+ A list of processed images.
+ """
+ if not images:
+ return []
+
+ # Check if all images are of the same type.
+ image_type = type(images[0])
+ if not all(isinstance(img, image_type) for img in images):
+ raise ValueError(f"All images must be of the same type, got {image_type=}")
+
+ if not issubclass(image_type, self.Image.Image):
+ images = await self.fetch_images(images)
+ return images
+
+
+class PrepareImageUDF(StatefulStageUDF):
+ def __init__(self, data_column: str, expected_input_keys: List[str]):
+ super().__init__(data_column, expected_input_keys)
+ self.Image = importlib.import_module("PIL.Image")
+ self.image_processor = ImageProcessor()
+
+ def extract_image_info(self, messages: List[Dict]) -> List[_ImageType]:
+ """Extract image information from chat messages.
+
+ Args:
+ messages: List of chat messages.
+
+ Returns:
+ List of _ImageType.
+
+ Note:
+ The optional 'detail' parameter from the OpenAI schema is not
+ passed forward to downstream templates.
+ """
+
+ image_info: List[_ImageType] = []
+ for message in messages:
+ content = message["content"]
+
+ # Convert PyArrow objects to Python objects if needed (like ChatTemplateStage).
+ # This handles the case where unform content types are serialized with PyArrow
+ # instead of pickle- happens when all messages have the same content structure
+ # (e.g., no system prompt + string content mixed with user messages with list content).
+ if hasattr(content, "tolist"):
+ content = content.tolist()
+
+ if not isinstance(content, list):
+ continue
+ for content_item in content:
+ if content_item["type"] not in ("image", "image_url"):
+ continue
+
+ image_data = content_item[content_item["type"]]
+
+ if content_item["type"] == "image_url" and isinstance(image_data, dict):
+ # OpenAI nested format: {"image_url": {"url": "..."}}
+ image = image_data.get("url")
+ if not isinstance(image, str) or not image:
+ raise ValueError(
+ "image_url must be an object with a non-empty 'url' string"
+ )
+ else:
+ # Simple format: {"image": "..."} or {"image_url": "..."}
+ image = image_data
+
+ if not isinstance(image, str) and not isinstance(
+ image, self.Image.Image
+ ):
+ raise ValueError(f"Cannot handle image type {type(image)}")
+ image_info.append(image)
+ return image_info
+
+ async def udf(self, batch: List[Dict[str, Any]]) -> AsyncIterator[Dict[str, Any]]:
+ messages = [row["messages"] for row in batch]
+
+ # Process all images in this batch.
+ all_image_info = [self.extract_image_info(message) for message in messages]
+ flat_all_image_info = [img for imgs in all_image_info for img in imgs]
+ flat_all_images = await self.image_processor.process(flat_all_image_info)
+
+ # TODO: We now use asyncio.gather to process all images in this batch,
+ # so the outputs here must be in order. However, it is more efficient
+ # to support out-of-order outputs so that we won't be blocked by slow
+ # downloaded images.
+ img_start_idx = 0
+ idx_in_batch = 0
+ for image_info_per_req in all_image_info:
+ num_images_in_req = len(image_info_per_req)
+ ret = {self.IDX_IN_BATCH_COLUMN: idx_in_batch}
+ idx_in_batch += 1
+ if num_images_in_req > 0:
+ images = flat_all_images[
+ img_start_idx : img_start_idx + num_images_in_req
+ ]
+ ret.update(
+ {
+ "image": images,
+ "image_sizes": [(img.width, img.height) for img in images],
+ }
+ )
+ img_start_idx += num_images_in_req
+ yield ret
+
+
+class PrepareImageStage(StatefulStage):
+ """A stage to prepare images from OpenAI chat template messages."""
+
+ fn: StatefulStageUDF = PrepareImageUDF
+
+ def get_required_input_keys(self) -> Dict[str, str]:
+ """The required input keys of the stage and their descriptions."""
+ return {
+ "messages": "A list of messages in OpenAI chat format. "
+ "See https://platform.openai.com/docs/api-reference/chat/create "
+ "for details."
+ }
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/serve_deployment_stage.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/serve_deployment_stage.py
new file mode 100644
index 0000000000000000000000000000000000000000..04626e734cd7cb8f033ece71b736e6a31ba6cb1f
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/serve_deployment_stage.py
@@ -0,0 +1,156 @@
+"""The stage that runs serve deployment."""
+
+import asyncio
+import logging
+import time
+import uuid
+from typing import Any, AsyncIterator, Dict, List, Optional, Tuple, Type
+
+from pydantic import BaseModel
+
+from ray import serve
+from ray.llm._internal.batch.stages.base import (
+ StatefulStage,
+ StatefulStageUDF,
+)
+
+logger = logging.getLogger(__name__)
+
+
+class ServeDeploymentStageUDF(StatefulStageUDF):
+ def __init__(
+ self,
+ data_column: str,
+ expected_input_keys: List[str],
+ *,
+ deployment_name: str,
+ app_name: str,
+ dtype_mapping: Dict[str, Type[Any]],
+ ):
+ """
+ Initialize the ServeDeploymentStageUDF.
+
+ Args:
+ data_column: The data column name.
+ expected_input_keys: The expected input keys of the stage.
+ deployment_name: The name of the deployment.
+ app_name: The name of the deployment app.
+ dtype_mapping: The mapping of the request class name to the request class.
+ """
+ super().__init__(data_column, expected_input_keys)
+ self._dtype_mapping = dtype_mapping
+
+ # Using stream=True as LLM serve deployments return async generators.
+ # TODO (Kourosh): Generalize this to support non-streaming deployments.
+ self._dh = serve.get_deployment_handle(deployment_name, app_name).options(
+ stream=True
+ )
+ self.request_id = 0
+
+ def _prepare_request(
+ self, row: Dict[str, Any]
+ ) -> Tuple[Dict[str, Any], Optional[Type[Any]], str]:
+ """
+ Decorate the request with metadata related to the batch.
+
+ Args:
+ row: The row.
+
+ Returns:
+ A tuple of (decorated_request, dtype, method_name). dtype is the class of the request object and
+ can be None if the serve deployment accepts a raw dict. method_name is the name of the method to
+ invoke on the serve deployment.
+ """
+ method = row.get("method")
+ dtype_name = row.get("dtype")
+
+ dtype = None
+ if dtype_name is not None:
+ if not self._dtype_mapping or dtype_name not in self._dtype_mapping:
+ raise ValueError(
+ f"{dtype_name} must be provided in ServeDeploymentProcessorConfig's dtype_mapping."
+ )
+ dtype = self._dtype_mapping[dtype_name]
+
+ request_kwargs = row.pop("request_kwargs")
+ request = {
+ "request_id": str(self.request_id),
+ "idx_in_batch": row[self.IDX_IN_BATCH_COLUMN],
+ **request_kwargs,
+ }
+ self.request_id += 1
+
+ return request, dtype, method
+
+ async def generate_async(
+ self, row: Dict[str, Any]
+ ) -> Tuple[Dict[str, Any], Dict[str, Any], float]:
+ """
+ Run the serve deployment.
+
+ Args:
+ row: The row to run the serve deployment on.
+
+ Returns:
+ The response from the serve deployment.
+ """
+ request, dtype, method = self._prepare_request(row)
+ request_obj = dtype(**request) if dtype else request
+
+ if getattr(self._dh, method) is None:
+ raise ValueError(f"Method {method} not found in the serve deployment.")
+
+ t = time.perf_counter()
+ # Directly using anext() requires python3.10 and above
+ output_data = await getattr(self._dh, method).remote(request_obj).__anext__()
+ time_taken = time.perf_counter() - t
+
+ # Convert the output data to a dict if it is a Pydantic model.
+ if isinstance(output_data, BaseModel):
+ output_data = output_data.model_dump()
+
+ return request, output_data, time_taken
+
+ async def udf(self, batch: List[Dict[str, Any]]) -> AsyncIterator[Dict[str, Any]]:
+ """
+ Run the serve deployment.
+
+ Args:
+ batch: A list of rows to run the serve deployment on.
+
+ Yields:
+ Dict[str, Any]: A dictionary containing the response from the serve deployment
+ along with processing metadata.
+ """
+ batch_uuid = uuid.uuid4()
+ t = time.perf_counter()
+ tasks = [asyncio.create_task(self.generate_async(row)) for row in batch]
+
+ for resp in asyncio.as_completed(tasks):
+ request, output, time_taken = await resp
+
+ yield {
+ "request_id": request["request_id"],
+ self.IDX_IN_BATCH_COLUMN: request["idx_in_batch"],
+ "batch_uuid": batch_uuid.hex,
+ "time_taken": time_taken,
+ **output,
+ }
+
+ batch_time_taken = time.perf_counter() - t
+ logger.info(
+ "[LLM Batch - Serve Deployment] Elapsed time for batch %s with size %d: %s",
+ batch_uuid.hex,
+ len(batch),
+ batch_time_taken,
+ )
+
+
+class ServeDeploymentStage(StatefulStage):
+ fn: Type[StatefulStageUDF] = ServeDeploymentStageUDF
+
+ def get_required_input_keys(self) -> Dict[str, str]:
+ return {
+ "method": "Name of the method to invoke on the serve deployment.",
+ "request_kwargs": "The request_kwargs to construct the request to the serve deployment.",
+ }
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/sglang_engine_stage.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/sglang_engine_stage.py
new file mode 100644
index 0000000000000000000000000000000000000000..ba2a69e80fe27c6fbb6f4225ac6bf85f311a697b
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/sglang_engine_stage.py
@@ -0,0 +1,404 @@
+"""The stage that runs SGLang engine."""
+
+import asyncio
+import logging
+import time
+import uuid
+from contextlib import nullcontext
+from enum import Enum
+from typing import Any, AsyncIterator, Dict, List, Optional, Tuple, Type
+
+from pydantic import BaseModel, root_validator
+
+from ray.llm._internal.batch.stages.base import (
+ StatefulStage,
+ StatefulStageUDF,
+)
+from ray.llm._internal.batch.stages.common import maybe_convert_ndarray_to_list
+
+logger = logging.getLogger(__name__)
+
+
+class SGLangTaskType(str, Enum):
+ """The type of task to run on the SGLang engine."""
+
+ """Generate text."""
+ GENERATE = "generate"
+
+
+class SGLangEngineRequest(BaseModel):
+ """A request to the SGLang engine."""
+
+ # The request ID for the LLM engine (unique per replica).
+ request_id: int
+ # The index of the request in the batch.
+ idx_in_batch: int
+ # The input prompt.
+ prompt: Optional[str]
+ # Alternative to text. Specify the input as token IDs instead of text.
+ prompt_token_ids: Optional[List[int]]
+ # The sampling parameters (more details can be seen in https://docs.sglang.ai/backend/sampling_params.html).
+ params: Optional[Dict[str, Any]]
+
+ class Config:
+ validate_assignment = True
+ arbitrary_types_allowed = True
+
+
+class SGLangOutputData(BaseModel):
+ """The output of the SGLang engine."""
+
+ prompt: Optional[str]
+ prompt_token_ids: Optional[List[int]]
+ num_input_tokens: int
+
+ # Generate fields.
+ generated_tokens: Optional[List[int]]
+ generated_text: Optional[str]
+ num_generated_tokens: int
+
+ # Metrics fields.
+ metrics: Optional[Dict[str, Any]] = None
+
+ @classmethod
+ def from_sglang_engine_output(cls, output: Dict[str, Any]) -> "SGLangOutputData":
+ """Create a SGLangOutputData from a SGLang engine output."""
+
+ # Set by `_generate_async`.
+ assert "prompt" in output
+ assert "prompt_token_ids" in output
+
+ # Returned in the native output of the SGLang engine.
+ assert "meta_info" in output
+ assert "prompt_tokens" in output["meta_info"]
+ assert "completion_tokens" in output["meta_info"]
+
+ data = cls(
+ prompt=output["prompt"],
+ prompt_token_ids=output["prompt_token_ids"],
+ num_input_tokens=output["meta_info"]["prompt_tokens"],
+ generated_tokens=output["output_ids"] if "output_ids" in output else None,
+ generated_text=output["text"] if "text" in output else None,
+ num_generated_tokens=output["meta_info"]["completion_tokens"],
+ )
+
+ return data
+
+ class Config:
+ validate_assignment = True
+ arbitrary_types_allowed = True
+
+
+class SGLangEngineWrapper:
+ """Wrapper around the SGLang engine to handle async requests.
+
+ Args:
+ *args: The positional arguments for the engine.
+ max_pending_requests: The maximum number of pending requests in the queue.
+ **kwargs: The keyword arguments for the engine.
+ """
+
+ def __init__(
+ self,
+ idx_in_batch_column: str,
+ max_pending_requests: int = -1,
+ **kwargs,
+ ):
+ self.request_id = 0
+ self.idx_in_batch_column = idx_in_batch_column
+ self.task_type = kwargs.pop("task", SGLangTaskType.GENERATE)
+ self.model = kwargs.pop("model", None)
+ assert self.model is not None
+ # We need to rename the `model` to `model_path` for SGLang.
+ kwargs["model_path"] = self.model
+
+ # Set the skip_tokenizer_init to True by default for SGLang engine
+ # because we will not use the tokenizer/detokenizer in SGLang engine
+ # by default.
+ self.skip_tokenizer_init = kwargs.pop("skip_tokenizer_init", True)
+ kwargs["skip_tokenizer_init"] = self.skip_tokenizer_init
+
+ try:
+ import sglang
+ except ImportError as e:
+ raise ImportError(
+ "SGLang is not installed or failed to import. Please run "
+ "`pip install sglang[all]` to install required dependencies."
+ ) from e
+
+ # Initialize the SGLang engine
+ self.engine = sglang.Engine(**kwargs)
+
+ # The performance gets really bad if there are too many requests in the pending queue.
+ # We work around it with semaphore to limit the number of concurrent requests in the engine.
+ self.max_pending_requests = max_pending_requests
+ if self.max_pending_requests > 0:
+ self.semaphore = asyncio.Semaphore(self.max_pending_requests)
+ else:
+ # Use contextlib.nullcontext which works for both sync and async contexts.
+ self.semaphore = nullcontext()
+
+ async def _prepare_llm_request(self, row: Dict[str, Any]) -> SGLangEngineRequest:
+ """Prepare the inputs for LLM inference.
+
+ Args:
+ row: The row.
+
+ Returns:
+ A single SGLangEngineRequest.
+ """
+ prompt = row.pop("prompt")
+
+ if "tokenized_prompt" in row:
+ tokenized_prompt = row.pop("tokenized_prompt").tolist()
+ else:
+ tokenized_prompt = None
+
+ # Prepare sampling parameters.
+ if self.task_type == SGLangTaskType.GENERATE:
+ params = maybe_convert_ndarray_to_list(row.pop("sampling_params"))
+ else:
+ raise ValueError(f"Unsupported task type: {self.task_type}")
+
+ if tokenized_prompt is not None and not self.skip_tokenizer_init:
+ raise ValueError(
+ "To use a token-in-token-out mode of SGLang Engine, please set engine_kwargs['skip_tokenizer_init'] to True."
+ )
+
+ request = SGLangEngineRequest(
+ request_id=self.request_id,
+ idx_in_batch=row[self.idx_in_batch_column],
+ prompt=prompt,
+ prompt_token_ids=tokenized_prompt,
+ params=params,
+ )
+ self.request_id += 1
+ return request
+
+ async def generate_async(
+ self, row: Dict[str, Any]
+ ) -> Tuple[SGLangEngineRequest, Dict[str, Any], float]:
+ """Process a single request.
+
+ Args:
+ request: The request.
+
+ Returns:
+ A tuple of index in batch, request output and bypassed custom fields, and time taken.
+ """
+ request = await self._prepare_llm_request(row)
+ t = time.perf_counter()
+
+ async with self.semaphore:
+ output = await self._generate_async(request)
+
+ time_taken = time.perf_counter() - t
+
+ output_data = SGLangOutputData.from_sglang_engine_output(output)
+ return request, output_data.model_dump(), time_taken
+
+ async def _generate_async(self, request: SGLangEngineRequest) -> Any:
+ """Process a single request.
+
+ Args:
+ request: The request.
+
+ Returns:
+ The output of the request.
+ """
+
+ # Send the request to the LLM engine.
+ stream = await self.engine.async_generate(
+ prompt=request.prompt,
+ input_ids=request.prompt_token_ids,
+ sampling_params=request.params,
+ stream=True,
+ )
+
+ # Consume the stream until the request is finished.
+ async for output in stream:
+ if output["meta_info"]["finish_reason"] is not None:
+ output["prompt"] = request.prompt
+ output["prompt_token_ids"] = request.prompt_token_ids
+ return output
+
+ raise RuntimeError(
+ "[SGLang] The request is not finished. This should not happen. Please report this issue to the Ray team."
+ )
+
+ def shutdown(self):
+ """Shutdown the SGLang engine."""
+ if hasattr(self.engine, "shutdown"):
+ logger.info("Shutting down SGLang engine")
+ self.engine.shutdown()
+
+
+class SGLangEngineStageUDF(StatefulStageUDF):
+ def __init__(
+ self,
+ data_column: str,
+ expected_input_keys: List[str],
+ model: str,
+ engine_kwargs: Dict[str, Any],
+ task_type: SGLangTaskType = SGLangTaskType.GENERATE,
+ max_pending_requests: Optional[int] = None,
+ ):
+ """
+ Initialize the SGLangEngineStageUDF.
+
+ Args:
+ data_column: The data column name.
+ expected_input_keys: The expected input keys of the stage.
+ model: The path to the model to use for the SGLang engine.
+ engine_kwargs: The kwargs to pass to the SGLang engine.
+ task_type: The task to use for the SGLang engine (e.g., "generate", "embed", "reward").
+ max_pending_requests: The maximum number of pending requests. If None,
+ it will be set to a default value based on engine settings.
+ """
+ super().__init__(data_column, expected_input_keys)
+ self.model = model
+
+ # Setup SGLang engine kwargs.
+ self.task_type = task_type
+ self.engine_kwargs = self.normalize_engine_kwargs(task_type, engine_kwargs)
+
+ # Set up the max pending requests.
+ # Disable the semaphore if max_pending_requests is not set.
+ self.max_pending_requests = max_pending_requests or -1
+ if self.max_pending_requests > 0:
+ logger.info("Max pending requests is set to %d", self.max_pending_requests)
+
+ # Create an LLM engine.
+ self.llm = SGLangEngineWrapper(
+ model=self.model,
+ idx_in_batch_column=self.IDX_IN_BATCH_COLUMN,
+ max_pending_requests=self.max_pending_requests,
+ **self.engine_kwargs,
+ )
+
+ def normalize_engine_kwargs(
+ self,
+ task_type: SGLangTaskType,
+ engine_kwargs: Dict[str, Any],
+ ) -> Dict[str, Any]:
+ """
+ Normalize the engine kwargs.
+
+ Args:
+ task_type: The task to use for the SGLang engine (e.g., "generate", etc).
+ engine_kwargs: The kwargs to normalize.
+
+ Returns:
+ The normalized kwargs.
+ """
+ # Remove model from engine kwargs if set.
+ model = engine_kwargs.pop("model", None)
+ if model is not None and model != self.model:
+ logger.warning(
+ "The model set in engine kwargs (%s) is different from the "
+ "stage (%s). Please remove 'model' from engine kwargs.",
+ model,
+ self.model,
+ )
+
+ # Override the task if it is different from the stage.
+ task = SGLangTaskType(engine_kwargs.get("task", task_type))
+ if task != task_type:
+ logger.warning(
+ "The task set in engine kwargs (%s) is different from the "
+ "stage (%s). Overriding the task in engine kwargs to %s.",
+ task,
+ task_type,
+ task_type,
+ )
+ engine_kwargs["task"] = task_type
+ return engine_kwargs
+
+ async def udf(self, batch: List[Dict[str, Any]]) -> AsyncIterator[Dict[str, Any]]:
+ """Run the SGLang engine.
+
+ Args:
+ batch: A list of rows to run the SGLang engine on.
+
+ Returns:
+ The response of the SGLang engine.
+ """
+ batch_uuid = uuid.uuid4()
+ batch_start_time = time.perf_counter()
+
+ tasks = [asyncio.create_task(self.llm.generate_async(row)) for row in batch]
+
+ for resp in asyncio.as_completed(tasks):
+ request, output, time_taken_llm = await resp
+
+ yield {
+ **output,
+ "request_id": request.request_id,
+ self.IDX_IN_BATCH_COLUMN: request.idx_in_batch,
+ "batch_uuid": batch_uuid.hex,
+ "time_taken_llm": time_taken_llm,
+ "params": str(request.params),
+ }
+
+ batch_time_taken = time.perf_counter() - batch_start_time
+ logger.info(
+ "[SGLang] Elapsed time for batch %s with size %d: %s",
+ batch_uuid.hex,
+ len(batch),
+ batch_time_taken,
+ )
+
+ def __del__(self):
+ if hasattr(self, "llm"):
+ self.llm.shutdown()
+
+
+class SGLangEngineStage(StatefulStage):
+ """
+ A stage that runs SGLang engine.
+ """
+
+ fn: Type[StatefulStageUDF] = SGLangEngineStageUDF
+
+ @root_validator(pre=True)
+ def post_init(cls, values):
+ """Post-initialize the stage. Specifically,
+ this function determines the num_gpus and Ray remote args
+ for the .map_batches() call in this stage.
+
+ Args:
+ values: The raw stage values.
+ Returns:
+ The updated values.
+ """
+ map_batches_kwargs = values["map_batches_kwargs"]
+ accelerator_type = map_batches_kwargs.get("accelerator_type", "")
+ fn_constructor_kwargs = values["fn_constructor_kwargs"]
+ engine_kwargs = fn_constructor_kwargs.get("engine_kwargs", {})
+
+ ray_remote_args = {}
+ if accelerator_type:
+ ray_remote_args["accelerator_type"] = accelerator_type
+
+ # Set up num_gpus required
+ tp_size = engine_kwargs.get("tp_size", 1)
+ dp_size = engine_kwargs.get("dp_size", 1)
+ num_gpus = tp_size * dp_size
+
+ ray_remote_args["num_gpus"] = num_gpus
+ map_batches_kwargs.update(ray_remote_args)
+ return values
+
+ def get_required_input_keys(self) -> Dict[str, str]:
+ """The required input keys of the stage and their descriptions."""
+ ret = {"prompt": "The text prompt (str)."}
+ task_type = self.fn_constructor_kwargs.get("task_type", SGLangTaskType.GENERATE)
+ if task_type == SGLangTaskType.GENERATE:
+ ret[
+ "sampling_params"
+ ] = "The sampling parameters. See https://docs.sglang.ai/backend/sampling_params.html for details."
+ return ret
+
+ def get_optional_input_keys(self) -> Dict[str, str]:
+ """The optional input keys of the stage and their descriptions."""
+ return {}
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/tokenize_stage.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/tokenize_stage.py
new file mode 100644
index 0000000000000000000000000000000000000000..b6b64e29052e212b06d8a5ac4d6171b307fd3ec5
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/tokenize_stage.py
@@ -0,0 +1,142 @@
+"""Tokenize and detokenize stage"""
+
+from typing import Any, AsyncIterator, Dict, List, Type
+
+from ray.llm._internal.batch.stages.base import (
+ StatefulStage,
+ StatefulStageUDF,
+)
+from ray.llm._internal.batch.utils import get_cached_tokenizer
+from ray.llm._internal.common.utils.download_utils import (
+ NodeModelDownloadable,
+ download_model_files,
+)
+
+
+class TokenizeUDF(StatefulStageUDF):
+ def __init__(
+ self,
+ data_column: str,
+ expected_input_keys: List[str],
+ model: str,
+ ):
+ """
+ Initialize the TokenizeUDF.
+
+ Args:
+ data_column: The data column name.
+ expected_input_keys: The expected input keys of the stage.
+ model: The model to use for the chat template.
+ """
+ from transformers import AutoTokenizer
+
+ super().__init__(data_column, expected_input_keys)
+ model_path = download_model_files(
+ model_id=model,
+ mirror_config=None,
+ download_model=NodeModelDownloadable.TOKENIZER_ONLY,
+ download_extra_files=False,
+ )
+ self.tokenizer = get_cached_tokenizer(
+ AutoTokenizer.from_pretrained(
+ model_path,
+ trust_remote_code=True,
+ )
+ )
+
+ async def udf(self, batch: List[Dict[str, Any]]) -> AsyncIterator[Dict[str, Any]]:
+ """
+ Tokenize the given batch.
+
+ Args:
+ batch: A list of rows to send.
+
+ Yields:
+ A generator of rows with the tokenized prompt.
+ """
+ for row, prompt_token_ids in zip(
+ batch,
+ self.tokenizer([row["prompt"] for row in batch])["input_ids"],
+ ):
+ yield {
+ self.IDX_IN_BATCH_COLUMN: row[self.IDX_IN_BATCH_COLUMN],
+ "tokenized_prompt": prompt_token_ids,
+ }
+
+
+class TokenizeStage(StatefulStage):
+ """
+ A stage that tokenizes the input.
+ """
+
+ fn: Type[StatefulStageUDF] = TokenizeUDF
+
+ def get_required_input_keys(self) -> Dict[str, str]:
+ """The required input keys of the stage and their descriptions."""
+ return {"prompt": "The text prompt (str) to tokenize."}
+
+
+class DetokenizeUDF(StatefulStageUDF):
+ def __init__(
+ self,
+ data_column: str,
+ expected_input_keys: List[str],
+ model: str,
+ ):
+ """
+ Initialize the DetokenizeUDF.
+
+ Args:
+ data_column: The data column name.
+ expected_input_keys: The expected input keys of the stage.
+ model: The model to use for the chat template.
+ """
+ from transformers import AutoTokenizer
+
+ super().__init__(data_column, expected_input_keys)
+ model_path = download_model_files(
+ model_id=model,
+ mirror_config=None,
+ download_model=NodeModelDownloadable.TOKENIZER_ONLY,
+ download_extra_files=False,
+ )
+ self.tokenizer = get_cached_tokenizer(
+ AutoTokenizer.from_pretrained(
+ model_path,
+ trust_remote_code=True,
+ )
+ )
+
+ async def udf(self, batch: List[Dict[str, Any]]) -> AsyncIterator[Dict[str, Any]]:
+ """
+ Detokenize the given batch.
+
+ Args:
+ batch: A list of rows to send.
+
+ Yields:
+ A generator of rows with the detokenized prompt.
+ """
+ for row, generated_text in zip(
+ batch,
+ self.tokenizer.batch_decode(
+ [row["generated_tokens"] for row in batch],
+ skip_special_tokens=True,
+ ),
+ ):
+ yield {
+ self.IDX_IN_BATCH_COLUMN: row[self.IDX_IN_BATCH_COLUMN],
+ "generated_text": generated_text,
+ }
+
+
+class DetokenizeStage(StatefulStage):
+ """
+ A stage that detokenizes the input.
+ """
+
+ fn: Type[StatefulStageUDF] = DetokenizeUDF
+
+ def get_required_input_keys(self) -> Dict[str, str]:
+ """The required input keys of the stage and their descriptions."""
+ return {"generated_tokens": "A list of generated tokens (int) to detokenize."}
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/vllm_engine_stage.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/vllm_engine_stage.py
new file mode 100644
index 0000000000000000000000000000000000000000..b69ab5b2610606cc783d990613a4daf32c04b173
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/stages/vllm_engine_stage.py
@@ -0,0 +1,698 @@
+"""The stage that runs vLLM engine."""
+
+import asyncio
+import copy
+import dataclasses
+import logging
+import math
+import time
+import uuid
+from collections import Counter
+from enum import Enum
+from functools import partial
+from typing import Any, AsyncIterator, Dict, List, Optional, Tuple, Type
+
+import numpy as np
+import torch
+from pydantic import BaseModel, Field, root_validator
+
+import ray
+from ray.llm._internal.batch.stages.base import (
+ StatefulStage,
+ StatefulStageUDF,
+)
+from ray.llm._internal.batch.stages.common import maybe_convert_ndarray_to_list
+from ray.llm._internal.common.utils.cloud_utils import is_remote_path
+from ray.llm._internal.common.utils.download_utils import (
+ STREAMING_LOAD_FORMATS,
+ NodeModelDownloadable,
+ download_model_files,
+)
+from ray.llm._internal.common.utils.lora_utils import download_lora_adapter
+from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy
+
+logger = logging.getLogger(__name__)
+
+
+class vLLMTaskType(str, Enum):
+ """The type of task to run on the vLLM engine."""
+
+ """Generate text."""
+ GENERATE = "generate"
+
+ """Generate embeddings."""
+ EMBED = "embed"
+
+
+class vLLMEngineRequest(BaseModel):
+ """A request to the vLLM engine."""
+
+ # The request ID for the LLM engine (unique per replica).
+ request_id: int
+ # The index of the request in the batch.
+ idx_in_batch: int
+ # The full prompt string (with chat template applied if any).
+ prompt: str
+ # The images inputs for the multimodal model. Use Any to avoid importing PIL.
+ images: List[Any]
+ # The tokenized prompt IDs. If None, then the string prompt will be
+ # tokenized by the LLM engine. This is not recommended for performance reasons.
+ prompt_token_ids: Optional[List[int]]
+ # The sampling or pooling parameters. Use Any to avoid importing vLLM.
+ params: Any
+ # LoRA request.
+ lora_request: Optional[Any] = None
+
+ class Config:
+ validate_assignment = True
+ arbitrary_types_allowed = True
+
+
+class vLLMOutputData(BaseModel):
+ """The output of the vLLM engine."""
+
+ prompt: str
+ prompt_token_ids: Optional[List[int]]
+ num_input_tokens: int
+
+ # Generate fields.
+ generated_tokens: List[int] = Field(default_factory=list)
+ generated_text: str = Field(default="")
+ num_generated_tokens: int = Field(default=0)
+
+ # Embed fields. The type should be torch.Tensor, but we use Any to avoid
+ # importing torch because of an error in sphinx-build with an unknown reason.
+ embeddings: Optional[Any] = None
+
+ # Metrics fields.
+ metrics: Optional[Dict[str, Any]] = None
+
+ @classmethod
+ def from_vllm_engine_output(cls, output: Any) -> "vLLMOutputData":
+ """Create a vLLMOutputData from a vLLM engine output."""
+
+ prompt_token_ids = output.prompt_token_ids
+ if isinstance(prompt_token_ids, np.ndarray):
+ prompt_token_ids = prompt_token_ids.tolist()
+
+ data = cls(
+ prompt=output.prompt,
+ prompt_token_ids=prompt_token_ids,
+ num_input_tokens=len(prompt_token_ids),
+ )
+
+ import vllm
+
+ if isinstance(output, vllm.outputs.RequestOutput):
+ metrics = {}
+ if output.metrics is not None:
+ metrics = dict(dataclasses.asdict(output.metrics))
+ data.metrics = metrics
+ data.generated_tokens = output.outputs[0].token_ids
+ data.generated_text = output.outputs[0].text
+ data.num_generated_tokens = len(output.outputs[0].token_ids)
+ elif isinstance(output, vllm.outputs.PoolingRequestOutput):
+ data.embeddings = output.outputs.data.cpu()
+ if (
+ isinstance(data.embeddings, torch.Tensor)
+ and data.embeddings.dtype == torch.bfloat16
+ ):
+ data.embeddings = data.embeddings.to(torch.float32)
+ else:
+ raise ValueError(f"Unknown output type: {type(output)}")
+
+ return data
+
+ class Config:
+ validate_assignment = True
+ arbitrary_types_allowed = True
+
+
+class vLLMEngineWrapper:
+ """Wrapper around the vLLM engine to handle async requests.
+
+ Args:
+ *args: The positional arguments for the engine.
+ max_pending_requests: The maximum number of pending requests in the queue.
+ dynamic_lora_loading_path: The S3 path to the dynamic LoRA adapter.
+ **kwargs: The keyword arguments for the engine.
+ """
+
+ def __init__(
+ self,
+ idx_in_batch_column: str,
+ max_pending_requests: int = -1,
+ dynamic_lora_loading_path: Optional[str] = None,
+ **kwargs,
+ ):
+ self.request_id = 0
+ self.idx_in_batch_column = idx_in_batch_column
+ self.task_type = kwargs.get("task", vLLMTaskType.GENERATE)
+
+ # Use model_source in kwargs["model"] because "model" is actually
+ # the model source in vLLM.
+ self.model = kwargs.pop("model", None)
+ self.model_source = kwargs.pop("model_source", None)
+ assert self.model is not None and self.model_source is not None
+ kwargs["model"] = self.model_source
+
+ # LoRA related.
+ self.dynamic_lora_loading_path = dynamic_lora_loading_path
+ self.lora_lock = asyncio.Lock()
+ self.lora_name_to_request = {}
+
+ # Convert the task type back to a string to pass to the engine.
+ kwargs["task"] = self.task_type.value
+
+ try:
+ import vllm
+ except ImportError as e:
+ raise ImportError(
+ "vLLM is not installed or failed to import. Please run "
+ "`pip install ray[llm]` to install required dependencies."
+ ) from e
+
+ # Construct PoolerConfig if override_pooler_config is specified.
+ if self.task_type == vLLMTaskType.EMBED and "override_pooler_config" in kwargs:
+ kwargs["override_pooler_config"] = vllm.config.PoolerConfig(
+ **kwargs["override_pooler_config"]
+ )
+
+ # Initialize the vLLM engine.
+ engine_args = vllm.AsyncEngineArgs(
+ **kwargs,
+ )
+ # create_engine_config will set default values including `max_num_seqs`.
+ self._vllm_config = engine_args.create_engine_config()
+ self.engine = vllm.AsyncLLMEngine.from_engine_args(engine_args)
+
+ # The performance gets really bad if there are too many requests in the pending queue.
+ # We work around it with semaphore to limit the number of concurrent requests in the engine.
+ self.max_pending_requests = max_pending_requests
+ if self.max_pending_requests > 0:
+ self.semaphore = asyncio.Semaphore(self.max_pending_requests)
+ else:
+ self.semaphore = asyncio.NullContext()
+
+ async def _maybe_get_lora_request(
+ self,
+ row: Dict[str, Any],
+ ) -> Optional[Any]:
+ """Get the LoRA request for the given row.
+ Specifically, if the model name is given and is different from the model
+ set in the config, then this request has LoRA.
+
+ Args:
+ row: The row.
+
+ Returns:
+ The LoRA request (vllm.lora.request.LoRARequest),
+ or None if there is no LoRA. We use Any in type hint to
+ pass doc build in the environment without vLLM.
+ """
+ import vllm
+
+ lora_request = None
+ if "model" in row and row["model"] != self.model:
+
+ lora_name = row["model"]
+ if lora_name not in self.lora_name_to_request:
+ if is_remote_path(lora_name):
+ raise ValueError(
+ "LoRA name cannot be a remote path (s3:// or gs://). "
+ "Please specify dynamic_lora_loading_path in the processor config."
+ )
+
+ async with self.lora_lock:
+ if lora_name not in self.lora_name_to_request:
+ # Load a new LoRA adapter if it is not loaded yet.
+ lora_path = download_lora_adapter(
+ lora_name,
+ remote_path=self.dynamic_lora_loading_path,
+ )
+ logger.info(
+ "Downloaded LoRA adapter for %s to %s", lora_name, lora_path
+ )
+ lora_request = vllm.lora.request.LoRARequest(
+ lora_name=lora_name,
+ # LoRA ID starts from 1.
+ lora_int_id=len(self.lora_name_to_request) + 1,
+ lora_path=lora_path,
+ )
+ self.lora_name_to_request[lora_name] = lora_request
+ lora_request = self.lora_name_to_request[lora_name]
+ return lora_request
+
+ async def _prepare_llm_request(self, row: Dict[str, Any]) -> vLLMEngineRequest:
+ """Prepare the inputs for LLM inference.
+
+ Args:
+ row: The row.
+
+ Returns:
+ A single vLLMEngineRequest.
+ """
+ prompt = row.pop("prompt")
+
+ if "tokenized_prompt" in row:
+ tokenized_prompt = maybe_convert_ndarray_to_list(
+ row.pop("tokenized_prompt")
+ )
+ else:
+ tokenized_prompt = None
+
+ # Extract image data from preprocessing output
+ # Note: Field name is 'image' (singular) not 'images' (plural).
+ if "image" in row:
+ image = row.pop("image")
+ else:
+ image = []
+
+ lora_request = await self._maybe_get_lora_request(row)
+
+ # Prepare sampling parameters.
+ import vllm
+
+ if self.task_type == vLLMTaskType.GENERATE:
+ sampling_params = row.pop("sampling_params")
+ if "guided_decoding" in sampling_params:
+ guided_decoding = vllm.sampling_params.GuidedDecodingParams(
+ **maybe_convert_ndarray_to_list(
+ sampling_params.pop("guided_decoding")
+ )
+ )
+ else:
+ guided_decoding = None
+ params = vllm.SamplingParams(
+ **maybe_convert_ndarray_to_list(sampling_params),
+ guided_decoding=guided_decoding,
+ )
+ elif self.task_type == vLLMTaskType.EMBED:
+ params = vllm.PoolingParams(task=self.task_type.value)
+ else:
+ raise ValueError(f"Unsupported task type: {self.task_type}")
+
+ request = vLLMEngineRequest(
+ request_id=self.request_id,
+ idx_in_batch=row[self.idx_in_batch_column],
+ prompt=prompt,
+ prompt_token_ids=tokenized_prompt,
+ images=image,
+ params=params,
+ lora_request=lora_request,
+ )
+ self.request_id += 1
+ return request
+
+ async def generate_async(
+ self, row: Dict[str, Any]
+ ) -> Tuple[vLLMEngineRequest, Dict[str, Any], float]:
+ """Process a single request.
+
+ Args:
+ request: The request.
+
+ Returns:
+ A tuple of index in batch, request output and bypassed custom fields, and time taken.
+ """
+ request = await self._prepare_llm_request(row)
+ t = time.perf_counter()
+
+ async with self.semaphore:
+ output = await self._generate_async(request)
+
+ time_taken = time.perf_counter() - t
+
+ output_data = vLLMOutputData.from_vllm_engine_output(output)
+ return request, output_data.model_dump(), time_taken
+
+ async def _generate_async(self, request: vLLMEngineRequest) -> Any:
+ """Process a single request.
+
+ Args:
+ request: The request.
+
+ Returns:
+ The output of the request.
+ """
+
+ # NOTE: vLLM v1 tighly couples tokenizer and detokenizer to the engine.
+ # We should investigate whether decoupling them could lead to better
+ # performance. Given that v1 tokenizer and detokenizer are already
+ # in a separate process, the benefit of decoupling them in the Processor
+ # may be limited.
+ assert request.prompt
+ import vllm
+
+ multi_modal_data = {"image": request.images} if request.images else None
+ llm_prompt = vllm.inputs.data.TextPrompt(
+ prompt=request.prompt, multi_modal_data=multi_modal_data
+ )
+
+ # Send the request to the LLM engine.
+ stream = self.engine.generate(
+ request_id=str(request.request_id),
+ prompt=llm_prompt,
+ sampling_params=request.params,
+ )
+
+ # Consume the stream until the request is finished.
+ async for request_output in stream:
+ if request_output.finished:
+ # Bypass the original full prompt.
+ request_output.prompt = request.prompt
+ return request_output
+
+ raise RuntimeError(
+ "[vLLM] The request is not finished. This should not happen. Please report this issue to the Ray team."
+ )
+
+ def shutdown(self):
+ """Shutdown the vLLM v1 engine. This kills child processes forked
+ by the vLLM engine. If not called, the child processes will be
+ orphaned and will not be killed when the parent process exits,
+ and they won't be able to be tracked by Ray anymore.
+ """
+ if hasattr(self.engine, "shutdown"):
+ logger.info("Shutting down vLLM engine")
+ self.engine.shutdown()
+
+ def get_scheduler_config(self):
+ return self._vllm_config.scheduler_config
+
+
+class vLLMEngineStageUDF(StatefulStageUDF):
+ def __init__(
+ self,
+ data_column: str,
+ expected_input_keys: List[str],
+ batch_size: int,
+ max_concurrent_batches: int,
+ model: str,
+ engine_kwargs: Dict[str, Any],
+ task_type: vLLMTaskType = vLLMTaskType.GENERATE,
+ max_pending_requests: Optional[int] = None,
+ dynamic_lora_loading_path: Optional[str] = None,
+ ):
+ """
+ Initialize the vLLMEngineStageUDF.
+
+ Args:
+ data_column: The data column name.
+ expected_input_keys: The expected input keys of the stage.
+ model: The model to use for the vLLM engine.
+ engine_kwargs: The kwargs to pass to the vLLM engine.
+ task_type: The task to use for the vLLM engine (e.g., "generate", "embed", etc).
+ max_pending_requests: The maximum number of pending requests. If None,
+ it will be set to 1.1 * max_num_seqs * pipeline_parallel_size.
+ dynamic_lora_loading_path: The path to the dynamic LoRA adapter. It is expected
+ to hold subfolders each for a different lora checkpoint.
+ """
+ super().__init__(data_column, expected_input_keys)
+ self.model = model
+
+ # Setup vLLM engine kwargs.
+ self.task_type = task_type
+ self.engine_kwargs = self.normalize_engine_kwargs(task_type, engine_kwargs)
+
+ # Set up the max pending requests.
+ pp_size = self.engine_kwargs.get("pipeline_parallel_size", 1)
+ self.max_pending_requests = max_pending_requests or math.ceil(
+ self.engine_kwargs.get("max_num_seqs", 128) * pp_size * 1.1
+ )
+ if self.max_pending_requests > 0:
+ logger.info("Max pending requests is set to %d", self.max_pending_requests)
+
+ exclude_safetensors = (
+ self.engine_kwargs.get("load_format") in STREAMING_LOAD_FORMATS
+ )
+ if exclude_safetensors:
+ logger.info("Excluding safetensors files when downloading the model.")
+ download_model = NodeModelDownloadable.EXCLUDE_SAFETENSORS
+ else:
+ logger.info("Downloading model and tokenizer.")
+ download_model = NodeModelDownloadable.MODEL_AND_TOKENIZER
+
+ # Download the model if needed.
+ model_source = download_model_files(
+ model_id=self.model,
+ mirror_config=None,
+ download_model=download_model,
+ download_extra_files=False,
+ )
+
+ # If we are using streaming load formats, we need to pass in self.model which is a remote cloud storage path.
+ source = model_source if not exclude_safetensors else self.model
+ self.llm = vLLMEngineWrapper(
+ model=self.model,
+ model_source=source,
+ idx_in_batch_column=self.IDX_IN_BATCH_COLUMN,
+ enable_log_requests=False,
+ max_pending_requests=self.max_pending_requests,
+ dynamic_lora_loading_path=dynamic_lora_loading_path,
+ **self.engine_kwargs,
+ )
+
+ max_num_seqs = self.llm.get_scheduler_config().max_num_seqs
+ if batch_size * max_concurrent_batches < max_num_seqs:
+ logger.warning(
+ f"The product of batch_size ({batch_size}) and "
+ f"max_concurrent_batches ({max_concurrent_batches}) is too small "
+ "to saturate vLLM engine. This may lead to suboptimal "
+ "throughput. Please increase max_concurrent_batches to at least "
+ f"{math.ceil(max_num_seqs / batch_size)}."
+ )
+
+ def normalize_engine_kwargs(
+ self,
+ task_type: vLLMTaskType,
+ engine_kwargs: Dict[str, Any],
+ ) -> Dict[str, Any]:
+ """
+ Normalize the engine kwargs.
+
+ Args:
+ task_type: The task to use for the vLLM engine (e.g., "generate", "embed", etc).
+ engine_kwargs: The kwargs to normalize.
+
+ Returns:
+ The normalized kwargs.
+ """
+ # Remove model from engine kwargs if set.
+ model = engine_kwargs.pop("model", None)
+ if model is not None and model != self.model:
+ logger.warning(
+ "The model set in engine kwargs (%s) is different from the "
+ "stage (%s). Please remove 'model' from engine kwargs.",
+ model,
+ self.model,
+ )
+
+ # Override the task if it is different from the stage.
+ task = vLLMTaskType(engine_kwargs.get("task", task_type))
+ if task != task_type:
+ logger.warning(
+ "The task set in engine kwargs (%s) is different from the "
+ "stage (%s). Overriding the task in engine kwargs to %s.",
+ task,
+ task_type,
+ task_type,
+ )
+ engine_kwargs["task"] = task_type
+ return engine_kwargs
+
+ async def udf(self, batch: List[Dict[str, Any]]) -> AsyncIterator[Dict[str, Any]]:
+ """Run the vLLM engine.
+
+ Args:
+ batch: A list of rows to run the vLLM engine on.
+
+ Returns:
+ The response of the vLLM engine.
+ """
+ batch_uuid = uuid.uuid4()
+ batch_start_time = time.perf_counter()
+
+ tasks = [asyncio.create_task(self.llm.generate_async(row)) for row in batch]
+
+ for resp in asyncio.as_completed(tasks):
+ request, output, time_taken_llm = await resp
+
+ yield {
+ **output,
+ "request_id": request.request_id,
+ self.IDX_IN_BATCH_COLUMN: request.idx_in_batch,
+ "batch_uuid": batch_uuid.hex,
+ "time_taken_llm": time_taken_llm,
+ "params": str(request.params),
+ }
+
+ batch_time_taken = time.perf_counter() - batch_start_time
+ # TODO: Add metrics to the UDf wrapper so that we don't need
+ # timer in UDFs anymore.
+ logger.info(
+ "[vLLM] Elapsed time for batch %s with size %d: %s",
+ batch_uuid.hex,
+ len(batch),
+ batch_time_taken,
+ )
+
+ # Log engine stats after each batch is done conditioned on the flag
+ # passed to the engine.
+ if not self.engine_kwargs.get("disable_log_stats", False):
+ await self.llm.engine.do_log_stats()
+
+ def __del__(self):
+ if hasattr(self, "llm"):
+ # Kill the engine processes.
+ self.llm.shutdown()
+
+
+def _ray_scheduling_strategy_fn(
+ num_bundles_per_replica: int,
+ accelerator_type: Optional[str] = None,
+ placement_group_config: Optional[Dict[str, Any]] = None,
+):
+ """Create a Ray scheduling strategy for the engine.
+
+ Args:
+ num_bundles_per_replica: The number of device bundles per
+ engine replica.
+ accelerator_type: The accelerator type. If None, the
+ accelerator_type label will not be set.
+ placement_group_config: The custom placement group configuration.
+ If None, we use the default placement group configuration.
+
+ Returns:
+ The Ray scheduling strategy.
+ """
+
+ def _get_bundle() -> Dict[str, float]:
+ # GPU bundles
+ bundle = {"GPU": 1, "CPU": 1}
+
+ # Accelerator type
+ if accelerator_type:
+ bundle[f"accelerator_type:{accelerator_type}"] = 0.001
+ return bundle
+
+ if placement_group_config:
+ placement_group_config = copy.deepcopy(placement_group_config)
+
+ if accelerator_type:
+ for bundle in placement_group_config["bundles"]:
+ bundle[f"accelerator_type:{accelerator_type}"] = 0.001
+
+ pg = ray.util.placement_group(**placement_group_config)
+ else:
+ pg = ray.util.placement_group(
+ [_get_bundle()] * num_bundles_per_replica,
+ strategy="PACK",
+ )
+ return dict(
+ scheduling_strategy=PlacementGroupSchedulingStrategy(
+ pg, placement_group_capture_child_tasks=True
+ )
+ )
+
+
+class vLLMEngineStage(StatefulStage):
+ """
+ A stage that runs vLLM engine.
+ """
+
+ fn: Type[StatefulStageUDF] = vLLMEngineStageUDF
+
+ @root_validator(pre=True)
+ def post_init(cls, values):
+ """Post-initialize the stage. Specifically,
+ this function determines the num_gpus and Ray remote args
+ for the .map_batches() call in this stage.
+
+ Args:
+ values: The raw stage values.
+ Returns:
+ The updated values.
+ """
+ map_batches_kwargs = values["map_batches_kwargs"]
+ accelerator_type = map_batches_kwargs.get("accelerator_type", "")
+ fn_constructor_kwargs = values["fn_constructor_kwargs"]
+ engine_kwargs = fn_constructor_kwargs.get("engine_kwargs", {})
+
+ ray_remote_args = {}
+ if accelerator_type:
+ ray_remote_args["accelerator_type"] = accelerator_type
+
+ # Setup num_workers required per vLLM engine.
+ tp_size = engine_kwargs.get("tensor_parallel_size", 1)
+ pp_size = engine_kwargs.get("pipeline_parallel_size", 1)
+ num_bundles_per_replica = tp_size * pp_size
+
+ # Use the MP backend by default.
+ engine_kwargs.setdefault("distributed_executor_backend", "mp")
+ executor_backend = engine_kwargs.get("distributed_executor_backend")
+
+ # When Ray is used in the vLLM engine, we set num_devices to 0 so that
+ # Ray Data won't reserve GPUs in advance. Instead, we specify scheduling
+ # strategy in .map_batches() arguments and let vLLM Ray executor to
+ # create placement groups for each TP/PP worker.
+ placement_group_config = fn_constructor_kwargs.pop(
+ "placement_group_config", None
+ )
+ if executor_backend == "ray":
+ # Note that we have to use partial() to pass a function
+ # instead of an object.
+ map_batches_kwargs["ray_remote_args_fn"] = partial(
+ _ray_scheduling_strategy_fn,
+ num_bundles_per_replica,
+ accelerator_type,
+ placement_group_config,
+ )
+ ray_remote_args["num_gpus"] = 0
+ else:
+ if not placement_group_config:
+ # Default to GPUs per bundle if placement group is not specified.
+ ray_remote_args["num_gpus"] = num_bundles_per_replica
+ else:
+ bundles = placement_group_config["bundles"]
+ resource_counter = Counter()
+ for bundle in bundles:
+ resource_counter.update(bundle)
+
+ total_cpus = resource_counter.pop("CPU", 0)
+ total_gpus = resource_counter.pop("GPU", 0)
+
+ # Ray Data expects CPU/GPU to be specified via num_cpus/num_gpus,
+ # not inside the resources dict.
+ if total_cpus:
+ ray_remote_args["num_cpus"] = total_cpus
+ if total_gpus:
+ ray_remote_args["num_gpus"] = total_gpus
+
+ # Keep only non-CPU/GPU custom resources, if any.
+ if resource_counter:
+ ray_remote_args["resources"] = dict(resource_counter)
+
+ map_batches_kwargs.update(ray_remote_args)
+ return values
+
+ def get_required_input_keys(self) -> Dict[str, str]:
+ """The required input keys of the stage and their descriptions."""
+ ret = {"prompt": "The text prompt (str)."}
+ task_type = self.fn_constructor_kwargs.get("task_type", vLLMTaskType.GENERATE)
+ if task_type == vLLMTaskType.GENERATE:
+ ret["sampling_params"] = (
+ "The sampling parameters. See "
+ "https://docs.vllm.ai/en/latest/api/inference_params.html#sampling-parameters "
+ "for details."
+ )
+ return ret
+
+ def get_optional_input_keys(self) -> Dict[str, str]:
+ """The optional input keys of the stage and their descriptions."""
+ return {
+ "tokenized_prompt": "The tokenized prompt. If provided, the prompt will not be tokenized by the vLLM engine.",
+ "image": "The image(s) for multimodal input. Accepts a single image or list of images.",
+ "model": "The model to use for this request. If the model is different from the "
+ "model set in the stage, then this is a LoRA request.",
+ }
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/batch/utils.py b/lib/python3.12/site-packages/ray/llm/_internal/batch/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..5cca5d3dd880cfaa635b40d2cfa43219b9c9c53c
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/batch/utils.py
@@ -0,0 +1,62 @@
+"""Utility functions for batch processing."""
+import logging
+from typing import TYPE_CHECKING, Any, Union
+
+if TYPE_CHECKING:
+ from transformers import PreTrainedTokenizer, PreTrainedTokenizerFast
+
+AnyTokenizer = Union["PreTrainedTokenizer", "PreTrainedTokenizerFast", Any]
+
+logger = logging.getLogger(__name__)
+
+
+def get_cached_tokenizer(tokenizer: AnyTokenizer) -> AnyTokenizer:
+ """Get tokenizer with cached properties.
+ This will patch the tokenizer object in place.
+ By default, transformers will recompute multiple tokenizer properties
+ each time they are called, leading to a significant slowdown. This
+ function caches these properties for faster access.
+ Args:
+ tokenizer: The tokenizer object.
+ Returns:
+ The patched tokenizer object.
+ """
+ chat_template = getattr(tokenizer, "chat_template", None)
+ # For VLM, the text tokenizer is wrapped by a processor.
+ if hasattr(tokenizer, "tokenizer"):
+ tokenizer = tokenizer.tokenizer
+ # Some VLM's tokenizer has chat_template attribute (e.g. Qwen/Qwen2-VL-7B-Instruct),
+ # however some other VLM's tokenizer does not have chat_template attribute (e.g.
+ # mistral-community/pixtral-12b). Therefore, we cache the processor's chat_template.
+ if chat_template is None:
+ chat_template = getattr(tokenizer, "chat_template", None)
+
+ tokenizer_all_special_ids = set(tokenizer.all_special_ids)
+ tokenizer_all_special_tokens_extended = tokenizer.all_special_tokens_extended
+ tokenizer_all_special_tokens = set(tokenizer.all_special_tokens)
+ tokenizer_len = len(tokenizer)
+
+ class CachedTokenizer(tokenizer.__class__): # type: ignore
+ @property
+ def all_special_ids(self):
+ return tokenizer_all_special_ids
+
+ @property
+ def all_special_tokens(self):
+ return tokenizer_all_special_tokens
+
+ @property
+ def all_special_tokens_extended(self):
+ return tokenizer_all_special_tokens_extended
+
+ @property
+ def chat_template(self):
+ return chat_template
+
+ def __len__(self):
+ return tokenizer_len
+
+ CachedTokenizer.__name__ = f"Cached{tokenizer.__class__.__name__}"
+
+ tokenizer.__class__ = CachedTokenizer
+ return tokenizer
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..7a44581bf5f39a2b7c3307863450d512c8867f7e
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/__init__.py
@@ -0,0 +1,14 @@
+from ray.llm._internal.common.observability.logging_utils import (
+ disable_vllm_custom_ops_logger_on_cpu_nodes,
+)
+from ray.llm._internal.serve.observability import setup_observability
+
+# Set up observability
+disable_vllm_custom_ops_logger_on_cpu_nodes()
+setup_observability()
+
+
+def _worker_process_setup_hook():
+ """Noop setup hook used for ENABLE_WORKER_PROCESS_SETUP_HOOK
+ (see python/ray/llm/_internal/serve/configs/constants.py)."""
+ pass
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/base_configs/templates/default_deployment_configs.yaml b/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/base_configs/templates/default_deployment_configs.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..66da8729b54986d4389009d47940dfbe0321a721
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/base_configs/templates/default_deployment_configs.yaml
@@ -0,0 +1,83 @@
+model_id_to_gpu_deployment_configs:
+ meta-llama/Meta-Llama-3.1-8B-Instruct:
+ A10G: &base_llama_31_8b_config
+ model_loading_config:
+ model_id: meta-llama/Meta-Llama-3.1-8B-Instruct
+ engine_kwargs:
+ # Llama 3.1's context length is 128k, but we set a lower one to avoid GPU OOM.
+ max_model_len: 8192
+ max_num_batched_tokens: 2048
+ tensor_parallel_size: 1
+ enable_chunked_prefill: true
+ L4: *base_llama_31_8b_config
+ L40S: *base_llama_31_8b_config
+ A100-40G: *base_llama_31_8b_config
+ A100-80G: *base_llama_31_8b_config
+ H100: *base_llama_31_8b_config
+
+ meta-llama/Meta-Llama-3.1-70B-Instruct:
+ A100-40G:
+ model_loading_config: &meta_llama_31_70b_model_loading_config
+ model_id: meta-llama/Meta-Llama-3.1-70B-Instruct
+ engine_kwargs:
+ # Llama 3.1's context length is 128k, but we set a lower one to avoid GPU OOM.
+ max_model_len: 8192
+ max_num_batched_tokens: 2048
+ tensor_parallel_size: 8
+ enable_chunked_prefill: true
+ A100-80G:
+ model_loading_config: *meta_llama_31_70b_model_loading_config
+ engine_kwargs:
+ # Llama 3.1's context length is 128k, but we set a lower one to avoid GPU OOM.
+ max_model_len: 8192
+ max_num_batched_tokens: 2048
+ tensor_parallel_size: 4
+ enable_chunked_prefill: true
+ H100:
+ model_loading_config: *meta_llama_31_70b_model_loading_config
+ engine_kwargs:
+ # Llama 3.1's context length is 128k, but we set a lower one to avoid GPU OOM.
+ max_model_len: 8192
+ max_num_batched_tokens: 2048
+ tensor_parallel_size: 4
+ enable_chunked_prefill: true
+
+ meta-llama/Meta-Llama-3.1-405B-Instruct-FP8:
+ H100:
+ model_loading_config: &meta_llama_31_405b_model_loading_config
+ model_id: meta-llama/Meta-Llama-3.1-405B-Instruct-FP8
+ engine_kwargs:
+ # Llama 3.1's context length is 128k, but we set a lower one to avoid GPU OOM.
+ max_model_len: 8192
+ max_num_batched_tokens: 2048
+ tensor_parallel_size: 8
+ enable_chunked_prefill: true
+
+ mistral-community/pixtral-12b:
+ L40S: &pixtral_base_12b_config
+ model_loading_config: &pixtral_base_12b_model_loading_config
+ model_id: mistral-community/pixtral-12b
+ engine_kwargs:
+ tensor_parallel_size: 1
+ A100-40G: *pixtral_base_12b_config
+ A100-80G: *pixtral_base_12b_config
+ H100: *pixtral_base_12b_config
+
+ meta-llama/Llama-3.2-11B-Vision-Instruct:
+ L40S: &llama_32_base_11b_config
+ model_loading_config: &llama_32_base_11b_model_loading_config
+ model_id: meta-llama/Llama-3.2-11B-Vision-Instruct
+ engine_kwargs:
+ tensor_parallel_size: 1
+ A100-40G: *llama_32_base_11b_config
+ A100-80G: *llama_32_base_11b_config
+ H100: *llama_32_base_11b_config
+
+ meta-llama/Llama-3.2-90B-Vision-Instruct:
+ A100-40G: &llama_32_base_90b_config
+ model_loading_config: &llama_32_base_90b_model_loading_config
+ model_id: meta-llama/Llama-3.2-90B-Vision-Instruct
+ engine_kwargs:
+ tensor_parallel_size: 1
+ A100-80G: *llama_32_base_90b_config
+ H100: *llama_32_base_90b_config
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/generator.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/generator.py
new file mode 100644
index 0000000000000000000000000000000000000000..2b00f9f85417b05b0d1fe6707571ffa1444ecfae
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/generator.py
@@ -0,0 +1,124 @@
+import copy
+import os
+from typing import Any, Dict
+
+import yaml
+
+from ray.llm._internal.serve.config_generator.utils.constants import TEMPLATE_DIR
+from ray.llm._internal.serve.config_generator.utils.models import (
+ TextCompletionModelConfig,
+)
+from ray.llm._internal.serve.config_generator.utils.overrides import (
+ ask_and_merge_model_overrides,
+)
+from ray.llm._internal.serve.config_generator.utils.text_completion import (
+ populate_text_completion_model_config,
+)
+from ray.serve._private.constants import DEFAULT_TARGET_ONGOING_REQUESTS
+from ray.serve.config import AutoscalingConfig
+
+
+def get_model_base_config(
+ input_model_config: TextCompletionModelConfig,
+) -> Dict[str, Any]:
+ """
+ This method returns the base model config based on user inputs.
+ """
+ return populate_text_completion_model_config(input_model_config)
+
+
+def get_model_config(input_model_config: TextCompletionModelConfig) -> Dict[str, Any]:
+ """
+ This method decorates the base model config based on serving type, such as Lora or function calling.
+ """
+
+ model_config = get_model_base_config(input_model_config)
+
+ model_config = maybe_get_lora_enabled_config(
+ input_model_config,
+ model_config,
+ )
+
+ return model_config
+
+
+def get_serve_config(
+ model_config_path: str,
+):
+ """
+ This util method constructs the final serve config.
+
+ It doesn't inline the model config and, instead, just puts the file path based on the type of models.
+ """
+ serve_config = get_serve_base_config()
+ serve_config["applications"][0]["args"]["llm_configs"] = [model_config_path]
+ return serve_config
+
+
+def override_model_configs(base_model_config: Dict[str, Any]) -> Dict[str, Any]:
+ existing_autoscaling_config = base_model_config["deployment_config"].setdefault(
+ "autoscaling_config", {}
+ )
+ # Getting the default target ongoing requests from the base model config.
+ # If not present, default to 2 (same as Ray Serve's default).
+ default_ongoing_requests = (
+ base_model_config.get("deployment_config", {})
+ .get("autoscaling_config", {})
+ .get("target_ongoing_requests", DEFAULT_TARGET_ONGOING_REQUESTS)
+ )
+ final_autoscaling_config = ask_and_merge_model_overrides(
+ existing_configs=existing_autoscaling_config,
+ model_cls=AutoscalingConfig,
+ fields=["min_replicas", "max_replicas", "target_ongoing_requests"],
+ defaults={"target_ongoing_requests": default_ongoing_requests},
+ )
+ final_autoscaling_config["initial_replicas"] = final_autoscaling_config[
+ "min_replicas"
+ ]
+ base_model_config["deployment_config"][
+ "autoscaling_config"
+ ] = final_autoscaling_config
+
+ # Make sure max_ongoing request is always double of target_ongoing_requests
+ base_model_config["deployment_config"]["max_ongoing_requests"] = (
+ 2 * final_autoscaling_config["target_ongoing_requests"]
+ )
+
+ return base_model_config
+
+
+def maybe_get_lora_enabled_config(
+ input_model_config: TextCompletionModelConfig,
+ base_config: Dict[str, Any],
+):
+ """
+ If the input model contains lora config, this method decorates the base
+ config with lora-specific configurations.
+ """
+ if input_model_config.lora_config:
+ max_num_adapters_per_replica = (
+ input_model_config.lora_config.max_num_lora_per_replica
+ )
+
+ res = copy.deepcopy(base_config)
+ res["lora_config"] = {
+ "dynamic_lora_loading_path": input_model_config.lora_config.uri,
+ "max_num_adapters_per_replica": max_num_adapters_per_replica,
+ }
+
+ res.setdefault("engine_kwargs", {})["enable_lora"] = True
+ res.setdefault("engine_kwargs", {})["max_lora_rank"] = 32
+ res.setdefault("engine_kwargs", {})["max_loras"] = max_num_adapters_per_replica
+ return res
+ else:
+ return base_config
+
+
+_BASE_SERVE_CONFIG = "base_serve_config.yaml"
+
+
+def get_serve_base_config() -> Dict[str, Any]:
+ # Construct the full path to the YAML file
+ file_path = os.path.join(TEMPLATE_DIR, _BASE_SERVE_CONFIG)
+ with open(file_path, "r") as stream:
+ return yaml.safe_load(stream)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/inputs.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/inputs.py
new file mode 100644
index 0000000000000000000000000000000000000000..706fbb2502c7a1eeba67e24d537a732ac21545d9
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/inputs.py
@@ -0,0 +1,220 @@
+from typing import List, Optional
+
+from huggingface_hub import repo_info
+from pydantic import BaseModel
+from rich import print
+from rich.prompt import Confirm, Prompt
+
+from ray.llm._internal.serve.config_generator.utils.constants import (
+ get_lora_storage_uri,
+)
+from ray.llm._internal.serve.config_generator.utils.files import (
+ clear_hf_token,
+ get_hf_token,
+)
+from ray.llm._internal.serve.config_generator.utils.gpu import (
+ DEFAULT_MODEL_ID_TO_GPU,
+ GPUType,
+ get_available_gpu_types,
+)
+from ray.llm._internal.serve.config_generator.utils.input_converter import (
+ convert_inputs_to_text_completion_model,
+)
+from ray.llm._internal.serve.config_generator.utils.models import (
+ TEXT_COMPLETION_MODEL_TYPE,
+ ModelType,
+ TextCompletionLoraModelConfig,
+ TextCompletionModelConfig,
+)
+from ray.llm._internal.serve.config_generator.utils.prompt import (
+ BoldIntPrompt,
+ BoldPrompt,
+)
+from ray.llm._internal.serve.config_generator.utils.text_completion import (
+ get_default_llm_config,
+)
+
+
+def _get_user_input_from_list(prompt: str, options: List[GPUType]):
+ while True:
+ user_input = BoldPrompt.ask(
+ f"{prompt}",
+ choices=[option.value for option in options],
+ )
+ for option in options:
+ if user_input.lower() == option.value.lower():
+ return option
+ print(f"Please enter a valid option from {options}")
+
+
+class BaseModelInfo(BaseModel):
+ id: str
+ hf_token: Optional[str] = None
+ type: ModelType
+ import_model_storage_uri: Optional[str] = None
+ reference_model_id: Optional[str] = None
+
+
+def _get_user_input_from_lists(
+ prompt: str, options: List[str], allow_any_user_input: bool
+):
+ while True:
+ res = Prompt.ask(prompt)
+ if allow_any_user_input or res in options:
+ return res
+ else:
+ print("Please enter a valid option.")
+
+
+def _get_model_id():
+ model_list = list(DEFAULT_MODEL_ID_TO_GPU.keys())
+ model_ids = "\n".join(model_list)
+ prompt_message = f"""[bold]We have provided the defaults for the following models[not bold]:
+{model_ids}
+[bold]Please enter the model ID you would like to serve, or enter your own custom model ID. For multi-lora serving, enter only the base model ID"""
+
+ model_id = _get_user_input_from_lists(
+ prompt_message, model_list, allow_any_user_input=True
+ )
+ return model_id
+
+
+def _get_validated_model_info():
+ """
+ If the model is supposed to be loaded from huggingface, we make an API call to confirm that
+ user's credentials work.
+ """
+ while True:
+ ref_model_id = None
+
+ model_id = _get_model_id()
+ model_type = TEXT_COMPLETION_MODEL_TYPE
+ if model_id not in DEFAULT_MODEL_ID_TO_GPU:
+ ref_model_id = "other"
+ import_model = Confirm.ask(
+ "Do you have the model weights stored in your cloud buckets? "
+ "If not, model weights will be downloaded from HuggingFace.",
+ default=False,
+ )
+
+ if import_model:
+ # No need to validate since the model is loaded from remote uri
+ remote_storage_uri = BoldPrompt.ask("Storage uri of your model: ")
+ return BaseModelInfo(
+ id=model_id,
+ hf_token=None,
+ type=model_type,
+ import_model_storage_uri=remote_storage_uri,
+ reference_model_id=ref_model_id,
+ )
+
+ hf_token = get_hf_token()
+
+ # Need to validate
+ try:
+ repo_info(model_id, token=hf_token)
+ print(
+ f"Verified that you can access {model_id} with your HuggingFace token."
+ )
+ return BaseModelInfo(
+ id=model_id,
+ hf_token=hf_token,
+ type=model_type,
+ reference_model_id=ref_model_id,
+ )
+ except Exception as e:
+ print(f"An error occurred: {e}")
+ # Clear cached tf token in case it was incorrectly entered
+ clear_hf_token()
+
+ print(
+ "\nUnable to access the HuggingFace repo based on the "
+ "provided information. Please confirm that you have access "
+ "to the model and try again."
+ )
+
+
+def _get_default_tensor_parallelism(model_id: str, gpu_type: GPUType) -> int:
+ if model_id in DEFAULT_MODEL_ID_TO_GPU:
+ llm_config = get_default_llm_config(model_id=model_id, gpu_type=gpu_type)
+ return llm_config.engine_kwargs.setdefault("tensor_parallel_size", 1)
+
+ return 1
+
+
+_DEFAULT_NUM_LORA_PER_REPLICA = 16
+
+
+def get_input_model_via_args(
+ model_id: str,
+ hf_token: Optional[str],
+ gpu_type: GPUType,
+ tensor_parallelism: int,
+ enable_lora: Optional[bool],
+ num_loras_per_replica: Optional[int],
+) -> TextCompletionModelConfig:
+ if enable_lora:
+ max_num_lora_per_replica = (
+ num_loras_per_replica or _DEFAULT_NUM_LORA_PER_REPLICA
+ )
+ default_lora_uri = get_lora_storage_uri()
+ lora_config = TextCompletionLoraModelConfig(
+ max_num_lora_per_replica=max_num_lora_per_replica,
+ uri=default_lora_uri,
+ )
+ else:
+ lora_config = None
+
+ return convert_inputs_to_text_completion_model(
+ model_id=model_id,
+ hf_token=hf_token,
+ gpu_type=gpu_type,
+ tensor_parallelism=tensor_parallelism,
+ lora_config=lora_config,
+ )
+
+
+def get_input_model_via_interactive_inputs() -> TextCompletionModelConfig:
+ """
+ This method constructs a corresponding model object based on user inputs.
+
+ This model object should be used to construct the final serve config.
+ """
+ base_model_info = _get_validated_model_info()
+
+ available_gpu_types = get_available_gpu_types(base_model_info.id)
+ gpu_type = _get_user_input_from_list("GPU type", available_gpu_types)
+
+ default_tensor_parallelism = _get_default_tensor_parallelism(
+ base_model_info.id, gpu_type=gpu_type
+ )
+
+ tensor_parallelism = BoldIntPrompt.ask(
+ "Tensor parallelism", default=default_tensor_parallelism
+ )
+
+ is_lora_enabled = Confirm.ask("[bold]Enable LoRA serving", default=False)
+
+ if is_lora_enabled:
+ default_lora_uri = get_lora_storage_uri()
+ max_num_lora_per_replica = BoldIntPrompt.ask(
+ "Maximum number of LoRA models per replica",
+ default=_DEFAULT_NUM_LORA_PER_REPLICA,
+ )
+ lora_uri = BoldPrompt.ask("LoRA storage uri", default=default_lora_uri)
+ lora_config = TextCompletionLoraModelConfig(
+ max_num_lora_per_replica=max_num_lora_per_replica,
+ uri=lora_uri,
+ )
+ else:
+ lora_config = None
+
+ return convert_inputs_to_text_completion_model(
+ model_id=base_model_info.id,
+ hf_token=base_model_info.hf_token,
+ remote_storage_uri=base_model_info.import_model_storage_uri,
+ gpu_type=gpu_type,
+ tensor_parallelism=tensor_parallelism,
+ lora_config=lora_config,
+ reference_model_id=base_model_info.reference_model_id,
+ )
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/start.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/start.py
new file mode 100644
index 0000000000000000000000000000000000000000..97c8e6d512669abd02ede09c30af92431f1674bb
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/config_generator/start.py
@@ -0,0 +1,224 @@
+import datetime
+import os
+from typing import Any, Dict, Optional
+
+import typer
+import yaml
+from rich import print
+from rich.prompt import Confirm
+from typing_extensions import Annotated
+
+from ray.llm._internal.serve.config_generator.generator import (
+ get_model_config,
+ get_serve_config,
+ override_model_configs,
+)
+from ray.llm._internal.serve.config_generator.inputs import (
+ get_input_model_via_args,
+ get_input_model_via_interactive_inputs,
+)
+from ray.llm._internal.serve.config_generator.utils.gpu import (
+ DEFAULT_MODEL_ID_TO_GPU,
+ GPUType,
+)
+
+
+def _format_yaml_dumper():
+ """Changes the YAML dumper's format."""
+
+ # Prompt formats often contain newline characters. We modify the YAML
+ # dumper here, so it prints newline characters instead of actual newlines.
+ def newline_representer(dumper, data):
+ if "\n" in data:
+ # Only adjust the dumper if a newline is present.
+ return dumper.represent_scalar("tag:yaml.org,2002:str", data, style='"')
+ else:
+ # Use the default dumper otherwise.
+ return dumper.represent_scalar("tag:yaml.org,2002:str", data)
+
+ yaml.SafeDumper.add_representer(str, newline_representer)
+
+
+def _write_model_config_to_disk(
+ model_config: Dict[str, Any], model_id: str, timestamp: str
+) -> str:
+ # Set the filename with prefix, timestamp, and suffix
+ sanitized_model_id = model_id.replace("/", "--").replace(".", "_")
+ file_name = f"{sanitized_model_id}_{timestamp}.yaml"
+ file_path = "./model_config" # Directory where the file will be saved
+
+ os.makedirs(file_path, exist_ok=True)
+
+ # Create the full path for the file
+ full_file_path = f"{file_path}/{file_name}"
+
+ # Dump the configuration to the file
+ with open(full_file_path, "w+") as file:
+ yaml.safe_dump(model_config, file)
+ return full_file_path
+
+
+def _write_config_to_disk(serve_config: Dict[str, Any], timestamp: str):
+ # Set the filename with prefix, timestamp, and suffix
+ file_name = f"serve_{timestamp}.yaml"
+ file_path = "./" # Directory where the file will be saved
+
+ # Create the full path for the file
+ full_file_path = f"{file_path}{file_name}"
+
+ # Dump the configuration to the file
+ with open(full_file_path, "w+") as file:
+ yaml.safe_dump(serve_config, file)
+
+ return full_file_path
+
+
+def _get_model_with_validation(
+ model_id: Optional[str],
+ hf_token: Optional[str],
+ gpu_type: Optional[GPUType],
+ tensor_parallelism: Optional[int],
+ enable_lora: Optional[bool],
+ num_loras_per_replica: Optional[int],
+):
+ if model_id and model_id not in DEFAULT_MODEL_ID_TO_GPU:
+ raise RuntimeError(
+ "In non-interactive CLI mode, please only specify model ID whose configs are provided by default."
+ )
+
+ if model_id and gpu_type and tensor_parallelism:
+ return get_input_model_via_args(
+ model_id,
+ hf_token=hf_token,
+ gpu_type=gpu_type,
+ tensor_parallelism=tensor_parallelism,
+ enable_lora=enable_lora,
+ num_loras_per_replica=num_loras_per_replica,
+ )
+ else:
+ raise RuntimeError(
+ "If specifying args, please make sure all arguments are specified."
+ )
+
+
+def gen_config(
+ model_id: Annotated[
+ Optional[str],
+ typer.Option(
+ help="HuggingFace model ID to download and run",
+ hidden=True,
+ ),
+ ] = None,
+ hf_token: Annotated[
+ Optional[str], typer.Option(help="Huggingface token", hidden=True)
+ ] = None,
+ gpu_type: Annotated[
+ Optional[GPUType], typer.Option(help="Type of GPU", hidden=True)
+ ] = None,
+ tensor_parallelism: Annotated[
+ Optional[int], typer.Option(help="Number of tensor parallelism", hidden=True)
+ ] = None,
+ enable_lora: Annotated[
+ Optional[bool], typer.Option(help="Whether to enable LoRA serving", hidden=True)
+ ] = False,
+ # TODO (shrekris): Expose these hidden options after the API stabilizes.
+ external_model_id: Annotated[
+ Optional[str],
+ typer.Option(
+ help="[EXPERIMENTAL] Model ID to expose to end users. Defaults to model_id.",
+ hidden=True,
+ ),
+ ] = None,
+ override_lora_uri: Annotated[
+ Optional[str],
+ typer.Option(help="[EXPERIMENTAL] URI to LoRA weights", hidden=True),
+ ] = None,
+ num_loras_per_replica: Annotated[
+ Optional[int],
+ typer.Option(
+ help="[EXPERIMENTAL] Number of LoRA weights per replica", hidden=True
+ ),
+ ] = None,
+ enable_chunk_prefill: Annotated[
+ Optional[bool],
+ typer.Option(
+ "--enable-chunked-prefill/--disable-chunked-prefill",
+ help="Whether to enable chunked prefill",
+ hidden=True,
+ ),
+ ] = None,
+ max_num_batched_tokens: Annotated[
+ Optional[int],
+ typer.Option(help="Max Number of batched tokens", hidden=True),
+ ] = None,
+):
+ """Starts an interactive session to generate RayLLM YAML configs."""
+
+ if (override_lora_uri or num_loras_per_replica) and not enable_lora:
+ raise ValueError(
+ "--enable-lora is not set. --enable-lora must be set "
+ "to use --override-lora-uri or --num-loras-per-replica."
+ )
+
+ if model_id or gpu_type or tensor_parallelism:
+ interactive_mode = False
+ input_model_config = _get_model_with_validation(
+ model_id=model_id,
+ hf_token=hf_token,
+ gpu_type=gpu_type,
+ tensor_parallelism=tensor_parallelism,
+ enable_lora=enable_lora,
+ num_loras_per_replica=num_loras_per_replica,
+ )
+ else:
+ # sets up conditional vars for the interactive mode to not skip prompts
+ interactive_mode = True
+ input_model_config = get_input_model_via_interactive_inputs()
+
+ model_config = get_model_config(input_model_config)
+ if interactive_mode and Confirm.ask(
+ "[bold]Further customize the auto-scaling config", default=False
+ ):
+ model_config = override_model_configs(model_config)
+
+ if external_model_id:
+ model_config["model_loading_config"]["model_id"] = external_model_id
+
+ if override_lora_uri:
+ model_config["lora_config"]["dynamic_lora_loading_path"] = override_lora_uri
+
+ if enable_chunk_prefill is not None:
+ model_config["engine_kwargs"]["enable_chunked_prefill"] = enable_chunk_prefill
+
+ if max_num_batched_tokens is not None:
+ model_config["engine_kwargs"]["max_num_batched_tokens"] = max_num_batched_tokens
+
+ timestamp = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
+
+ _format_yaml_dumper()
+
+ # Write the model config file to disk
+ model_config_path = _write_model_config_to_disk(
+ model_config, input_model_config.id, timestamp
+ )
+ serve_config = get_serve_config(model_config_path)
+
+ full_file_path = _write_config_to_disk(serve_config, timestamp)
+
+ print(
+ f"\nYour serve configuration file is successfully written to {full_file_path}\n"
+ )
+
+ if input_model_config.id not in DEFAULT_MODEL_ID_TO_GPU:
+ print(
+ f"Note: we don't have serving defaults for {input_model_config.id} "
+ "and we generally recommend you going over engine_kwargs in the yaml before serving. "
+ )
+
+ # Define the serve run command
+ command_str = f"serve run {full_file_path}"
+ print(f"Start the RayLLM app on this workspace by running: {command_str}")
+
+
+if __name__ == "__main__":
+ typer.run(gen_config)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/constants.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/constants.py
new file mode 100644
index 0000000000000000000000000000000000000000..68307ca54a67cdcb897220c599951bf54cf540de
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/constants.py
@@ -0,0 +1,103 @@
+import os
+
+ALLOW_NEW_PLACEMENT_GROUPS_IN_DEPLOYMENT = int(
+ os.getenv("RAYLLM_ALLOW_NEW_PLACEMENT_GROUPS_IN_DEPLOYMENT", "1")
+)
+
+
+# Timeout before download in multiplex deployment fails. <=0 means no timeout.
+DEFAULT_MULTIPLEX_DOWNLOAD_TIMEOUT_S = float(
+ os.getenv("DEFAULT_MULTIPLEX_DOWNLOAD_TIMEOUT_S", "30")
+)
+if DEFAULT_MULTIPLEX_DOWNLOAD_TIMEOUT_S <= 0:
+ DEFAULT_MULTIPLEX_DOWNLOAD_TIMEOUT_S = None
+
+
+# Number of retries for downloading a model in multiplex deployment.
+DEFAULT_MULTIPLEX_DOWNLOAD_TRIES = int(
+ os.getenv("DEFAULT_MULTIPLEX_DOWNLOAD_RETRIES", "3")
+)
+
+
+# If true, a default runtime_env will be injected to import rayllm on worker startup.
+# This is a startup time optimization to avoid the latency penalty of sequentially
+# importing rayllm in multiple layers of worker processes.
+ENABLE_WORKER_PROCESS_SETUP_HOOK = (
+ os.environ.get("RAYLLM_ENABLE_WORKER_PROCESS_SETUP_HOOK", "1") == "1"
+)
+
+
+CLOUD_OBJECT_MISSING_EXPIRE_S = 30
+CLOUD_OBJECT_EXISTS_EXPIRE_S = 60 * 60
+
+# Sentinel object used to indicate that a LoRA adapter config file is missing.
+LORA_ADAPTER_CONFIG_NAME = "adapter_config.json"
+
+DEFAULT_HEALTH_CHECK_PERIOD_S = int(
+ os.getenv("RAY_SERVE_LLM_DEFAULT_HEALTH_CHECK_PERIOD_S", "10")
+)
+DEFAULT_HEALTH_CHECK_TIMEOUT_S = int(
+ os.getenv("RAY_SERVE_LLM_DEFAULT_HEALTH_CHECK_TIMEOUT_S", "10")
+)
+DEFAULT_MAX_ONGOING_REQUESTS = int(
+ os.getenv("RAY_SERVE_LLM_DEFAULT_MAX_ONGOING_REQUESTS", str(int(1e9)))
+)
+DEFAULT_MAX_REPLICAS = int(os.getenv("RAY_SERVE_LLM_DEFAULT_MAX_REPLICAS", "10"))
+DEFAULT_MAX_TARGET_ONGOING_REQUESTS = int(
+ os.getenv("RAY_SERVE_LLM_DEFAULT_MAX_TARGET_ONGOING_REQUESTS", str(int(1e9)))
+)
+
+
+ENGINE_START_TIMEOUT_S = int(os.getenv("RAYLLM_ENGINE_START_TIMEOUT_S", str(60 * 60)))
+
+MIN_NUM_TOPLOGPROBS_ALLOWED = 0
+MAX_NUM_TOPLOGPROBS_ALLOWED = 5
+MODEL_RESPONSE_BATCH_TIMEOUT_MS = float(
+ os.getenv("RAYLLM_MODEL_RESPONSE_BATCH_TIMEOUT_MS", "50")
+)
+RAYLLM_ENABLE_REQUEST_PROMPT_LOGS = (
+ os.environ.get("RAYLLM_ENABLE_REQUEST_PROMPT_LOGS", "1") == "1"
+)
+RAYLLM_GUIDED_DECODING_BACKEND = os.environ.get(
+ "RAYLLM_GUIDED_DECODING_BACKEND", "xgrammar"
+)
+
+MAX_NUM_STOPPING_SEQUENCES = int(os.getenv("RAYLLM_MAX_NUM_STOPPING_SEQUENCES", "8"))
+ENV_VARS_TO_PROPAGATE = {
+ "HUGGING_FACE_HUB_TOKEN",
+ "HF_TOKEN",
+}
+# timeout in 10 minutes. Streaming can take longer than 3 min
+DEFAULT_LLM_ROUTER_HTTP_TIMEOUT = float(
+ os.environ.get("RAY_SERVE_LLM_ROUTER_HTTP_TIMEOUT", 600)
+)
+
+ENABLE_VERBOSE_TELEMETRY = bool(int(os.getenv("RAYLLM_ENABLE_VERBOSE_TELEMETRY", "0")))
+
+RAYLLM_VLLM_ENGINE_CLS_ENV = "RAYLLM_VLLM_ENGINE_CLS"
+
+# The ratio of number of router replicas to number of model replicas.
+# Default to 2 meaning that there are 2 router replicas for every model replica.
+DEFAULT_ROUTER_TO_MODEL_REPLICA_RATIO = float(
+ os.getenv("RAY_SERVE_LLM_ROUTER_TO_MODEL_REPLICA_RATIO", "2")
+)
+
+DEFAULT_LLM_ROUTER_MIN_REPLICAS = int(
+ os.environ.get("RAY_SERVE_LLM_ROUTER_MIN_REPLICAS", 2)
+)
+DEFAULT_LLM_ROUTER_INITIAL_REPLICAS = int(
+ os.environ.get("RAY_SERVE_LLM_ROUTER_INITIAL_REPLICAS", 2)
+)
+DEFAULT_LLM_ROUTER_MAX_REPLICAS = int(
+ os.environ.get("RAY_SERVE_LLM_ROUTER_MAX_REPLICAS", 1000)
+)
+DEFAULT_LLM_ROUTER_TARGET_ONGOING_REQUESTS = int(
+ os.environ.get(
+ "RAY_SERVE_LLM_ROUTER_TARGET_ONGOING_REQUESTS",
+ DEFAULT_MAX_TARGET_ONGOING_REQUESTS,
+ )
+)
+
+
+# HOME DIR
+RAYLLM_HOME_DIR = os.environ.get("RAYLLM_HOME_DIR", os.path.expanduser("~/.ray/llm"))
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2203755e8c3722dbbff5fc43bcf5d4f435a7828d
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/__pycache__/protocol.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/__pycache__/protocol.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..871b69dc8b9d3aef589d4fe87b85e1fbca30a02f
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/__pycache__/protocol.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ce6a765c84728b16a5990bfc24615709094a59f1
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__pycache__/llm_config.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__pycache__/llm_config.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0588bdd5e150e0a28d6616fc0a6a88144bbbae57
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__pycache__/llm_config.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__pycache__/openai_api_models.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__pycache__/openai_api_models.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..84e51863c731c518745f10268003a919899f102d
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/__pycache__/openai_api_models.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/llm_config.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/llm_config.py
new file mode 100644
index 0000000000000000000000000000000000000000..c4d25a6978efdd55312a1b4c070635955d8f5153
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/llm_config.py
@@ -0,0 +1,516 @@
+from enum import Enum
+from typing import (
+ Any,
+ Dict,
+ Optional,
+ TypeVar,
+ Union,
+)
+
+from pydantic import (
+ BaseModel,
+ Field,
+ PositiveInt,
+ PrivateAttr,
+ field_validator,
+ model_validator,
+)
+
+import ray.util.accelerators.accelerators as accelerators
+from ray.llm._internal.common.base_pydantic import BaseModelExtended
+from ray.llm._internal.common.callbacks.base import (
+ CallbackBase,
+ CallbackConfig,
+)
+from ray.llm._internal.common.utils.cloud_utils import (
+ CloudMirrorConfig,
+ is_remote_path,
+)
+from ray.llm._internal.common.utils.download_utils import (
+ STREAMING_LOAD_FORMATS,
+ NodeModelDownloadable,
+)
+from ray.llm._internal.common.utils.import_utils import load_class, try_import
+from ray.llm._internal.serve.constants import (
+ DEFAULT_MULTIPLEX_DOWNLOAD_TIMEOUT_S,
+ DEFAULT_MULTIPLEX_DOWNLOAD_TRIES,
+ MODEL_RESPONSE_BATCH_TIMEOUT_MS,
+)
+from ray.llm._internal.serve.engines.vllm.kv_transfer.factory import (
+ KVConnectorBackendFactory,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.serve._private.config import DeploymentConfig
+
+transformers = try_import("transformers")
+
+
+GPUType = Enum("GPUType", vars(accelerators))
+ModelT = TypeVar("ModelT", bound=BaseModel)
+
+
+logger = get_logger(__name__)
+
+
+class ServeMultiplexConfig(BaseModelExtended):
+ max_num_models_per_replica: PositiveInt = Field(
+ ..., description="The maximum number of models to be loaded on each replica."
+ )
+ download_timeout_s: Optional[float] = Field(
+ DEFAULT_MULTIPLEX_DOWNLOAD_TIMEOUT_S,
+ description="How much time the download subprocess has to download a single LoRA before a timeout. None means no timeout.",
+ )
+ max_download_tries: int = Field(
+ DEFAULT_MULTIPLEX_DOWNLOAD_TRIES,
+ description="The maximum number of download retries.",
+ )
+
+
+class InputModality(str, Enum):
+ text = "text"
+ image = "image"
+
+
+class LLMEngine(str, Enum):
+ """Enum that represents an LLMEngine."""
+
+ vLLM = "vLLM"
+
+
+class LoraConfig(BaseModelExtended):
+ dynamic_lora_loading_path: Optional[str] = Field(
+ default=None,
+ description="Cloud storage path where LoRA adapter weights are stored.",
+ )
+ max_num_adapters_per_replica: PositiveInt = Field(
+ default=16,
+ description="The maximum number of adapters load on each replica.",
+ )
+ download_timeout_s: Optional[float] = Field(
+ DEFAULT_MULTIPLEX_DOWNLOAD_TIMEOUT_S,
+ description=(
+ "How much time the download subprocess has to download a single "
+ "LoRA before a timeout. None means no timeout."
+ ),
+ )
+ max_download_tries: int = Field(
+ DEFAULT_MULTIPLEX_DOWNLOAD_TRIES,
+ description="The maximum number of download retries.",
+ )
+
+ @field_validator("dynamic_lora_loading_path")
+ def validate_dynamic_lora_loading_path(cls, value: Optional[str]):
+ if value is None:
+ return value
+
+ assert is_remote_path(value), (
+ "Only AWS S3, Google Cloud Storage, and Azure Storage are supported. The "
+ 'dynamic_lora_loading_path must start with "s3://", "gs://", "abfss://", or "azure://". '
+ f'Got "{value}" instead.'
+ )
+ return value.rstrip("/")
+
+
+class ModelLoadingConfig(BaseModelExtended):
+
+ model_id: str = Field(
+ description="The ID that should be used by end users to access this model.",
+ )
+ model_source: Optional[Union[str, CloudMirrorConfig]] = Field(
+ default=None,
+ description=(
+ "Where to obtain the model weights from. "
+ "Should be a HuggingFace model ID, S3 mirror config, GCS mirror config, "
+ "or a local path. When omitted, defaults to the model_id as a "
+ "HuggingFace model ID."
+ ),
+ )
+ tokenizer_source: Optional[str] = Field(
+ default=None,
+ description=(
+ "Where to obtain the tokenizer from. If None, tokenizer is "
+ "obtained from the model source. Only HuggingFace IDs are "
+ "supported for now."
+ ),
+ )
+
+
+EngineConfigType = Union[None, "VLLMEngineConfig"] # noqa: F821
+
+
+class LLMConfig(BaseModelExtended):
+
+ runtime_env: Optional[Dict[str, Any]] = Field(
+ default=None,
+ description=(
+ "The runtime_env to use for the model deployment replica "
+ "and the engine workers."
+ ),
+ )
+
+ model_loading_config: Union[Dict[str, Any], ModelLoadingConfig] = Field(
+ description="The settings for how to download and expose the model. Validated against ModelLoadingConfig."
+ )
+
+ llm_engine: str = Field(
+ default=LLMEngine.vLLM.value,
+ description=f"The LLMEngine that should be used to run the model. Only the following values are supported: {str([t.value for t in LLMEngine])}",
+ )
+
+ engine_kwargs: Dict[str, Any] = Field(
+ default={},
+ description=(
+ "Additional keyword arguments for the engine. In case of vLLM, "
+ "this will include all the configuration knobs they provide out "
+ "of the box, except for tensor-parallelism which is set "
+ "automatically from Ray Serve configs."
+ ),
+ )
+
+ accelerator_type: Optional[str] = Field(
+ default=None,
+ description=f"The type of accelerator runs the model on. Only the following values are supported: {str([t.value for t in GPUType])}",
+ )
+
+ placement_group_config: Optional[Dict[str, Any]] = Field(
+ default=None,
+ description=(
+ "Ray placement group configuration for scheduling vLLM engine workers. "
+ "Defines resource bundles and placement strategy for multi-node deployments. "
+ "Should contain 'bundles' (list of resource dicts) and optionally 'strategy' "
+ "(defaults to 'PACK'). Example: {'bundles': [{'GPU': 1, 'CPU': 2}], 'strategy': 'PACK'}"
+ ),
+ )
+
+ lora_config: Optional[Union[Dict[str, Any], LoraConfig]] = Field(
+ default=None,
+ description="Settings for LoRA adapter. Validated against LoraConfig.",
+ )
+
+ deployment_config: Dict[str, Any] = Field(
+ default_factory=dict,
+ description="""
+ The Ray @server.deployment options.
+ Supported fields are:
+ `name`, `num_replicas`, `ray_actor_options`, `max_ongoing_requests`,
+ `autoscaling_config`, `max_queued_requests`, `user_config`,
+ `health_check_period_s`, `health_check_timeout_s`,
+ `graceful_shutdown_wait_loop_s`, `graceful_shutdown_timeout_s`,
+ `logging_config`, `request_router_config`.
+ For more details, see the `Ray Serve Documentation `_.
+ """,
+ )
+
+ experimental_configs: Dict[str, Any] = Field(
+ default_factory=dict,
+ description="Experimental configurations for Ray Serve LLM. This is a "
+ "dictionary of key-value pairs. Current supported keys are:\n"
+ "- `stream_batching_interval_ms`: Ray Serve LLM batches streaming "
+ "requests together. This config decides how long to wait for the "
+ "batch before processing the requests. Defaults to "
+ f"{MODEL_RESPONSE_BATCH_TIMEOUT_MS}.\n"
+ "- `num_ingress_replicas`: The number of replicas for the router. Ray "
+ "Serve will take the max amount all the replicas. Default would be 2 "
+ "router replicas per model replica.\n",
+ )
+
+ log_engine_metrics: Optional[bool] = Field(
+ default=True,
+ description="Enable additional engine metrics via Ray Prometheus port.",
+ )
+
+ callback_config: CallbackConfig = Field(
+ default_factory=CallbackConfig,
+ description="Callback configuration to use for model initialization. Can be a string path to a class or a Callback subclass.",
+ )
+
+ _supports_vision: bool = PrivateAttr(False)
+ _model_architecture: str = PrivateAttr("UNSPECIFIED")
+ _engine_config: EngineConfigType = PrivateAttr(None)
+ _callback_instance: Optional[CallbackBase] = PrivateAttr(None)
+
+ def _infer_supports_vision(self, model_id_or_path: str) -> None:
+ """Called in llm node initializer together with other transformers calls. It
+ loads the model config from huggingface and sets the supports_vision
+ attribute based on whether the config has `vision_config`. All LVM models has
+ `vision_config` setup.
+ """
+ try:
+ hf_config = transformers.PretrainedConfig.from_pretrained(model_id_or_path)
+ self._supports_vision = hasattr(hf_config, "vision_config")
+ except Exception as e:
+ raise ValueError(
+ f"Failed to load Hugging Face config for model_id='{model_id_or_path}'.\
+ Ensure `model_id` is a valid Hugging Face repo or a local path that \
+ contains a valid `config.json` file. "
+ f"Original error: {repr(e)}"
+ ) from e
+
+ def _set_model_architecture(
+ self,
+ model_id_or_path: Optional[str] = None,
+ model_architecture: Optional[str] = None,
+ ) -> None:
+ """Called in llm node initializer together with other transformers calls. It
+ loads the model config from huggingface and sets the model_architecture
+ attribute based on whether the config has `architectures`.
+ """
+ if model_id_or_path:
+ try:
+ hf_config = transformers.PretrainedConfig.from_pretrained(
+ model_id_or_path
+ )
+ if (
+ hf_config
+ and hasattr(hf_config, "architectures")
+ and hf_config.architectures
+ ):
+ self._model_architecture = hf_config.architectures[0]
+ except Exception as e:
+ raise ValueError(
+ f"Failed to load Hugging Face config for model_id='{model_id_or_path}'.\
+ Ensure `model_id` is a valid Hugging Face repo or a local path that \
+ contains a valid `config.json` file. "
+ f"Original error: {repr(e)}"
+ ) from e
+
+ if model_architecture:
+ self._model_architecture = model_architecture
+
+ def apply_checkpoint_info(
+ self, model_id_or_path: str, trust_remote_code: bool = False
+ ) -> None:
+ """Apply the checkpoint info to the model config."""
+ self._infer_supports_vision(model_id_or_path)
+ self._set_model_architecture(model_id_or_path)
+
+ def get_or_create_callback(self) -> Optional[CallbackBase]:
+ """Get or create the callback instance for this process.
+
+ This ensures one callback instance per process (singleton pattern).
+ The instance is cached so the same object is used across all hooks.
+
+ Returns:
+ Instance of class that implements Callback
+ """ # Return cached instance if exists
+ if self._callback_instance is not None:
+ return self._callback_instance
+
+ engine_config = self.get_engine_config()
+ assert engine_config is not None
+ pg = engine_config.get_or_create_pg()
+ runtime_env = engine_config.get_runtime_env_with_local_env_vars()
+ if self.engine_kwargs.get("load_format", None) in STREAMING_LOAD_FORMATS:
+ worker_node_download_model = NodeModelDownloadable.NONE
+ else:
+ worker_node_download_model = NodeModelDownloadable.MODEL_AND_TOKENIZER
+
+ # Create new instance
+ if isinstance(self.callback_config.callback_class, str):
+ callback_class = load_class(self.callback_config.callback_class)
+ else:
+ callback_class = self.callback_config.callback_class
+
+ self._callback_instance = callback_class(
+ raise_error_on_callback=self.callback_config.raise_error_on_callback,
+ llm_config=self,
+ ctx_kwargs={
+ "worker_node_download_model": worker_node_download_model,
+ "placement_group": pg,
+ "runtime_env": runtime_env,
+ },
+ **self.callback_config.callback_kwargs,
+ )
+ return self._callback_instance
+
+ @property
+ def supports_vision(self) -> bool:
+ return self._supports_vision
+
+ @property
+ def model_architecture(self) -> str:
+ return self._model_architecture
+
+ @property
+ def input_modality(self) -> str:
+ """Returns the input modality of the model. There could be more types in the
+ future. Right now assumes if the model doesn't support version, it'll be text.
+ """
+ if self.supports_vision:
+ return InputModality.image.value
+
+ return InputModality.text.value
+
+ @property
+ def model_id(self) -> str:
+ return self.model_loading_config.model_id
+
+ @property
+ def max_request_context_length(self) -> Optional[int]:
+ return self.engine_kwargs.get("max_model_len")
+
+ @field_validator("accelerator_type")
+ def validate_accelerator_type(cls, value: Optional[str]):
+ if value is None:
+ return value
+
+ # Ensure A10 is converted to A10G.
+ if value == "A10":
+ value = "A10G"
+
+ if value not in [t.value for t in GPUType]:
+ raise ValueError(f"Unsupported accelerator type: {value}")
+
+ return value
+
+ @field_validator("llm_engine")
+ def validate_llm_engine(cls, value: str) -> str:
+ """Validates the llm_engine string value."""
+ try:
+ # Validate the engine
+ LLMEngine(value)
+ except ValueError as e:
+ raise ValueError(f"Unsupported engine: {value}") from e
+ return value
+
+ @field_validator("deployment_config")
+ def validate_deployment_config(cls, value: Dict[str, Any]) -> Dict[str, Any]:
+ """Validates the deployment config dictionary."""
+ try:
+ DeploymentConfig(**value)
+ except Exception as e:
+ raise ValueError(f"Invalid deployment config: {value}") from e
+
+ return value
+
+ @field_validator("model_loading_config")
+ def validate_model_loading_config(
+ cls, value: Union[Dict[str, Any], ModelLoadingConfig]
+ ) -> ModelLoadingConfig:
+ """Validates the model loading config dictionary."""
+ if isinstance(value, ModelLoadingConfig):
+ return value
+
+ try:
+ model_loading_config = ModelLoadingConfig(**value)
+ except Exception as e:
+ raise ValueError(f"Invalid model_loading_config: {value}") from e
+
+ return model_loading_config
+
+ @field_validator("lora_config")
+ def validate_lora_config(
+ cls, value: Optional[Union[Dict[str, Any], LoraConfig]]
+ ) -> Optional[LoraConfig]:
+ """Validates the lora config dictionary."""
+ if value is None or isinstance(value, LoraConfig):
+ return value
+
+ try:
+ lora_config = LoraConfig(**value)
+ except Exception as e:
+ raise ValueError(f"Invalid lora_config: {value}") from e
+
+ return lora_config
+
+ @field_validator("experimental_configs")
+ def validate_experimental_configs(cls, value: Dict[str, Any]) -> Dict[str, Any]:
+ """Validates the experimental configs dictionary."""
+ # TODO(Kourosh): Remove this deprecation check after users have
+ # migrated.
+ if "num_router_replicas" in value:
+ raise ValueError(
+ "The 'num_router_replicas' key in experimental_configs has "
+ "been renamed to 'num_ingress_replicas'. Please update "
+ "your configuration to use 'num_ingress_replicas' instead."
+ )
+ return value
+
+ @model_validator(mode="after")
+ def _check_log_stats_with_metrics(self):
+ """Validate that disable_log_stats isn't enabled when log_engine_metrics is enabled."""
+ if self.log_engine_metrics and self.engine_kwargs.get("disable_log_stats"):
+ raise ValueError(
+ "disable_log_stats cannot be set to True when log_engine_metrics is enabled. "
+ "Engine metrics require log stats to be enabled."
+ )
+
+ return self
+
+ def multiplex_config(self) -> ServeMultiplexConfig:
+ multiplex_config = None
+ if self.lora_config:
+ multiplex_config = ServeMultiplexConfig(
+ max_num_models_per_replica=self.lora_config.max_num_adapters_per_replica,
+ download_timeout_s=self.lora_config.download_timeout_s,
+ max_download_tries=self.lora_config.max_download_tries,
+ )
+ return multiplex_config
+
+ def get_engine_config(self) -> EngineConfigType:
+ """Returns the engine config for the given LLM config.
+
+ LLMConfig not only has engine config but also deployment config, etc.
+ """
+ # Note (genesu): This is important that we cache the engine config as the
+ # `hf_model_id` attribute on the engine config will be set based on whether
+ # the model is downloaded from a remote storage and will be set to the
+ # local path of the model. This is important for vLLM not going to Hugging
+ # Face to download the model again after it's already downloaded during node
+ # initialization step.
+ if self._engine_config:
+ return self._engine_config
+
+ if self.llm_engine == LLMEngine.vLLM:
+ from ray.llm._internal.serve.engines.vllm.vllm_models import (
+ VLLMEngineConfig,
+ )
+
+ self._engine_config = VLLMEngineConfig.from_llm_config(self)
+ else:
+ # Note (genesu): This should never happen because we validate the engine
+ # in the config.
+ raise ValueError(f"Unsupported engine: {self.llm_engine}")
+
+ return self._engine_config
+
+ def update_engine_kwargs(self, **kwargs: Any) -> None:
+ """Update the engine_kwargs and the engine_config engine_kwargs.
+
+ This is typically called during engine starts, when certain engine_kwargs
+ (e.g., data_parallel_rank) become available.
+ """
+ self.engine_kwargs.update(kwargs)
+ # engine_config may be created before engine starts, this makes sure
+ # the engine_config is updated with the latest engine_kwargs.
+ if self._engine_config:
+ self._engine_config.engine_kwargs.update(kwargs)
+
+ def setup_engine_backend(self):
+ self._setup_kv_connector_backend()
+
+ def _setup_kv_connector_backend(self):
+ """Private method to setup kv connector depending on the local deployment state"""
+ # 1. validate that the backend is one of the backends supported (Nixl or LMCache)
+ kv_transfer_config = self.engine_kwargs.get("kv_transfer_config")
+ if not kv_transfer_config:
+ return
+
+ kv_connector = kv_transfer_config.get("kv_connector")
+ if not kv_connector:
+ raise ValueError("Connector type is not specified.")
+
+ # 2. Setup the backend using factory
+ kv_connector_backend = KVConnectorBackendFactory.create_backend(
+ kv_connector, self
+ )
+ kv_connector_backend.setup()
+
+
+class DiskMultiplexConfig(BaseModelExtended):
+ model_id: str
+ max_total_tokens: Optional[int]
+ local_path: str
+
+ # this is a per process id assigned to the model
+ lora_assigned_int_id: int
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/openai_api_models.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/openai_api_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..9fc708ce0bc64981fa4bbb998bce32be86f4d079
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/configs/openai_api_models.py
@@ -0,0 +1,234 @@
+"""This module contains the wrapper classes for vLLM's OpenAI implementation.
+
+If there are any major differences in the interface, the expectation is that
+they will be upstreamed to vLLM.
+"""
+
+from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, List, Optional, Union
+
+from pydantic import (
+ BaseModel,
+ ConfigDict,
+ Field,
+)
+from vllm.entrypoints.openai.protocol import (
+ ChatCompletionRequest as vLLMChatCompletionRequest,
+ ChatCompletionResponse as vLLMChatCompletionResponse,
+ ChatCompletionStreamResponse as vLLMChatCompletionStreamResponse,
+ CompletionRequest as vLLMCompletionRequest,
+ CompletionResponse as vLLMCompletionResponse,
+ CompletionStreamResponse as vLLMCompletionStreamResponse,
+ EmbeddingChatRequest as vLLMEmbeddingChatRequest,
+ EmbeddingCompletionRequest as vLLMEmbeddingCompletionRequest,
+ EmbeddingResponse as vLLMEmbeddingResponse,
+ ErrorInfo as vLLMErrorInfo,
+ ErrorResponse as vLLMErrorResponse,
+ ScoreRequest as vLLMScoreRequest,
+ ScoreResponse as vLLMScoreResponse,
+ TranscriptionRequest as vLLMTranscriptionRequest,
+ TranscriptionResponse as vLLMTranscriptionResponse,
+ TranscriptionStreamResponse as vLLMTranscriptionStreamResponse,
+)
+from vllm.utils import random_uuid
+
+if TYPE_CHECKING:
+ from ray.llm._internal.serve.core.configs.llm_config import LLMConfig
+
+
+class ChatCompletionRequest(vLLMChatCompletionRequest):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+class ChatCompletionResponse(vLLMChatCompletionResponse):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+class ChatCompletionStreamResponse(vLLMChatCompletionStreamResponse):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+class ErrorInfo(vLLMErrorInfo):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+class ErrorResponse(vLLMErrorResponse):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+# TODO (Kourosh): Upstream
+class CompletionRequest(vLLMCompletionRequest):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+ request_id: str = Field(
+ default_factory=lambda: f"{random_uuid()}",
+ description=(
+ "The request_id related to this request. If the caller does "
+ "not set it, a random_uuid will be generated. This id is used "
+ "through out the inference process and return in response."
+ ),
+ )
+
+
+class CompletionResponse(vLLMCompletionResponse):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+class CompletionStreamResponse(vLLMCompletionStreamResponse):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+# TODO (Kourosh): Upstream
+class EmbeddingCompletionRequest(vLLMEmbeddingCompletionRequest):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+ request_id: str = Field(
+ default_factory=lambda: f"{random_uuid()}",
+ description=(
+ "The request_id related to this request. If the caller does "
+ "not set it, a random_uuid will be generated. This id is used "
+ "through out the inference process and return in response."
+ ),
+ )
+
+
+class EmbeddingChatRequest(vLLMEmbeddingChatRequest):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+class EmbeddingResponse(vLLMEmbeddingResponse):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+class TranscriptionRequest(vLLMTranscriptionRequest):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+ request_id: str = Field(
+ default_factory=lambda: f"{random_uuid()}",
+ description=(
+ "The request_id related to this request. If the caller does "
+ "not set it, a random_uuid will be generated. This id is used "
+ "through out the inference process and return in response."
+ ),
+ )
+
+
+class TranscriptionResponse(vLLMTranscriptionResponse):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+class TranscriptionStreamResponse(vLLMTranscriptionStreamResponse):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+class ScoreRequest(vLLMScoreRequest):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+class ScoreResponse(vLLMScoreResponse):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+
+
+EmbeddingRequest = Union[EmbeddingCompletionRequest, EmbeddingChatRequest]
+
+LLMEmbeddingsResponse = Union[
+ AsyncGenerator[Union[EmbeddingResponse, ErrorResponse], None],
+]
+
+LLMScoreResponse = Union[
+ AsyncGenerator[Union[ScoreResponse, ErrorResponse], None],
+]
+
+LLMChatResponse = Union[
+ AsyncGenerator[
+ Union[str, ChatCompletionStreamResponse, ChatCompletionResponse, ErrorResponse],
+ None,
+ ],
+]
+
+LLMCompletionsResponse = Union[
+ AsyncGenerator[
+ Union[str, CompletionStreamResponse, CompletionResponse, ErrorResponse], None
+ ],
+]
+
+LLMTranscriptionResponse = Union[
+ AsyncGenerator[
+ Union[str, TranscriptionStreamResponse, TranscriptionResponse, ErrorResponse],
+ None,
+ ],
+]
+
+
+# TODO: remove this class
+class OpenAIHTTPException(Exception):
+ def __init__(
+ self,
+ status_code: int,
+ message: str,
+ type: str = "Unknown",
+ internal_message: Optional[str] = None,
+ ) -> None:
+ self.status_code = status_code
+ self.message = message
+ self.type = type
+ self.internal_message = internal_message
+
+
+# TODO: upstream metadata for ModelData
+# Compared to vLLM this has a metadata field.
+class ModelCard(BaseModel):
+ model_config = ConfigDict(
+ protected_namespaces=tuple(), arbitrary_types_allowed=True
+ )
+
+ id: str
+ object: str
+ owned_by: str
+ permission: List[str]
+ metadata: Dict[str, Any]
+
+ @property
+ def model_type(self) -> str:
+ return self.metadata["engine_config"]["model_type"]
+
+
+class ModelList(BaseModel):
+ model_config = ConfigDict(arbitrary_types_allowed=True)
+ data: List[ModelCard]
+ object: str = "list"
+
+
+def to_model_metadata(
+ model_id: str,
+ model_config: "LLMConfig",
+ overrides: Optional[Dict[str, Any]] = None,
+) -> ModelCard:
+ """Creates an OpenAI-compatible ModelData object.
+
+ Args:
+ model_id: The ID of the model. Should contain the suffix if the model
+ is LoRA fine-tuned. For example:
+ meta-llama/Llama-2-7b-chat-hf:my_suffix:aBc1234
+ model_config: The model's YAML config.
+ overrides: should only be set for LoRA fine-tuned models. The
+ overrides of the fine-tuned model metadata.
+
+ Returns:
+ A ModelCard object.
+ """
+ metadata = {
+ "model_id": model_config.model_id,
+ "input_modality": model_config.input_modality,
+ "max_request_context_length": model_config.max_request_context_length,
+ }
+
+ if overrides:
+ metadata.update(overrides)
+
+ return ModelCard(
+ id=model_id,
+ object="model",
+ owned_by="organization-owner",
+ permission=[],
+ metadata=metadata,
+ )
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f7d840c437e2bc3089dc6c378109ef859c86f2f3
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/__pycache__/protocol.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/__pycache__/protocol.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4cf735079328320db870278dda4c7e0dbee347c0
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/__pycache__/protocol.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/protocol.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/protocol.py
new file mode 100644
index 0000000000000000000000000000000000000000..c36b8073d0dab53c5886b1291b5a9e95f5b71eda
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/engine/protocol.py
@@ -0,0 +1,178 @@
+import abc
+from typing import TYPE_CHECKING, AsyncGenerator, Union
+
+from ray.llm._internal.serve.core.configs.llm_config import (
+ DiskMultiplexConfig,
+ LLMConfig,
+)
+
+if TYPE_CHECKING:
+ from ray.llm._internal.serve.core.configs.openai_api_models import (
+ ChatCompletionRequest,
+ ChatCompletionResponse,
+ CompletionRequest,
+ CompletionResponse,
+ EmbeddingRequest,
+ EmbeddingResponse,
+ ErrorResponse,
+ TranscriptionRequest,
+ TranscriptionResponse,
+ )
+
+
+class LLMEngine(abc.ABC):
+ """Base protocol class for all LLM engines."""
+
+ @abc.abstractmethod
+ def __init__(self, llm_config: LLMConfig):
+ """Initialize the engine with the llm config"""
+ pass
+
+ @abc.abstractmethod
+ async def start(self):
+ """Start the engine"""
+ pass
+
+ @abc.abstractmethod
+ async def resolve_lora(self, lora_model: DiskMultiplexConfig):
+ """Mounts the LoRA model on the engine, given the local disk path."""
+ pass
+
+ @abc.abstractmethod
+ async def reset_prefix_cache(self) -> None:
+ """Reset the prefix cache of the underlying engine"""
+
+ @abc.abstractmethod
+ async def chat(
+ self, request: "ChatCompletionRequest"
+ ) -> AsyncGenerator[Union[str, "ChatCompletionResponse", "ErrorResponse"], None]:
+ """Run a ChatCompletion with the engine.
+
+ To implement this method, you need to take a openAI compatible chat request, internally cast it to the target engine request type, and then call the engine's chat method.
+
+ This method is an async generator, so it yields chunks of response and when it is done, it returns None. We have the following convention:
+
+ - In case of streaming, yield a string representing data: \n\n for each chunk. This should be already openAI compatible, so the higher level can just yield it to the client.
+ - In case of non-streaming, yield a single object of type ChatCompletionResponse.
+ - In case of error, yield a single object of type ErrorResponse.
+
+ Args:
+ request: The chat completion request.
+
+ Yields:
+ Union[str, ChatCompletionResponse, ErrorResponse]: A string representing a chunk of the response, a ChatCompletionResponse object, or an ErrorResponse object.
+
+ Returns:
+ None when the generator is done.
+ """
+ pass
+
+ @abc.abstractmethod
+ async def completions(
+ self, request: "CompletionRequest"
+ ) -> AsyncGenerator[Union[str, "CompletionResponse", "ErrorResponse"], None]:
+ """Run a Completion with the engine.
+
+ Similar to chat, this method is an async generator, so it yields chunks
+ of response and when it is done, it returns None. We have the following
+ convention:
+
+ * In case of streaming, yield a string representing data:
+ \n\n for each chunk. This should be already openAI compatible
+ with completion response format, so the higher level can just yield it
+ directly to the client.
+ * In case of non-streaming, yield a single object of type
+ CompletionResponse.
+ * In case of error, yield a single object of type ErrorResponse.
+
+ Args:
+ request: The completion request.
+
+ Yields:
+ Union[str, CompletionResponse, ErrorResponse]: A string
+ representing a chunk of the response, a CompletionResponse object,
+ or an ErrorResponse object.
+
+ Returns:
+ None when the generator is done.
+ """
+ pass
+
+ @abc.abstractmethod
+ async def embeddings(
+ self, request: "EmbeddingRequest"
+ ) -> AsyncGenerator[Union["EmbeddingResponse", "ErrorResponse"], None]:
+ """Run an Embedding with the engine.
+
+ This method is different from chat and completion in that it does not
+ have streaming, but still it is an async generator that yields response
+ objects and when it is done, it returns None. We have the following
+ convention:
+
+ * yield a single object of type EmbeddingResponse.
+ * For errors, yield a single object of type ErrorResponse.
+
+ Args:
+ request: The embedding request.
+
+ Returns:
+ An async generator that yields EmbeddingResponse objects or ErrorResponse objects, and returns None when the generator is done.
+ """
+ pass
+
+ @abc.abstractmethod
+ async def transcriptions(
+ self, request: "TranscriptionRequest"
+ ) -> AsyncGenerator[Union[str, "TranscriptionResponse", "ErrorResponse"], None]:
+ """Run a Transcription with the engine.
+
+ Similar to chat and completion, this method is an async generator,
+ so it yields chunks of response and when it is done, it returns None.
+ We have the following convention:
+
+ * In case of streaming, yield a string representing data:
+ \n\n for each chunk. This should be already openAI compatible,
+ so the higher level can just yield it to the client.
+ * In case of non-streaming, yield a single object of type TranscriptionResponse.
+ * In case of error, yield a single object of type ErrorResponse.
+
+ Args:
+ request: The transcription request.
+
+ Yields:
+ Union[str, TranscriptionResponse, ErrorResponse]: A string
+ representing a chunk of the response, a TranscriptionResponse object,
+ or an ErrorResponse object.
+
+ Returns:
+ None when the generator is done.
+ """
+ pass
+
+ async def check_health(self) -> None:
+ """Check the health of the engine.
+
+ Does not return anything. Raise error when the engine is dead and needs
+ to be restarted.
+ """
+ return
+
+ ##############################################################
+ # Optional methods
+ # These methods will be implemented in the future to allow
+ # more granular life-cycle management of the engine.
+ # e.g. in usecases like RL training, we need to put the engine
+ # to sleep during training and wake up during rollouts.
+ ##############################################################
+
+ async def sleep(self):
+ """Puts the engine to sleep"""
+ pass
+
+ async def wakeup(self):
+ """Wakes up the engine"""
+ pass
+
+ def shutdown(self):
+ """Shuts down the engine"""
+ pass
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d8587bea84822cbaa3529993a9a4ea473958a323
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/builder.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/builder.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..fa0daaff24e0a2206dcb28b707770544b829521b
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/builder.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/ingress.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/ingress.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..55dbc0e0b434d6c4ad5e5c5f5a5e5e52e49b3cd7
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/ingress.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/middleware.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/middleware.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1c150c43145fb2622b94d245ab888db3211eccfa
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/__pycache__/middleware.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/builder.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..b083b7133f5af3001060493d34341498b19ee9ce
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/builder.py
@@ -0,0 +1,136 @@
+import os
+import pprint
+from typing import Any, Dict, List, Optional, Type, Union
+
+from pydantic import Field, field_validator, model_validator
+
+from ray import serve
+from ray.llm._internal.common.base_pydantic import BaseModelExtended
+from ray.llm._internal.common.dict_utils import deep_merge_dicts
+from ray.llm._internal.common.utils.import_utils import load_class
+from ray.llm._internal.serve.core.configs.llm_config import LLMConfig
+from ray.llm._internal.serve.core.ingress.ingress import (
+ OpenAiIngress,
+ make_fastapi_ingress,
+)
+from ray.llm._internal.serve.core.server.builder import (
+ build_llm_deployment,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.serve.deployment import Application
+
+logger = get_logger(__name__)
+
+
+class IngressClsConfig(BaseModelExtended):
+ ingress_cls: Union[str, Type[OpenAiIngress]] = Field(
+ default=OpenAiIngress,
+ description="The class name of the ingress to use. It can be in form of `module_name.class_name` or `module_name:class_name` or the class itself. The class constructor should take the following arguments: `(llm_deployments: List[DeploymentHandle], **extra_kwargs)` where `llm_deployments` is a list of DeploymentHandle objects from `LLMServer` deployments.",
+ )
+
+ ingress_extra_kwargs: Optional[dict] = Field(
+ default_factory=dict,
+ description="""The kwargs to bind to the ingress deployment. This will be passed to the ingress class constructor.""",
+ )
+
+ @field_validator("ingress_cls")
+ @classmethod
+ def validate_class(
+ cls, value: Union[str, Type[OpenAiIngress]]
+ ) -> Type[OpenAiIngress]:
+ if isinstance(value, str):
+ return load_class(value)
+ return value
+
+
+class LLMServingArgs(BaseModelExtended):
+ llm_configs: List[Union[str, dict, LLMConfig]] = Field(
+ description="A list of LLMConfigs, or dicts representing LLMConfigs, or paths to yaml files defining LLMConfigs.",
+ )
+ ingress_cls_config: Union[dict, IngressClsConfig] = Field(
+ default_factory=IngressClsConfig,
+ description="The configuration for the ingress class. It can be a dict representing the ingress class configuration, or an IngressClsConfig object.",
+ )
+ ingress_deployment_config: Dict[str, Any] = Field(
+ default_factory=dict,
+ description="""
+ The Ray @server.deployment options for the ingress server.
+ """,
+ )
+
+ @field_validator("ingress_cls_config")
+ @classmethod
+ def _validate_ingress_cls_config(
+ cls, value: Union[dict, IngressClsConfig]
+ ) -> IngressClsConfig:
+ if isinstance(value, dict):
+ return IngressClsConfig.model_validate(value)
+ return value
+
+ @field_validator("llm_configs")
+ @classmethod
+ def _validate_llm_configs(
+ cls, value: List[Union[str, dict, LLMConfig]]
+ ) -> List[LLMConfig]:
+ llm_configs = []
+ for config in value:
+ if isinstance(config, str):
+ if not os.path.exists(config):
+ raise ValueError(
+ f"Could not load model config from {config}, as the file does not exist."
+ )
+ llm_configs.append(LLMConfig.from_file(config))
+ elif isinstance(config, dict):
+ llm_configs.append(LLMConfig.model_validate(config))
+ elif isinstance(config, LLMConfig):
+ llm_configs.append(config)
+ else:
+ raise TypeError(f"Invalid LLMConfig type: {type(config)}")
+ return llm_configs
+
+ @model_validator(mode="after")
+ def _validate_model_ids(self):
+ """Validate that model IDs are unique and at least one model is configured."""
+ if len({m.model_id for m in self.llm_configs}) != len(self.llm_configs):
+ raise ValueError("Duplicate models found. Make sure model ids are unique.")
+
+ if len(self.llm_configs) == 0:
+ raise ValueError(
+ "List of models is empty. Maybe some parameters cannot be parsed into the LLMConfig config."
+ )
+ return self
+
+
+def build_openai_app(builder_config: dict) -> Application:
+ """Build an OpenAI compatible app with the llm deployment setup from
+ the given builder configuration.
+
+ Args:
+ builder_config: The configuration for the builder. It has to conform
+ to the LLMServingArgs pydantic model.
+
+ Returns:
+ The configured Ray Serve Application router.
+ """
+
+ builder_config = LLMServingArgs.model_validate(builder_config)
+ llm_configs = builder_config.llm_configs
+
+ llm_deployments = [build_llm_deployment(c) for c in llm_configs]
+
+ ingress_cls_config = builder_config.ingress_cls_config
+ ingress_options = ingress_cls_config.ingress_cls.get_deployment_options(llm_configs)
+
+ if builder_config.ingress_deployment_config:
+ ingress_options = deep_merge_dicts(
+ ingress_options, builder_config.ingress_deployment_config
+ )
+
+ ingress_cls = make_fastapi_ingress(ingress_cls_config.ingress_cls)
+
+ logger.info("============== Ingress Options ==============")
+ logger.info(pprint.pformat(ingress_options))
+
+ return serve.deployment(ingress_cls, **ingress_options).bind(
+ llm_deployments=llm_deployments, **ingress_cls_config.ingress_extra_kwargs
+ )
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/ingress.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/ingress.py
new file mode 100644
index 0000000000000000000000000000000000000000..29a9e17ada4dcdd43af354287f88d1affaa46ad0
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/ingress.py
@@ -0,0 +1,665 @@
+import asyncio
+import json
+import sys
+from contextlib import asynccontextmanager
+from enum import Enum
+from typing import (
+ Annotated,
+ Any,
+ AsyncGenerator,
+ Awaitable,
+ Callable,
+ Dict,
+ List,
+ Optional,
+ Tuple,
+ Type,
+ TypeVar,
+ Union,
+)
+
+from fastapi import FastAPI, Form, HTTPException, status
+from fastapi.middleware.cors import CORSMiddleware
+from starlette.responses import JSONResponse, Response, StreamingResponse
+
+from ray import serve
+from ray._common.utils import get_or_create_event_loop
+from ray.llm._internal.common.utils.lora_utils import (
+ get_base_model_id,
+ get_lora_model_ids,
+)
+from ray.llm._internal.serve.constants import (
+ DEFAULT_LLM_ROUTER_HTTP_TIMEOUT,
+ DEFAULT_MAX_ONGOING_REQUESTS,
+)
+from ray.llm._internal.serve.core.configs.llm_config import LLMConfig
+from ray.llm._internal.serve.core.configs.openai_api_models import (
+ ChatCompletionRequest,
+ ChatCompletionResponse,
+ ChatCompletionStreamResponse,
+ CompletionRequest,
+ CompletionResponse,
+ CompletionStreamResponse,
+ EmbeddingRequest,
+ EmbeddingResponse,
+ ErrorResponse,
+ LLMChatResponse,
+ LLMCompletionsResponse,
+ LLMEmbeddingsResponse,
+ LLMScoreResponse,
+ LLMTranscriptionResponse,
+ ModelCard,
+ ModelList,
+ OpenAIHTTPException,
+ ScoreRequest,
+ ScoreResponse,
+ TranscriptionRequest,
+ TranscriptionResponse,
+ TranscriptionStreamResponse,
+ to_model_metadata,
+)
+from ray.llm._internal.serve.core.ingress.middleware import (
+ SetRequestIdMiddleware,
+ add_exception_handling_middleware,
+)
+from ray.llm._internal.serve.core.protocol import DeploymentProtocol
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.llm._internal.serve.observability.metrics.fast_api_metrics import (
+ add_http_metrics_middleware,
+ metrics_lifespan,
+)
+from ray.llm._internal.serve.utils.lora_serve_utils import (
+ get_lora_model_metadata,
+)
+from ray.llm._internal.serve.utils.server_utils import replace_prefix
+from ray.serve.handle import DeploymentHandle
+
+# Import asyncio timeout depends on python version
+if sys.version_info >= (3, 11):
+ from asyncio import timeout
+else:
+ from async_timeout import timeout
+
+logger = get_logger(__name__)
+
+T = TypeVar("T")
+
+
+DEFAULT_INGRESS_OPTIONS = {
+ "max_ongoing_requests": DEFAULT_MAX_ONGOING_REQUESTS,
+}
+
+# These methods correspond to functions defined in the LLMEngine class in python/ray/llm/_internal/serve/deployments/llm/llm_engine.py
+class CallMethod(Enum):
+ CHAT = "chat"
+ COMPLETIONS = "completions"
+ TRANSCRIPTIONS = "transcriptions"
+
+
+NON_STREAMING_RESPONSE_TYPES = (
+ ChatCompletionResponse,
+ CompletionResponse,
+ TranscriptionResponse,
+)
+
+
+def _sanitize_chat_completion_request(
+ request: ChatCompletionRequest,
+) -> ChatCompletionRequest:
+ """Sanitize ChatCompletionRequest to fix Pydantic ValidatorIterator serialization issue.
+
+ This addresses a known Pydantic bug where tool_calls fields become ValidatorIterator
+ objects that cannot be pickled for Ray remote calls.
+
+ References:
+ - vLLM PR that introduces the workaround: https://github.com/vllm-project/vllm/pull/9951
+ - Pydantic Issue: https://github.com/pydantic/pydantic/issues/9467
+ - Related Issue: https://github.com/pydantic/pydantic/issues/9541
+ - Official Workaround: https://github.com/pydantic/pydantic/issues/9467#issuecomment-2442097291
+
+ TODO(seiji): Remove when we update to Pydantic v2.11+ with the fix.
+ """
+ from vllm.transformers_utils.tokenizers.mistral import maybe_serialize_tool_calls
+
+ maybe_serialize_tool_calls(request)
+
+ return request
+
+
+StreamResponseType = Union[
+ ChatCompletionStreamResponse, CompletionStreamResponse, TranscriptionStreamResponse
+]
+BatchedStreamResponseType = List[StreamResponseType]
+
+
+DEFAULT_ENDPOINTS = {
+ "models": lambda app: app.get("/v1/models", response_model=ModelList),
+ "model_data": lambda app: app.get(
+ "/v1/models/{model:path}", response_model=ModelCard
+ ),
+ "completions": lambda app: app.post("/v1/completions"),
+ "chat": lambda app: app.post("/v1/chat/completions"),
+ "embeddings": lambda app: app.post("/v1/embeddings"),
+ "transcriptions": lambda app: app.post(
+ "/v1/audio/transcriptions",
+ ),
+ "score": lambda app: app.post("/v1/score"),
+}
+
+
+def init() -> FastAPI:
+ _fastapi_router_app = FastAPI(lifespan=metrics_lifespan)
+
+ # NOTE: PLEASE READ CAREFULLY BEFORE MODIFYING
+ #
+ # FastAPI middleware is executed in LIFO (last-in, first-out) order,
+ # hence maintaining current ordering is crucial as some of the middleware
+ # might have data dependency on the other: for ex, telemetry middleware
+ # depends on middleware generating request-id
+ #
+ # Add exception handling middleware
+ # NOTE: This middleware should be added first such that it's intercepting
+ # exceptions from the handlers, avoiding them propagating to other
+ # middleware (for ex, telemetry)
+ add_exception_handling_middleware(_fastapi_router_app)
+ # Configure CORS middleware
+ _fastapi_router_app.add_middleware(
+ CORSMiddleware,
+ allow_origins=["*"],
+ allow_credentials=True,
+ allow_methods=["*"],
+ allow_headers=["*"],
+ )
+ # Add HTTP metrics middleware
+ add_http_metrics_middleware(_fastapi_router_app)
+
+ # Inject unique per-request ID
+ #
+ # NOTE: This middleware should be executed among the last (since
+ # middleware is executed in LIFO).
+ _fastapi_router_app.add_middleware(SetRequestIdMiddleware)
+
+ return _fastapi_router_app
+
+
+def make_fastapi_ingress(
+ cls: Type,
+ *,
+ endpoint_map: Optional[Dict[str, Callable[[FastAPI], Callable]]] = None,
+ app: Optional[FastAPI] = None,
+):
+ """
+ Create a Ray Serve ingress deployment from a class and endpoint mapping.
+
+ Args:
+ cls: The class to convert into an ingress deployment
+ endpoint_map: Dictionary mapping method names to FastAPI route
+ decorators. Each value is a lambda that takes a FastAPI app and
+ returns a route decorator.
+ app: Optional FastAPI app to use for the ingress deployment. If not
+ provided, a new FastAPI app will be created.
+
+ Returns:
+ A class decorated with @serve.ingress
+
+ Example:
+ endpoint_map = {
+ "increment": lambda app: app.post("/increment"),
+ "get_counter": lambda app: app.get("/counter"),
+ }
+
+ # With additional FastAPI parameters:
+ endpoint_map = {
+ "increment": lambda app: app.post("/increment", status_code=201, tags=["counter"]),
+ "get_counter": lambda app: app.get("/counter", response_model=CounterResponse),
+ }
+ """
+
+ if app is None:
+ app = init()
+
+ if endpoint_map is None:
+ endpoint_map = DEFAULT_ENDPOINTS
+
+ # Create a new class that inherits from the original to avoid modifying it
+ # in-place. We populate the new class's __dict__ with decorated methods.
+ class_dict = {}
+
+ # Apply route decorators to the class methods and store them in class_dict
+ for method_name, route_factory in endpoint_map.items():
+ # Get the route decorator from the lambda
+ route_decorator = route_factory(app)
+ # Get the original method from the class
+ original_method = getattr(cls, method_name)
+ # Apply the decorator to the original method
+ decorated_method = route_decorator(original_method)
+ # Store in the class dict so it will be properly bound to new_cls
+ class_dict[method_name] = decorated_method
+
+ # Create new class with the decorated methods in its __dict__.
+ # IMPORTANT: We keep the same __name__ and __qualname__ as the original
+ # class so that make_fastapi_class_based_view can properly identify the routes
+ # (it checks if cls.__qualname__ is in route.endpoint.__qualname__).
+ new_cls = type(cls.__name__, (cls,), class_dict)
+ new_cls.__qualname__ = cls.__qualname__
+
+ # Apply the serve.ingress decorator to the new class
+ return serve.ingress(app)(new_cls)
+
+
+def _apply_openai_json_format(
+ response: Union[StreamResponseType, BatchedStreamResponseType],
+) -> str:
+ """Converts the stream response to OpenAI format.
+
+ Each model response is converted to the string:
+ data: \n\n
+
+ The converted strings are concatenated and returned:
+ data: \n\ndata: \n\n...
+ """
+ if isinstance(response, list):
+ first_response = next(iter(response))
+ if isinstance(first_response, str):
+ return "".join(response)
+ if isinstance(first_response, dict):
+ return "".join(f"data: {json.dumps(r)}\n\n" for r in response)
+ if hasattr(first_response, "model_dump_json"):
+ return "".join(f"data: {r.model_dump_json()}\n\n" for r in response)
+ raise ValueError(
+ f"Unexpected response type: {type(first_response)}, {first_response=}"
+ )
+ if hasattr(response, "model_dump_json"):
+ return f"data: {response.model_dump_json()}\n\n"
+ if isinstance(response, str):
+ return response
+ raise ValueError(f"Unexpected response type: {type(response)}, {response=}")
+
+
+async def _peek_at_generator(
+ gen: AsyncGenerator[T, None],
+) -> Tuple[T, AsyncGenerator[T, None]]:
+ # Peek at the first element
+ first_item = await gen.__anext__()
+
+ # Create a new generator that yields the peeked item first
+ async def new_generator() -> AsyncGenerator[T, None]:
+ yield first_item
+ async for item in gen:
+ yield item
+
+ return first_item, new_generator()
+
+
+async def _openai_json_wrapper(
+ generator: AsyncGenerator[
+ Union[StreamResponseType, BatchedStreamResponseType], None
+ ],
+) -> AsyncGenerator[str, None]:
+ """Wrapper that converts stream responses into OpenAI JSON strings.
+
+ Args:
+ generator: an async generator that yields either individual stream responses
+ (StreamResponseType) or batches of stream responses (BatchedStreamResponseType).
+ Each response is converted into OpenAI JSON format and streamed to the client.
+ For batched responses, the items are concatenated together as a single string.
+
+ Yields:
+ String chunks in OpenAI SSE format: "data: {json}\n\n", with a final
+ "data: [DONE]\n\n" to indicate completion.
+ """
+ async for response in generator:
+ packet = _apply_openai_json_format(response)
+ yield packet
+
+ yield "data: [DONE]\n\n"
+
+
+@asynccontextmanager
+async def router_request_timeout(timeout_duration: float):
+ try:
+ async with timeout(timeout_duration):
+ yield
+ except asyncio.TimeoutError as e:
+ raise OpenAIHTTPException(
+ status_code=status.HTTP_408_REQUEST_TIMEOUT,
+ message="Request server side timeout",
+ internal_message=str(e),
+ )
+
+
+class OpenAiIngress(DeploymentProtocol):
+ def __init__(
+ self,
+ llm_deployments: List[DeploymentHandle],
+ *,
+ _get_lora_model_metadata_func: Optional[
+ Callable[[str, LLMConfig], Awaitable[Dict[str, Any]]]
+ ] = None,
+ ):
+ self._default_serve_handles: Dict[str, DeploymentHandle] = {}
+ self._llm_configs: Dict[str, LLMConfig] = {}
+
+ # Configuring a ServeHandle with .options() creates a new ServeHandle
+ # object, which contains a new metrics pusher and long-polling call.
+ # Creating too many ServeHandles can impact event-loop and Serve Controller
+ # performance, so we save configured ServeHandles here and reuse them.
+ self._configured_serve_handles: Dict[str, DeploymentHandle] = {}
+ self._get_lora_model_metadata_func = (
+ _get_lora_model_metadata_func or self._default_get_lora_model_metadata_func
+ )
+
+ # Setup _default_serve_handles and _llm_configs asynchronously.
+ self._init_completed = asyncio.Event()
+ self.running_setup_task = get_or_create_event_loop().create_task(
+ self._setup_handle_and_config_maps(llm_deployments=llm_deployments)
+ )
+
+ async def _default_get_lora_model_metadata_func(
+ self, model_id: str, llm_config: LLMConfig
+ ) -> Dict[str, Any]:
+ return await get_lora_model_metadata(model_id, llm_config)
+
+ async def _setup_handle_and_config_maps(
+ self, llm_deployments: List[DeploymentHandle]
+ ):
+ for handle in llm_deployments:
+ llm_config = await handle.llm_config.remote()
+ self._default_serve_handles[llm_config.model_id] = handle
+ self._llm_configs[llm_config.model_id] = llm_config
+
+ # Note (genesu): Even though we have already checked model id uniqueness in
+ # `router_application()` under run.py. When we OSS this router component, users
+ # would be able to directly use the lower level api and bypass that check. We
+ # check it again here to ensure all the model ids are unique.
+ if len(llm_deployments) != len(self._llm_configs):
+ raise ValueError("Duplicate models found. Make sure model ids are unique.")
+
+ self._init_completed.set()
+
+ async def check_health(self):
+ await self._init_completed.wait()
+
+ def _get_configured_serve_handle(self, model_id: str):
+ """Gets a ServeHandle to a model deployment.
+
+ Configures the handle's options, and stores it in a cache.
+
+ If the model_id includes LoRA suffix, we set the model ID as
+ the multiplexed_model_id, so the request uses Serve's multiplexed
+ routing logic.
+
+ If the model_id is a base model- even if the model has LoRA
+ adapters- we don't set multiplexed_model_id. Setting
+ multiplexed_model_id would cause base model requests to be
+ sent to a single model replica, instead of being load
+ balanced across all replicas. This is undesirable for base
+ model requests (unlike LoRA requests) because all the replicas
+ have a copy of the base model.
+ """
+
+ if model_id not in self._configured_serve_handles:
+ base_model_id = get_base_model_id(model_id)
+ if base_model_id in self._default_serve_handles:
+ if model_id == base_model_id:
+ default_handle = self._default_serve_handles[model_id]
+ configured_handle = default_handle.options(stream=True)
+ self._configured_serve_handles[model_id] = configured_handle
+ else:
+ default_handle = self._default_serve_handles[base_model_id]
+ configured_handle = default_handle.options(
+ stream=True,
+ multiplexed_model_id=model_id,
+ )
+ self._configured_serve_handles[model_id] = configured_handle
+ else:
+ raise HTTPException(
+ status.HTTP_404_NOT_FOUND,
+ f'Could not find model with id "{model_id}".',
+ )
+
+ return self._configured_serve_handles[model_id]
+
+ async def _get_response(
+ self,
+ *,
+ body: Union[
+ CompletionRequest,
+ ChatCompletionRequest,
+ EmbeddingRequest,
+ TranscriptionRequest,
+ ScoreRequest,
+ ],
+ call_method: str,
+ ) -> AsyncGenerator[
+ Union[
+ LLMChatResponse,
+ LLMCompletionsResponse,
+ LLMEmbeddingsResponse,
+ LLMTranscriptionResponse,
+ LLMScoreResponse,
+ ],
+ None,
+ ]:
+ """Calls the model deployment and returns the stream."""
+ model: str = body.model
+ base_model_id = get_base_model_id(model)
+ if base_model_id not in self._llm_configs:
+ raise HTTPException(
+ status.HTTP_404_NOT_FOUND,
+ f'Got request for model "{model}". '
+ f'Could not find base model with ID "{base_model_id}".',
+ )
+
+ model_handle = self._get_configured_serve_handle(model)
+
+ # TODO(seiji): Remove when we update to Pydantic v2.11+ with the fix
+ # for tool calling ValidatorIterator serialization issue.
+ if isinstance(body, ChatCompletionRequest):
+ body = _sanitize_chat_completion_request(body)
+
+ async for response in getattr(model_handle, call_method).remote(body):
+ yield response
+
+ async def model(self, model_id: str) -> Optional[ModelCard]:
+ if model_id in self._llm_configs:
+ return to_model_metadata(model_id, self._llm_configs[model_id])
+
+ base_model_id = get_base_model_id(model_id)
+ if (
+ base_model_id in self._llm_configs
+ and self._llm_configs[base_model_id].lora_config
+ ):
+ try:
+ overrides = await self._get_lora_model_metadata_func(
+ model_id, self._llm_configs[base_model_id]
+ )
+
+ return to_model_metadata(
+ model_id=model_id,
+ model_config=self._llm_configs[base_model_id],
+ overrides=overrides,
+ )
+ except HTTPException:
+ logger.exception(
+ "Unable to retrieve LoRA adapter config file for "
+ f'"{model_id}". Omitting it from list of available models. '
+ "Check that adapter config file exists in cloud bucket."
+ )
+
+ async def models(self) -> ModelList:
+ """OpenAI API-compliant endpoint to get all rayllm models."""
+ all_models = dict()
+ for base_model_id, llm_config in self._llm_configs.items():
+ # Add the base model.
+ all_models[base_model_id] = await self.model(base_model_id)
+
+ if llm_config.lora_config is not None:
+ # Add all the fine-tuned models.
+ lora_model_ids = get_lora_model_ids(
+ dynamic_lora_loading_path=llm_config.lora_config.dynamic_lora_loading_path,
+ base_model_id=base_model_id,
+ )
+ for lora_id in lora_model_ids:
+ model_data = await self.model(lora_id)
+ if model_data is not None:
+ all_models[lora_id] = model_data
+
+ return ModelList(data=list(all_models.values()))
+
+ async def model_data(self, model: str) -> ModelCard:
+ """OpenAI API-compliant endpoint to get one rayllm model.
+
+ :param model: The model ID (e.g. "amazon/LightGPT")
+ """
+ model = replace_prefix(model)
+ model_data = await self.model(model)
+ if model_data is None:
+ raise OpenAIHTTPException(
+ message=f"Unable to find {model}. Please ensure that the model exists and you have permission.",
+ status_code=status.HTTP_404_NOT_FOUND,
+ type="InvalidModel",
+ )
+ return model_data
+
+ async def _process_llm_request(
+ self,
+ body: Union[CompletionRequest, ChatCompletionRequest, TranscriptionRequest],
+ call_method: str,
+ ) -> Response:
+
+ async with router_request_timeout(DEFAULT_LLM_ROUTER_HTTP_TIMEOUT):
+
+ gen = self._get_response(body=body, call_method=call_method)
+
+ # In streaming with batching enabled, this first response can be a list of chunks.
+ initial_response, gen = await _peek_at_generator(gen)
+
+ if isinstance(initial_response, list):
+ first_chunk = initial_response[0]
+ else:
+ first_chunk = initial_response
+
+ if isinstance(first_chunk, ErrorResponse):
+ raise OpenAIHTTPException(
+ message=first_chunk.error.message,
+ status_code=first_chunk.error.code,
+ type=first_chunk.error.type,
+ )
+
+ if isinstance(first_chunk, NON_STREAMING_RESPONSE_TYPES):
+ # Not streaming, first chunk should be a single response
+ return JSONResponse(content=first_chunk.model_dump())
+
+ # In case of streaming we need to iterate over the chunks and yield them
+ openai_stream_generator = _openai_json_wrapper(gen)
+
+ return StreamingResponse(
+ openai_stream_generator, media_type="text/event-stream"
+ )
+
+ async def completions(self, body: CompletionRequest) -> Response:
+ """Given a prompt, the model will return one or more predicted completions,
+ and can also return the probabilities of alternative tokens at each position.
+
+ Args:
+ body: The CompletionRequest object.
+
+ Returns:
+ A response object with completions.
+ """
+ return await self._process_llm_request(
+ body, call_method=CallMethod.COMPLETIONS.value
+ )
+
+ async def chat(self, body: ChatCompletionRequest) -> Response:
+ """Given a prompt, the model will return one or more predicted completions,
+ and can also return the probabilities of alternative tokens at each position.
+
+ Args:
+ body: The ChatCompletionRequest object.
+
+ Returns:
+ A response object with completions.
+ """
+
+ return await self._process_llm_request(body, call_method=CallMethod.CHAT.value)
+
+ async def embeddings(self, body: EmbeddingRequest) -> Response:
+ """Create embeddings for the provided input.
+
+ Args:
+ body: The EmbeddingRequest object.
+
+ Returns:
+ A response object with embeddings.
+ """
+ async with router_request_timeout(DEFAULT_LLM_ROUTER_HTTP_TIMEOUT):
+ results = self._get_response(body=body, call_method="embeddings")
+ result = await results.__anext__()
+ if isinstance(result, ErrorResponse):
+ raise OpenAIHTTPException(
+ message=result.error.message,
+ status_code=result.error.code,
+ type=result.error.type,
+ )
+
+ if isinstance(result, EmbeddingResponse):
+ return JSONResponse(content=result.model_dump())
+
+ # Annotated[..., Form()] is wrapper that is used to handle multiple form data, which is how audio is sent in transcription requests.
+ # vLLM implementation for handling transcription requests: https://github.com/vllm-project/vllm/blob/0825197bee8dea547f2ab25f48afd8aea0cd2578/vllm/entrypoints/openai/api_server.py#L839.
+ async def transcriptions(
+ self, body: Annotated[TranscriptionRequest, Form()]
+ ) -> Response:
+ """Create transcription for the provided audio input.
+
+ Args:
+ body: The TranscriptionRequest object.
+
+ Returns:
+ A response object with transcriptions.
+ """
+
+ return await self._process_llm_request(
+ body, call_method=CallMethod.TRANSCRIPTIONS.value
+ )
+
+ async def score(self, body: ScoreRequest) -> Response:
+ """Create scores for the provided text pairs.
+
+ Note: This is a vLLM specific endpoint.
+
+ Args:
+ body: The score request containing input text pairs to score.
+
+ Returns:
+ A response object with scores.
+ """
+
+ async with router_request_timeout(DEFAULT_LLM_ROUTER_HTTP_TIMEOUT):
+ results = self._get_response(body=body, call_method="score")
+ result = await results.__anext__()
+ if isinstance(result, ErrorResponse):
+ raise OpenAIHTTPException(
+ message=result.message,
+ status_code=result.code,
+ type=result.type,
+ )
+
+ if isinstance(result, ScoreResponse):
+ return JSONResponse(content=result.model_dump())
+
+ @classmethod
+ def get_deployment_options(
+ cls, llm_configs: Optional[List[LLMConfig]] = None
+ ) -> Dict[str, Any]:
+ """Get the deployment options for the ingress deployment.
+
+ Args:
+ llm_configs: The LLM configs to infer the number of ingress replicas from.
+
+ Returns:
+ A dictionary containing the deployment options for the ingress deployment.
+ """
+ return DEFAULT_INGRESS_OPTIONS
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/middleware.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/middleware.py
new file mode 100644
index 0000000000000000000000000000000000000000..dcb0021cd97f33b5776dfc82a0c0526f9139e28a
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/ingress/middleware.py
@@ -0,0 +1,165 @@
+import uuid
+from asyncio import CancelledError
+from typing import Optional
+
+from fastapi import FastAPI, Request, status
+from fastapi.exceptions import RequestValidationError
+from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
+from starlette.responses import JSONResponse, Response
+
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.llm._internal.serve.utils.server_utils import (
+ get_response_for_error,
+)
+
+logger = get_logger(__file__)
+
+
+def get_request_id(request) -> str:
+ """Fetches request-id from Starlette's request object.
+
+ NOTE: This method relies on "request_id" value to be injected into the
+ Starlette's `request.state` via `inject_request_id` middleware
+
+ :param request: Starlette request object
+ :return: (optional) Id allowing to identify particular user
+ """
+ return getattr(request.state, "request_id", None)
+
+
+async def _handle_validation_error(
+ request: Request, exc: RequestValidationError
+) -> JSONResponse:
+ """Handle pydantic validation errors in an OpenAI-like format."""
+ error_details = exc.errors()[0] if exc.errors() else {"msg": "Invalid request"}
+
+ error_msg = error_details.get("msg", "Unknown validation error")
+ error_loc = error_details.get("loc", ("body"))
+ error_input = error_details.get("input", None)
+ msg = f"Invalid request format: {error_msg} at {error_loc}"
+
+ error_response = {
+ "error": {
+ "message": msg,
+ "type": error_details.get("type", "invalid_request_error"),
+ "param": error_input,
+ "code": "invalid_parameter",
+ }
+ }
+
+ return JSONResponse(status_code=status.HTTP_400_BAD_REQUEST, content=error_response)
+
+
+def _uncaught_exception_handler(request: Request, e: Exception):
+ """This method serves as an uncaught exception handler being
+ the last resort to return properly formatted response.
+
+ NOTE: Exceptions from application handlers should NOT be reaching this point,
+ this handler is here to intercept "fly-away" exceptions and should not
+ be handled for handling of converting application exceptions into
+ appropriate responses
+ """
+
+ if isinstance(e, CancelledError):
+ return JSONResponse(content={}, status_code=204)
+
+ request_id = get_request_id(request)
+
+ logger.error(f"Uncaught exception while handling request {request_id}", exc_info=e)
+
+ error_response = get_response_for_error(e, request_id)
+
+ return JSONResponse(
+ content=error_response.model_dump(), status_code=error_response.error.code
+ )
+
+
+def add_exception_handling_middleware(router: FastAPI):
+ # NOTE: PLEASE READ CAREFULLY BEFORE CHANGING
+ #
+ # Starlette has different behavior depending on the Exception class being handled
+ # that we unfortunately have to take into account here:
+ #
+ # - Handler for `Exception` will be added as uncaught exception handler (of last resort)
+ # that is going to be executed absolute last, making sure that in case of any fly-away
+ # (uncaught) exception
+ # - Handlers for any other classes of exceptions will be executed as last middleware layer,
+ # therefore being to intercept any exceptions originating from the handler before it
+ # propagates to the middleware above it
+ #
+ # As such we're aiming for 2 goals here:
+ # - Intercepting exceptions from the handlers, converting them into proper user-facing
+ # response (avoiding exception propagation up the middleware stack)
+ # - Adding uncaught exception handler (of last resort) to intercept any exceptions that
+ # might be originating from the middleware itself
+
+ async def _handle_application_exceptions(
+ request: Request, call_next: RequestResponseEndpoint
+ ) -> Response:
+ """This method intercepts application level exceptions not handled by the
+ application code converting them into appropriately formatted (JSON) response
+ """
+
+ try:
+ return await call_next(request)
+ except CancelledError as ce:
+ # NOTE: We re-raise CancelledError as is to let other middleware handle it.
+ # Since no response is expected in this case, it's deferred to uncaught
+ # exception handler to ultimately handle it
+ raise ce
+ except RequestValidationError as e:
+ return await _handle_validation_error(request, e)
+ except Exception as e:
+ request_id = get_request_id(request)
+ error_response = get_response_for_error(e, request_id)
+
+ return JSONResponse(
+ content=error_response.model_dump(),
+ status_code=error_response.error.code,
+ )
+
+ # This adds last-resort uncaught exception handler into Starlette
+ router.add_exception_handler(Exception, _uncaught_exception_handler)
+ # Add validation error handler
+ router.add_exception_handler(RequestValidationError, _handle_validation_error)
+ # This adds application exception handler, allowing to convert application
+ # exceptions into properly formatted responses
+ router.add_middleware(
+ BaseHTTPMiddleware,
+ dispatch=_handle_application_exceptions,
+ )
+
+
+class SetRequestIdMiddleware:
+ """Injects request ID into the request's state.
+
+ The ID is either:
+ 1. the value of the request's "x-request-id" header, set by Ray
+ Serve's Proxy, or
+ 2. if "x-request-id" header is unavailable, this middleware creates
+ a UUIDv4 request ID.
+ """
+
+ def __init__(self, app):
+ self.app = app
+
+ async def __call__(self, scope, receive, send):
+ if scope["type"] == "http":
+ request = Request(scope)
+ request.state.request_id = request.headers.get("x-request-id", None) or str(
+ uuid.uuid4()
+ )
+
+ return await self.app(scope, receive, send)
+
+
+def get_user_id(request) -> Optional[str]:
+ """Fetches user id inside Starlette's request object.
+
+ NOTE: This method relies on "user_id" value to be injected into the
+ Starlette's `request.state` via authentication middleware
+
+ :param request: Starlette request object
+ :return: (optional) Id identifying particular user
+ """
+ return getattr(request.state, "user_id", None)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/protocol.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/protocol.py
new file mode 100644
index 0000000000000000000000000000000000000000..cc468c86179f411e76f770e3d91ef838f1433603
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/protocol.py
@@ -0,0 +1,78 @@
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ AsyncGenerator,
+ Dict,
+ List,
+ Optional,
+ Protocol,
+ Union,
+)
+
+if TYPE_CHECKING:
+ from ray.llm._internal.serve.core.configs.llm_config import LLMConfig
+ from ray.llm._internal.serve.core.configs.openai_api_models import (
+ ChatCompletionRequest,
+ ChatCompletionResponse,
+ CompletionRequest,
+ CompletionResponse,
+ ErrorResponse,
+ )
+
+
+class DeploymentProtocol(Protocol):
+ @classmethod
+ def get_deployment_options(cls, **kwargs) -> Dict[str, Any]:
+ """Get the default deployment options for the this deployment."""
+
+
+class LLMServerProtocol(DeploymentProtocol):
+ """
+ This is the common interface between all the llm deployment. All llm deployments
+ need to implement a sync constructor, an async start method, and check_health method.
+ """
+
+ def __init__(self):
+ """
+ Constructor takes basic setup that doesn't require async operations.
+ """
+
+ async def start(self) -> None:
+ """
+ Start the underlying engine. This handles async initialization.
+ """
+
+ async def chat(
+ self, request: "ChatCompletionRequest"
+ ) -> AsyncGenerator[Union[str, "ChatCompletionResponse", "ErrorResponse"], None]:
+ """
+ Inferencing to the engine for chat, and return the response.
+ """
+
+ async def completions(
+ self, request: "CompletionRequest"
+ ) -> AsyncGenerator[
+ Union[List[Union[str, "ErrorResponse"]], "CompletionResponse"], None
+ ]:
+ """
+ Inferencing to the engine for completion api, and return the response.
+ """
+
+ async def check_health(self) -> None:
+ """
+ Check the health of the replica. Does not return anything.
+ Raise error when the engine is dead and needs to be restarted.
+ """
+
+ async def reset_prefix_cache(self) -> None:
+ """Reset the prefix cache of the underlying engine"""
+
+ async def start_profile(self) -> None:
+ """Start profiling"""
+
+ async def stop_profile(self) -> None:
+ """Stop profiling"""
+
+ # TODO (Kourosh): This does not belong here.
+ async def llm_config(self) -> Optional["LLMConfig"]:
+ """Get the LLM config"""
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..656530bd6439130bc1357e1d28a49b395ddc170f
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__pycache__/builder.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__pycache__/builder.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c0d2e536e26a2dc2a39ca5de16bf0509cb7ded5d
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__pycache__/builder.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__pycache__/llm_server.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__pycache__/llm_server.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..37d2ff989641869418adf77768cdc54e6ff184c8
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/__pycache__/llm_server.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/builder.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/builder.py
new file mode 100644
index 0000000000000000000000000000000000000000..032e195784a2f28aa7c8cab7362f269a580ca92d
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/builder.py
@@ -0,0 +1,78 @@
+import pprint
+from typing import Optional, Type
+
+from ray import serve
+from ray.llm._internal.common.dict_utils import deep_merge_dicts
+from ray.llm._internal.serve.constants import (
+ DEFAULT_HEALTH_CHECK_PERIOD_S,
+ DEFAULT_HEALTH_CHECK_TIMEOUT_S,
+ DEFAULT_MAX_ONGOING_REQUESTS,
+)
+from ray.llm._internal.serve.core.configs.llm_config import (
+ LLMConfig,
+)
+from ray.llm._internal.serve.core.server.llm_server import LLMServer
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.serve.deployment import Application
+
+logger = get_logger(__name__)
+
+
+DEFAULT_DEPLOYMENT_OPTIONS = {
+ "max_ongoing_requests": DEFAULT_MAX_ONGOING_REQUESTS,
+ "health_check_period_s": DEFAULT_HEALTH_CHECK_PERIOD_S,
+ "health_check_timeout_s": DEFAULT_HEALTH_CHECK_TIMEOUT_S,
+}
+
+
+def _get_deployment_name(llm_config: LLMConfig) -> str:
+ return llm_config.model_id.replace("/", "--").replace(".", "_")
+
+
+def build_llm_deployment(
+ llm_config: LLMConfig,
+ *,
+ name_prefix: Optional[str] = None,
+ bind_kwargs: Optional[dict] = None,
+ override_serve_options: Optional[dict] = None,
+ deployment_cls: Optional[Type[LLMServer]] = None,
+) -> Application:
+ """Build an LLMServer deployment.
+
+ Args:
+ llm_config: The LLMConfig to build the deployment.
+ name_prefix: The prefix to add to the deployment name.
+ bind_kwargs: The optional extra kwargs to pass to the deployment.
+ Used for customizing the deployment.
+ override_serve_options: The optional serve options to override the
+ default options.
+ deployment_cls: The deployment class to use. Defaults to LLMServer.
+
+ Returns:
+ The Ray Serve Application for the LLMServer deployment.
+ """
+ deployment_cls = deployment_cls or LLMServer
+ name_prefix = name_prefix or f"{deployment_cls.__name__}:"
+ bind_kwargs = bind_kwargs or {}
+
+ deployment_options = deployment_cls.get_deployment_options(llm_config)
+
+ # Set the name of the deployment config to map to the model ID.
+ deployment_name = deployment_options.get("name", _get_deployment_name(llm_config))
+
+ if name_prefix:
+ deployment_options["name"] = name_prefix + deployment_name
+
+ if override_serve_options:
+ deployment_options.update(override_serve_options)
+
+ deployment_options = deep_merge_dicts(
+ DEFAULT_DEPLOYMENT_OPTIONS, deployment_options
+ )
+
+ logger.info("============== Deployment Options ==============")
+ logger.info(pprint.pformat(deployment_options))
+
+ return serve.deployment(deployment_cls, **deployment_options).bind(
+ llm_config=llm_config, **bind_kwargs
+ )
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/llm_server.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/llm_server.py
new file mode 100644
index 0000000000000000000000000000000000000000..0da17151cc12fe1b391b31956d6ae851e504d6cf
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/core/server/llm_server.py
@@ -0,0 +1,523 @@
+import asyncio
+import copy
+import os
+from typing import (
+ TYPE_CHECKING,
+ Any,
+ AsyncGenerator,
+ Dict,
+ List,
+ Optional,
+ Type,
+ TypeVar,
+ Union,
+)
+
+import ray
+from ray import serve
+from ray._common.utils import import_attr
+from ray.llm._internal.serve.constants import (
+ ENABLE_WORKER_PROCESS_SETUP_HOOK,
+ ENGINE_START_TIMEOUT_S,
+ MODEL_RESPONSE_BATCH_TIMEOUT_MS,
+ RAYLLM_VLLM_ENGINE_CLS_ENV,
+)
+from ray.llm._internal.serve.core.configs.llm_config import (
+ DiskMultiplexConfig,
+ LLMConfig,
+)
+from ray.llm._internal.serve.core.engine.protocol import LLMEngine
+from ray.llm._internal.serve.core.protocol import LLMServerProtocol
+from ray.llm._internal.serve.engines.vllm.vllm_engine import VLLMEngine
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.llm._internal.serve.observability.usage_telemetry.usage import (
+ push_telemetry_report_for_all_models,
+)
+from ray.llm._internal.serve.utils.batcher import Batcher
+from ray.llm._internal.serve.utils.lora_serve_utils import (
+ LoraModelLoader,
+)
+from ray.llm._internal.serve.utils.server_utils import (
+ get_serve_request_id,
+)
+
+if TYPE_CHECKING:
+ from ray.llm._internal.serve.core.configs.openai_api_models import (
+ ChatCompletionRequest,
+ ChatCompletionResponse,
+ CompletionRequest,
+ CompletionResponse,
+ EmbeddingRequest,
+ EmbeddingResponse,
+ ErrorResponse,
+ ScoreRequest,
+ ScoreResponse,
+ TranscriptionRequest,
+ TranscriptionResponse,
+ )
+
+logger = get_logger(__name__)
+T = TypeVar("T")
+
+
+def _merge_replica_actor_and_child_actor_bundles(
+ child_actor_bundles: List[Dict[str, float]],
+ replica_actor_bundle: Dict[str, float],
+) -> List[Dict[str, float]]:
+ """Sum up the bundles from replica actor bundles with the first bundle from child actor bundles.
+
+ This is because the replica actor will use the first bundle in the list, and we want to collocate the replica actor with the child actor.
+ So we need to group them together.
+
+ So for example:
+ child_actor_bundles = [{"GPU": 1, "CPU": 1}, {"GPU": 1, "CPU": 1}]
+ replica_actor_bundle = {"GPU": 0, "CPU": 1, "memory": 100}
+ return [{"GPU": 1, "CPU": 2, "memory": 100}, {"GPU": 1, "CPU": 1}]
+ """
+
+ if not child_actor_bundles:
+ return [copy.copy(replica_actor_bundle)]
+
+ if not replica_actor_bundle:
+ return [copy.copy(bundle) for bundle in child_actor_bundles]
+
+ original_first_bundle = child_actor_bundles[0]
+ bundle_key_set = set(original_first_bundle.keys()) | set(
+ replica_actor_bundle.keys()
+ )
+
+ merged_first_bundle = {
+ key: original_first_bundle.get(key, 0) + replica_actor_bundle.get(key, 0)
+ for key in bundle_key_set
+ }
+
+ return [merged_first_bundle] + [
+ copy.copy(bundle) for bundle in child_actor_bundles[1:]
+ ]
+
+
+class LLMServer(LLMServerProtocol):
+ """This is a shim layer to decouple the LLM engine from the ingress
+ deployment.
+
+ It has a very similar API as the engine. Almost all of the abstractions are
+ implemented by the engine. This class just a little bit more logic on top:
+
+ 1. Logic for serve multiplexing (e.g. LoRA loading).
+ 2. Request id handing from serve context.
+ 3. Batching in case of streaming (only for chat and completions).
+ 4. Telemetry reporting.
+
+ Usage Patterns:
+
+ 1. Basic pattern (for testing):
+ server = LLMServer.sync_init(llm_config) # Sync constructor, unstarted
+ await server.start() # Must explicitly start
+
+ 2. Async context (default, used by Ray Serve):
+ server = await LLMServer(llm_config) # Async constructor, fully started
+
+ 3. Ray Serve deployment:
+ # Ray Serve calls the async constructor directly
+ deployment = serve.deployment(LLMServer).bind(llm_config)
+ """
+
+ _default_engine_cls = VLLMEngine
+
+ async def __init__(
+ self,
+ llm_config: LLMConfig,
+ *,
+ engine_cls: Optional[Type[LLMEngine]] = None,
+ model_downloader: Optional[Type[LoraModelLoader]] = None,
+ ):
+ """Asynchronous constructor that returns a fully started instance.
+
+ This is the default constructor used by Ray Serve deployments.
+
+ Args:
+ llm_config: LLMConfig for the model.
+ engine_cls: Dependency injection for the vllm engine class.
+ Defaults to `VLLMEngine`.
+ model_downloader: Dependency injection for the model downloader.
+ Defaults to `LoraModelLoader`.
+ """
+ super().__init__()
+ self._init_shared(llm_config, engine_cls, model_downloader)
+ await self.start()
+
+ def _init_shared(
+ self,
+ llm_config: LLMConfig,
+ engine_cls: Optional[Type[LLMEngine]] = None,
+ model_downloader: Optional[Type[LoraModelLoader]] = None,
+ ):
+ """Shared initialization logic between constructors."""
+ self._llm_config = llm_config
+ self._engine_cls = engine_cls or self._get_default_engine_class()
+ self.engine: Optional[LLMEngine] = None
+ self._init_multiplex_loader(model_downloader)
+
+ @classmethod
+ def sync_init(
+ cls,
+ llm_config: LLMConfig,
+ *,
+ engine_cls: Optional[Type[LLMEngine]] = None,
+ model_downloader: Optional[Type[LoraModelLoader]] = None,
+ ) -> "LLMServer":
+ """Synchronous constructor that returns an unstarted instance.
+
+ This is used for testing the new pattern where initialization
+ and starting are explicitly separated.
+
+ Args:
+ llm_config: LLMConfig for the model.
+ engine_cls: Dependency injection for the vllm engine class.
+ Defaults to `VLLMEngine`.
+ model_downloader: Dependency injection for the model downloader.
+ Defaults to `LoraModelLoader`.
+
+ Returns:
+ An unstarted LLMServer instance. Caller must call await start().
+ """
+ instance = cls.__new__(cls)
+ LLMServerProtocol.__init__(instance)
+ instance._init_shared(llm_config, engine_cls, model_downloader)
+ return instance
+
+ async def start(self):
+ """Start the underlying engine. This handles async initialization."""
+ if self._engine_cls is not None:
+ self.engine = self._engine_cls(self._llm_config)
+ await asyncio.wait_for(self._start_engine(), timeout=ENGINE_START_TIMEOUT_S)
+
+ def _init_multiplex_loader(
+ self, model_downloader_cls: Optional[Type[LoraModelLoader]] = None
+ ):
+ """Initialize the multiplex loader."""
+
+ model_downloader_cls = model_downloader_cls or LoraModelLoader
+ mx_config = self._llm_config.multiplex_config()
+
+ if mx_config is not None:
+ model_downloader = model_downloader_cls(
+ download_timeout_s=mx_config.download_timeout_s,
+ max_tries=mx_config.max_download_tries,
+ )
+
+ async def _load_model(lora_model_id: str) -> DiskMultiplexConfig:
+ return await model_downloader.load_model_from_config(
+ lora_model_id=lora_model_id,
+ llm_config=self._llm_config,
+ )
+
+ self._load_model = serve.multiplexed(
+ max_num_models_per_replica=mx_config.max_num_models_per_replica
+ )(_load_model)
+ else:
+
+ async def _load_model(lora_model_id: str) -> DiskMultiplexConfig:
+ raise ValueError("LoRA config is not set in the LLMConfig")
+
+ self._load_model = _load_model
+
+ def _get_default_engine_class(self) -> Type[LLMEngine]:
+ """Helper to load the engine class from the environment variable.
+ This is used for testing or escape-hatch for patching purposes.
+ If env variable is not set, it will fallback to the default engine class.
+ """
+ engine_cls_path = os.environ.get(RAYLLM_VLLM_ENGINE_CLS_ENV)
+ if engine_cls_path:
+ return import_attr(engine_cls_path)
+ return self._default_engine_cls
+
+ async def _start_engine(self):
+ if self.engine is None:
+ raise ValueError("Engine is not set")
+
+ await self.engine.start()
+
+ # Push telemetry reports for the model in the current deployment.
+ push_telemetry_report_for_all_models(all_models=[self._llm_config])
+
+ def _get_batch_interval_ms(self, stream: bool = True) -> int:
+ """Calculate the batching interval for responses."""
+ stream_batching_interval_ms = self._llm_config.experimental_configs.get(
+ "stream_batching_interval_ms"
+ )
+ if stream_batching_interval_ms is None:
+ stream_batching_interval_ms = MODEL_RESPONSE_BATCH_TIMEOUT_MS
+ return stream_batching_interval_ms if stream else None
+
+ async def _maybe_add_request_id_to_request(
+ self,
+ request: Union[
+ "ChatCompletionRequest",
+ "CompletionRequest",
+ "EmbeddingRequest",
+ "TranscriptionRequest",
+ ],
+ ):
+ """Add the request id to the request."""
+ request_id = get_serve_request_id()
+ if request_id:
+ request.request_id = request_id
+
+ async def _maybe_resolve_lora_from_multiplex(self) -> None:
+ """Handle the lora model for the request."""
+ multiplexed_model_id = serve.get_multiplexed_model_id()
+ if multiplexed_model_id:
+ if self._llm_config.lora_config is None:
+ raise ValueError("Must setup lora config for multiplexed requests.")
+ disk_lora_model = await self._load_model(multiplexed_model_id)
+ await self.engine.resolve_lora(disk_lora_model)
+
+ def _batch_output_stream(
+ self, generator: AsyncGenerator[T, None]
+ ) -> AsyncGenerator[List[T], None]:
+ return Batcher(
+ generator,
+ interval_ms=self._get_batch_interval_ms(),
+ ).stream()
+
+ async def _run_request(
+ self,
+ request: Union[
+ "ChatCompletionRequest",
+ "CompletionRequest",
+ "EmbeddingRequest",
+ "TranscriptionRequest",
+ "ScoreRequest",
+ ],
+ *,
+ engine_method: str,
+ batch_output_stream: bool = False,
+ ) -> AsyncGenerator[Any, None]:
+ """Run the engine method on the request + perform batching when stream=True.
+
+ Args:
+ request: The request to run.
+ engine_method: The method to call on the engine.
+ batch_output_stream: Whether to batch the output stream.
+
+ Returns:
+ An AsyncGenerator of the response. If stream is True and batching is enabled, then the generator will yield a list of streaming responses (strings of the format data: {response_json}\n\n). Otherwise, it will yield the non-streaming response from engine directly.
+ """
+
+ await self._maybe_add_request_id_to_request(request)
+ await self._maybe_resolve_lora_from_multiplex()
+
+ is_stream = hasattr(request, "stream") and request.stream
+ if is_stream and batch_output_stream:
+ stream = self._batch_output_stream(
+ getattr(self.engine, engine_method)(request)
+ )
+ else:
+ stream = getattr(self.engine, engine_method)(request)
+
+ return stream
+
+ async def chat(
+ self, request: "ChatCompletionRequest"
+ ) -> AsyncGenerator[
+ Union[List[Union[str, "ErrorResponse"]], "ChatCompletionResponse"], None
+ ]:
+ """Runs a chat request to the LLM engine and returns the response.
+
+ Args:
+ request: A ChatCompletionRequest object.
+
+ Returns:
+ An AsyncGenerator of the response. If stream is True and batching is enabled, then the generator will yield a list of chat streaming responses (strings of the format data: {response_json}\n\n). Otherwise, it will yield the ChatCompletionResponse object directly.
+ """
+ return await self._run_request(
+ request,
+ engine_method="chat",
+ batch_output_stream=True,
+ )
+
+ async def completions(
+ self, request: "CompletionRequest"
+ ) -> AsyncGenerator[
+ Union[List[Union[str, "ErrorResponse"]], "CompletionResponse"], None
+ ]:
+ """Runs a completion request to the LLM engine and returns the response.
+
+ Args:
+ request: A CompletionRequest object.
+
+ Returns:
+ An AsyncGenerator of the response. If stream is True and batching is enabled, then the generator will yield a list of completion streaming responses (strings of the format data: {response_json}\n\n). Otherwise, it will yield the CompletionResponse object directly.
+ """
+ return await self._run_request(
+ request,
+ engine_method="completions",
+ batch_output_stream=True,
+ )
+
+ async def embeddings(
+ self, request: "EmbeddingRequest"
+ ) -> AsyncGenerator[Union[List["ErrorResponse"], "EmbeddingResponse"], None]:
+ """Runs an embeddings request to the engine and returns the response.
+
+ Returns an AsyncGenerator over the EmbeddingResponse object. This is so that the caller can have a consistent interface across all the methods of chat, completions, embeddings and transcriptions.
+
+ Args:
+ request: An EmbeddingRequest object.
+
+ Returns:
+ An AsyncGenerator over the EmbeddingResponse object.
+ """
+ # NOTE: Embeddings does not need batching.
+ return await self._run_request(
+ request,
+ engine_method="embeddings",
+ batch_output_stream=False,
+ )
+
+ async def transcriptions(
+ self, request: "TranscriptionRequest"
+ ) -> AsyncGenerator[
+ Union[List[Union[str, "ErrorResponse"]], "TranscriptionResponse"], None
+ ]:
+ """Runs an transcriptions request to the engine and returns the response.
+
+ Returns an AsyncGenerator over the TranscriptionResponse object. This is so that the caller can have a consistent interface across all the methods of chat, completions, embeddings and transcriptions.
+
+ Args:
+ request: An TranscriptionRequest object.
+
+ Returns:
+ An AsyncGenerator over the TranscriptionResponse object.
+ """
+ return await self._run_request(
+ request,
+ engine_method="transcriptions",
+ batch_output_stream=True,
+ )
+
+ async def score(
+ self, request: "ScoreRequest"
+ ) -> AsyncGenerator[Union["ScoreResponse", "ErrorResponse"], None]:
+ """Runs a score request to the engine and returns the response.
+
+ Returns an AsyncGenerator over the ScoreResponse object. This is so that the caller can have a consistent interface across all the methods of chat, completions, embeddings, and score.
+
+ Args:
+ request: A ScoreRequest object.
+
+ Returns:
+ An AsyncGenerator over the ScoreResponse object.
+ """
+ # NOTE: Score does not need batching, similar to embeddings.
+ return await self._run_request(
+ request, engine_method="score", batch_output_stream=False
+ )
+
+ async def check_health(self) -> None:
+ """
+ Check the health of the replica. Does not return anything. Raise error when
+ the engine is dead and needs to be restarted.
+ """
+ if self.engine is None:
+ return
+ try:
+ return await self.engine.check_health()
+ except Exception as e:
+ logger.error("Engine health check failed in LLMServer.check_health: %s", e)
+ raise e
+
+ async def reset_prefix_cache(self) -> None:
+ """Reset the prefix cache of the underlying engine"""
+ if self.engine is None:
+ return
+ try:
+ await self.engine.reset_prefix_cache()
+ except Exception as e:
+ logger.error(
+ "Engine reset prefix cache failed in LLMServer.reset_prefix_cache: %s",
+ e,
+ )
+ raise e
+
+ async def start_profile(self) -> None:
+ """Start profiling"""
+ if self.engine is None:
+ return
+ try:
+ await self.engine.start_profile()
+ except Exception as e:
+ logger.error(
+ "Engine start profile failed in LLMServer.start_profile: %s", e
+ )
+ raise e
+
+ async def stop_profile(self) -> None:
+ """Stop profiling"""
+ if self.engine is None:
+ return
+ try:
+ await self.engine.stop_profile()
+ except Exception as e:
+ logger.error("Engine stop profile failed in LLMServer.stop_profile: %s", e)
+ raise e
+
+ async def llm_config(self) -> Optional[LLMConfig]:
+ return self._llm_config
+
+ @classmethod
+ def get_deployment_options(cls, llm_config: "LLMConfig"):
+ engine_config = llm_config.get_engine_config()
+ deployment_options = copy.deepcopy(llm_config.deployment_config)
+
+ # Handle the ray_actor_options that could be passed in to
+ # deployment_options
+ ray_actor_options = deployment_options.get("ray_actor_options", {})
+
+ replica_actor_resources = {
+ "CPU": ray_actor_options.get("num_cpus", 1),
+ "GPU": ray_actor_options.get("num_gpus", 0),
+ **ray_actor_options.get("resources", {}),
+ }
+ if "memory" in ray_actor_options:
+ replica_actor_resources["memory"] = ray_actor_options["memory"]
+
+ if (
+ "placement_group_bundles" in llm_config.deployment_config
+ or "placement_group_strategy" in llm_config.deployment_config
+ ):
+ raise ValueError(
+ "placement_group_bundles and placement_group_strategy must not be specified in deployment_config. You can override the default values by setting the `placement_group_config` in the LLMConfig."
+ )
+
+ # TODO: Move this _merge_replica_actor_and_child_actor_bundles to a
+ # more generic place.
+ pg_bundles = _merge_replica_actor_and_child_actor_bundles(
+ engine_config.placement_bundles, replica_actor_resources
+ )
+
+ deployment_options.update(
+ {
+ "placement_group_bundles": pg_bundles,
+ "placement_group_strategy": engine_config.placement_strategy,
+ }
+ )
+
+ # Handle env vars from runtime_env
+ default_runtime_env = ray.get_runtime_context().runtime_env
+ if ENABLE_WORKER_PROCESS_SETUP_HOOK:
+ default_runtime_env[
+ "worker_process_setup_hook"
+ ] = "ray.llm._internal.serve._worker_process_setup_hook"
+
+ ray_actor_options = deployment_options.get("ray_actor_options", {})
+ ray_actor_options["runtime_env"] = {
+ **default_runtime_env,
+ # Existing runtime_env should take precedence over the default.
+ **ray_actor_options.get("runtime_env", {}),
+ **(llm_config.runtime_env if llm_config.runtime_env else {}),
+ }
+ deployment_options["ray_actor_options"] = ray_actor_options
+
+ return deployment_options
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1fe0209ade3c1e84bbf9f845a59b514f1bc65aef
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..9daffe5d3b9e86de588cd6d83578c6d87282b429
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__pycache__/vllm_engine.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__pycache__/vllm_engine.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..38cd1c285f71b84d810376d619864db8074ccc09
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__pycache__/vllm_engine.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__pycache__/vllm_models.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__pycache__/vllm_models.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..53ae67086cca84f8065c2903a310c6e6fda08346
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/__pycache__/vllm_models.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..f3fd0377f8e7680725208b45d6b23ecf5d6d9106
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__init__.py
@@ -0,0 +1,5 @@
+"""KV Transfer connector backends for Ray Serve LLM.
+
+This package provides connector backends for KV cache transfer in vLLM.
+All backends are lazily loaded through the factory to avoid circular imports.
+"""
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..792dae4e0423f4e3cc1a873c9300963a5a240955
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/base.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/base.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..799c53686d101601c8555d2f2fd7c442641095be
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/base.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/factory.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/factory.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..20f7348ab88205d2e0f89af8ec803498f1e45d0c
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/factory.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/lmcache.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/lmcache.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..c8096021f9e6af29082af93ce4fea2cebce742e7
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/lmcache.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/multi_connector.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/multi_connector.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d6c4ce104b625f6f2b19654206eb4c19d9931dc9
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/multi_connector.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/nixl.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/nixl.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..aed1cc65db0d5be5e8e71fb620a4eeeba4d2478f
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/__pycache__/nixl.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/base.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/base.py
new file mode 100644
index 0000000000000000000000000000000000000000..527d3fd7ec14d21345e5641b8434748e0129a045
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/base.py
@@ -0,0 +1,81 @@
+import abc
+import random
+import string
+from typing import TYPE_CHECKING, Any, Dict
+
+from ray import serve
+
+if TYPE_CHECKING:
+ from ray.llm._internal.serve.core.configs.llm_config import LLMConfig
+
+
+class BaseConnectorBackend(abc.ABC):
+ def __init__(self, llm_config: "LLMConfig"):
+ """Base class for connector backends.
+
+ Args:
+ llm_config: The llm configuration for this engine
+ """
+ self.llm_config = llm_config
+
+ @property
+ def kv_transfer_config(self) -> Dict[str, Any]:
+ engine_kwargs = self.llm_config.engine_kwargs
+ kv_transfer_config = engine_kwargs.get("kv_transfer_config")
+ assert (
+ kv_transfer_config is not None
+ ), "In Connector backend, kv_transfer_config is not set"
+ return kv_transfer_config
+
+ def _get_unique_suffix(self, len: int = 6) -> str:
+ """Generates unique alphanumeric suffix.
+
+ Args:
+ len: Length of the suffix to generate.
+ Returns:
+ A unique alphanumeric suffix string of specified length.
+ """
+ return "".join(random.choices(string.ascii_letters + string.digits, k=len))
+
+ def _compute_port_offset(self) -> int:
+ """Compute a deterministic port offset for this replica.
+
+ Uses data_parallel_rank if DP case, otherwise falls back to
+ the replica rank assigned by Ray Serve (TP/PP case).
+
+ For TP/PP cases, multiply by num_devices (tp × pp) to reserve
+ sufficient port space, since each worker needs a unique port.
+ Each TP worker adds its tp_rank (0, 1, ..., tp_size-1) to the
+ base port at bind time, and PP stages also need separate ports.
+
+ Returns:
+ Non-negative integer offset to add to a base port.
+ """
+ # Prefer explicit DP rank when available
+ dp_rank = self.llm_config.engine_kwargs.get("data_parallel_rank")
+ if isinstance(dp_rank, int) and dp_rank >= 0:
+ # vLLM already accounts for TP spacing in DP offset calculation
+ # (data_parallel_rank × tp_size), don't multiply here
+ return dp_rank
+
+ # Fall back to Serve replica rank for TP/PP cases
+ try:
+ rc = serve.get_replica_context()
+ if rc and hasattr(rc, "rank"):
+ # Use num_devices (tp × pp) to reserve ports for all workers
+ # Each replica spawns num_devices workers, each needing a unique port
+ engine_config = self.llm_config.get_engine_config()
+ num_devices = engine_config.num_devices
+ return rc.rank * num_devices
+ except Exception:
+ # Best-effort fallback; avoid introducing failures in setup paths
+ pass
+
+ return 0
+
+ def setup(self) -> None:
+ """Setup the connector backend.
+
+ This method is called to setup the connector backend.
+ """
+ pass
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/factory.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/factory.py
new file mode 100644
index 0000000000000000000000000000000000000000..742955e6f6e2d6ce7e424309858fd00183772dbe
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/factory.py
@@ -0,0 +1,136 @@
+"""Factory for lazy-loading KV connector backends.
+
+This module provides a factory pattern for registering and instantiating
+KV connector backends without eagerly importing all implementations.
+This avoids circular import issues and improves startup performance.
+"""
+
+from typing import TYPE_CHECKING, Type, Union
+
+from ray.llm._internal.serve.engines.vllm.kv_transfer.base import (
+ BaseConnectorBackend,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.llm._internal.serve.utils.registry import get_registry
+
+if TYPE_CHECKING:
+ from ray.llm._internal.serve.core.configs.llm_config import LLMConfig
+
+
+logger = get_logger(__name__)
+
+# Get the registry instance for KV connector backends
+_kv_backend_registry = get_registry("kv_connector_backend")
+
+
+class KVConnectorBackendFactory:
+ """Factory for creating KV connector backend instances with lazy loading."""
+
+ @classmethod
+ def register_backend(
+ cls,
+ name: str,
+ backend_class_or_path: Union[Type["BaseConnectorBackend"], str],
+ ) -> None:
+ """Register a connector backend.
+
+ This enables the backend to be accessed on every Ray process in the cluster.
+
+ Args:
+ name: The name of the connector (e.g., "LMCacheConnectorV1")
+ backend_class_or_path: Either:
+ - The backend class object directly (preferred), or
+ - A string in the format "module_path:class_name" for lazy loading
+
+ Examples:
+ # Register with class directly (recommended):
+ KVConnectorBackendFactory.register_backend("MyConnector", MyConnectorClass)
+
+ # Register with module path string (for lazy loading):
+ KVConnectorBackendFactory.register_backend("MyConnector", "my.module:MyClass")
+ """
+ _kv_backend_registry.register(name, backend_class_or_path)
+
+ @classmethod
+ def get_backend_class(cls, name: str) -> Type["BaseConnectorBackend"]:
+ """Get the connector backend class by name.
+
+ For registered connectors, returns the registered backend class.
+ For unregistered connectors, returns BaseConnectorBackend which has
+ a no-op setup() method, allowing connectors that don't require
+ Ray Serve orchestration to work without registration.
+
+ Args:
+ name: The name of the connector backend
+
+ Returns:
+ The connector backend class
+
+ Raises:
+ ImportError: If a registered backend fails to load
+ """
+ try:
+ return _kv_backend_registry.get(name)
+ except ValueError:
+ logger.warning(
+ f"Unsupported connector backend: {name}. "
+ f"Using default: {BaseConnectorBackend.__name__}."
+ )
+ return BaseConnectorBackend
+ except Exception as e:
+ raise ImportError(
+ f"Failed to load connector backend '{name}': {type(e).__name__}: {e}"
+ ) from e
+
+ @classmethod
+ def create_backend(
+ cls, name: str, llm_config: "LLMConfig"
+ ) -> "BaseConnectorBackend":
+ """Create a connector backend instance.
+
+ Args:
+ name: The name of the connector backend
+ llm_config: The LLM configuration
+
+ Returns:
+ An instance of the connector backend
+ """
+ return cls.get_backend_class(name)(llm_config)
+
+ @classmethod
+ def is_registered(cls, name: str) -> bool:
+ """Check if a connector backend is registered."""
+ return _kv_backend_registry.contains(name)
+
+ @classmethod
+ def unregister_backend(cls, name: str) -> None:
+ """Unregister a connector backend.
+
+ Removes the backend from the registry across all Ray processes.
+
+ Args:
+ name: The name of the connector backend to unregister
+ """
+ _kv_backend_registry.unregister(name)
+
+
+BUILTIN_BACKENDS = {
+ "LMCacheConnectorV1": "ray.llm._internal.serve.engines.vllm.kv_transfer.lmcache:LMCacheConnectorV1Backend",
+ "NixlConnector": "ray.llm._internal.serve.engines.vllm.kv_transfer.nixl:NixlConnectorBackend",
+ "MultiConnector": "ray.llm._internal.serve.engines.vllm.kv_transfer.multi_connector:MultiConnectorBackend",
+}
+
+
+def _initialize_registry() -> None:
+ """Initialize the registry with built-in backends.
+
+ This function is called when the module is imported to ensure
+ built-in backends are registered.
+ """
+ for name, backend_path in BUILTIN_BACKENDS.items():
+ if not KVConnectorBackendFactory.is_registered(name):
+ KVConnectorBackendFactory.register_backend(name, backend_path)
+
+
+# Initialize registry when module is imported
+_initialize_registry()
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/lmcache.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/lmcache.py
new file mode 100644
index 0000000000000000000000000000000000000000..b60e85a4764f38e115c097be3155f6c5b60524c5
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/lmcache.py
@@ -0,0 +1,61 @@
+from ray.llm._internal.serve.engines.vllm.kv_transfer.base import (
+ BaseConnectorBackend,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+
+logger = get_logger(__name__)
+
+
+def _check_lmcache_installed():
+ try:
+ import lmcache # noqa: F401
+ except ImportError:
+ raise ImportError(
+ "LMCache is not installed. Please install it with `pip install lmcache`."
+ )
+
+
+class LMCacheConnectorV1Backend(BaseConnectorBackend):
+
+ KV_CONNECTOR_EXTRA_CONFIG_FIELD_NAME = "kv_connector_extra_config"
+ LMCACHE_RPC_PORT_FIELD_NAME = "lmcache_rpc_port"
+ DEFAULT_LMCACHE_RPC_PORT_NAME = "lmcache_rpc_port"
+
+ def setup(self) -> None:
+ """Initialize the LMCache connector backend.
+
+ Creates a unique LMCache RPC port name across replicas by appending
+ a random suffix to the base port name.
+
+ Raises:
+ ImportError: If LMCache is not installed.
+ """
+ _check_lmcache_installed()
+
+ if (
+ LMCacheConnectorV1Backend.KV_CONNECTOR_EXTRA_CONFIG_FIELD_NAME
+ not in self.kv_transfer_config
+ ):
+ return
+
+ kv_connector_extra_config = self.kv_transfer_config[
+ LMCacheConnectorV1Backend.KV_CONNECTOR_EXTRA_CONFIG_FIELD_NAME
+ ]
+ base_value = kv_connector_extra_config.get(
+ LMCacheConnectorV1Backend.LMCACHE_RPC_PORT_FIELD_NAME,
+ LMCacheConnectorV1Backend.DEFAULT_LMCACHE_RPC_PORT_NAME,
+ )
+
+ # Append random suffix for uniqueness
+ lmcache_rpc_port_value = str(base_value) + self._get_unique_suffix()
+ if (
+ LMCacheConnectorV1Backend.LMCACHE_RPC_PORT_FIELD_NAME
+ in kv_connector_extra_config
+ ):
+ logger.info(
+ f"Setting unique lmcache_rpc_port={lmcache_rpc_port_value} for current replica."
+ )
+
+ kv_connector_extra_config[
+ LMCacheConnectorV1Backend.LMCACHE_RPC_PORT_FIELD_NAME
+ ] = lmcache_rpc_port_value
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/multi_connector.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/multi_connector.py
new file mode 100644
index 0000000000000000000000000000000000000000..56e556bbbfc19d3f8123a106f4206c09456d9349
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/multi_connector.py
@@ -0,0 +1,51 @@
+import copy
+from typing import TYPE_CHECKING
+
+from ray.llm._internal.serve.engines.vllm.kv_transfer.base import (
+ BaseConnectorBackend,
+)
+from ray.llm._internal.serve.engines.vllm.kv_transfer.factory import (
+ KVConnectorBackendFactory,
+)
+
+if TYPE_CHECKING:
+ from ray.llm._internal.serve.core.configs.llm_config import LLMConfig
+
+
+class MultiConnectorBackend(BaseConnectorBackend):
+ def __init__(self, llm_config: "LLMConfig"):
+ super().__init__(llm_config)
+
+ def setup(self) -> None:
+ """Setup all connectors listed in the kv_transfer_config."""
+ kv_transfer_config = self.kv_transfer_config
+ connectors = kv_transfer_config.get("kv_connector_extra_config", {}).get(
+ "connectors", []
+ )
+
+ for connector in connectors:
+ connector_backend_str = connector.get("kv_connector")
+ if connector_backend_str is None:
+ raise ValueError("kv_connector is not set in the connector")
+
+ if connector_backend_str == "MultiConnector":
+ raise ValueError(
+ "Nesting MultiConnector within MultiConnector is not supported."
+ )
+
+ # Merge parent config with connector-specific config
+ sub_llm_config = copy.deepcopy(self.llm_config)
+ sub_llm_config.engine_kwargs["kv_transfer_config"] = {
+ **{
+ k: v
+ for k, v in kv_transfer_config.items()
+ if k != "kv_connector_extra_config"
+ },
+ **connector,
+ }
+
+ # Use factory to get backend class lazily
+ connector_backend = KVConnectorBackendFactory.create_backend(
+ connector_backend_str, sub_llm_config
+ )
+ connector_backend.setup()
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/nixl.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/nixl.py
new file mode 100644
index 0000000000000000000000000000000000000000..7f95546d3df424f0fe208adde27383ec4910fc78
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/kv_transfer/nixl.py
@@ -0,0 +1,61 @@
+import os
+
+from ray.llm._internal.serve.engines.vllm.kv_transfer.base import (
+ BaseConnectorBackend,
+)
+
+
+class NixlConnectorBackend(BaseConnectorBackend):
+ def _set_side_channel_port(self):
+ from vllm import envs as vllm_envs, utils as vllm_utils
+
+ if not vllm_envs.is_set("VLLM_NIXL_SIDE_CHANNEL_PORT"):
+ base_port: int = int(
+ self.llm_config.experimental_configs.get(
+ "NIXL_SIDE_CHANNEL_PORT_BASE", vllm_utils.get_open_port()
+ )
+ )
+ # Use a deterministic rank-based offset (DP rank if set; else replica hash)
+ port = base_port + self._compute_port_offset()
+ os.environ["VLLM_NIXL_SIDE_CHANNEL_PORT"] = str(port)
+
+ def _set_side_channel_host(self):
+ from vllm import envs as vllm_envs, utils as vllm_utils
+
+ if not vllm_envs.is_set("VLLM_NIXL_SIDE_CHANNEL_HOST"):
+ os.environ["VLLM_NIXL_SIDE_CHANNEL_HOST"] = vllm_utils.get_ip()
+
+ def setup(self) -> None:
+ """Initialize the NIXL connector backend.
+
+ This method sets up the NIXL (NVIDIA Inference Xfer Library) connector by:
+ 1. Verifying that the required vLLM environment variables are supported
+ 2. Configuring the side channel port and host if not already set
+ 3. Creating a unique engine ID across replicas
+
+ The side channel is used for KV cache transfer between vLLM instances.
+
+ Raises:
+ ValueError: If the current vLLM version doesn't support the required
+ NIXL environment variables.
+ """
+ from vllm import envs as vllm_envs
+
+ if (
+ "VLLM_NIXL_SIDE_CHANNEL_PORT" not in vllm_envs.environment_variables
+ or "VLLM_NIXL_SIDE_CHANNEL_HOST" not in vllm_envs.environment_variables
+ ):
+ raise ValueError(
+ "This vLLM version does not support VLLM_NIXL_SIDE_CHANNEL_PORT"
+ "or VLLM_NIXL_SIDE_CHANNEL_HOST environment variable. It's likely"
+ "that you are using an older version of vLLM."
+ )
+
+ self._set_side_channel_port()
+ self._set_side_channel_host()
+
+ # We need to overwrite the engine_id to make it unique across replicas.
+ engine_id = self.kv_transfer_config.get("engine_id", self._get_unique_suffix())
+ host = vllm_envs.VLLM_NIXL_SIDE_CHANNEL_HOST
+ port = vllm_envs.VLLM_NIXL_SIDE_CHANNEL_PORT
+ self.kv_transfer_config["engine_id"] = "-".join([engine_id, host, str(port)])
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/vllm_engine.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/vllm_engine.py
new file mode 100644
index 0000000000000000000000000000000000000000..13be7465f885e283a47a9552c0b004708f74dbba
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/vllm_engine.py
@@ -0,0 +1,531 @@
+import argparse
+import os
+from typing import TYPE_CHECKING, AsyncGenerator, Optional, Tuple, Union
+
+from starlette.datastructures import State
+from starlette.requests import Request
+from vllm.engine.arg_utils import AsyncEngineArgs
+from vllm.entrypoints.openai.cli_args import FrontendArgs
+from vllm.entrypoints.openai.protocol import ErrorResponse as VLLMErrorResponse
+
+import ray
+from ray.llm._internal.common.callbacks.base import CallbackCtx
+from ray.llm._internal.common.utils.import_utils import try_import
+from ray.llm._internal.serve.core.configs.llm_config import (
+ DiskMultiplexConfig,
+ LLMConfig,
+)
+from ray.llm._internal.serve.core.configs.openai_api_models import (
+ ChatCompletionRequest,
+ ChatCompletionResponse,
+ CompletionRequest,
+ CompletionResponse,
+ EmbeddingRequest,
+ EmbeddingResponse,
+ ErrorInfo,
+ ErrorResponse,
+ ScoreRequest,
+ ScoreResponse,
+ TranscriptionRequest,
+ TranscriptionResponse,
+)
+from ray.llm._internal.serve.core.engine.protocol import LLMEngine
+from ray.llm._internal.serve.engines.vllm.vllm_models import (
+ VLLMEngineConfig,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.llm._internal.serve.utils.node_initialization_utils import (
+ initialize_node,
+)
+from ray.util.placement_group import PlacementGroup
+from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy
+
+if TYPE_CHECKING:
+ from vllm.config import VllmConfig
+ from vllm.engine.protocol import EngineClient
+ from vllm.entrypoints.openai.serving_chat import OpenAIServingChat
+ from vllm.entrypoints.openai.serving_completion import OpenAIServingCompletion
+ from vllm.entrypoints.openai.serving_embedding import OpenAIServingEmbedding
+ from vllm.entrypoints.openai.serving_models import OpenAIServingModels
+ from vllm.entrypoints.openai.serving_score import ServingScores
+ from vllm.entrypoints.openai.serving_transcription import OpenAIServingTranscription
+
+vllm = try_import("vllm")
+logger = get_logger(__name__)
+
+
+def _get_vllm_engine_config(
+ llm_config: LLMConfig,
+) -> Tuple["AsyncEngineArgs", "VllmConfig"]:
+ engine_config = llm_config.get_engine_config()
+
+ # Resolve to local cache path if model was downloaded from S3/GCS mirror
+ # Only do this if mirror_config was specified (intentional S3/GCS download)
+ if engine_config.mirror_config:
+ from ray.llm._internal.common.utils.download_utils import (
+ get_model_location_on_disk,
+ )
+
+ local_path = get_model_location_on_disk(engine_config.actual_hf_model_id)
+ if local_path and local_path != engine_config.actual_hf_model_id:
+ engine_config.hf_model_id = local_path
+ logger.info(f"Resolved model from mirror to local path: {local_path}")
+
+ async_engine_args = vllm.engine.arg_utils.AsyncEngineArgs(
+ **engine_config.get_initialization_kwargs()
+ )
+ from vllm.usage.usage_lib import UsageContext
+
+ vllm_engine_config = async_engine_args.create_engine_config(
+ usage_context=UsageContext.OPENAI_API_SERVER
+ )
+ return async_engine_args, vllm_engine_config
+
+
+def _clear_current_platform_cache():
+ """Clear the cache of the current platform.
+
+ vllm current has an lru cache for getting device compatibility
+ that will not have the correct returned value if
+ CUDA_VISIBLE_DEVICES is not set properly. In RayLLM eventually
+ when we want to create the engine the env will be set properly,
+ but till then, upon the import of vllm somewhere
+ (which is a mystery) the lru cache will have the wrong value.
+ This function will clear the cache so that the next time the
+ cache is accessed, it will be re-evaluated.
+
+ Related issues:
+ https://github.com/vllm-project/vllm/issues/8402
+ https://github.com/vllm-project/vllm/issues/7890
+ """
+ from vllm.platforms import current_platform
+
+ # TODO(seiji): remove this once https://github.com/vllm-project/vllm/pull/18979 is merged
+ if (
+ "CUDA_VISIBLE_DEVICES" in os.environ
+ and os.environ["CUDA_VISIBLE_DEVICES"] == ""
+ ):
+ del os.environ["CUDA_VISIBLE_DEVICES"]
+
+ # This check is just to future proof this implementation
+ # in case vllm removes their lru_cache decorator
+ if hasattr(current_platform.get_device_capability, "cache_clear"):
+ logger.info("Clearing the current platform cache ...")
+ current_platform.get_device_capability.cache_clear()
+
+
+class VLLMEngine(LLMEngine):
+ def __init__(
+ self,
+ llm_config: LLMConfig,
+ ):
+ """Create a vLLM Engine class
+
+ Args:
+ llm_config: The llm configuration for this engine
+ """
+ super().__init__(llm_config)
+
+ self.llm_config = llm_config
+
+ if vllm is None:
+ raise ImportError(
+ "vLLM is not installed. Please install it with `pip install ray[llm]`."
+ )
+ from vllm import envs as vllm_envs
+
+ # TODO (Kourosh): Remove this after a few releases.
+ if not vllm_envs.VLLM_USE_V1:
+ logger.error(
+ "vLLM v0 is fully deprecated. As a result in Ray Serve LLM only v1 is supported."
+ )
+
+ self.llm_config.setup_engine_backend()
+
+ self._running = False
+
+ # vLLM Integration points. Will be set through .start()
+ self._engine_client = None
+ self._oai_models: Optional["OpenAIServingModels"] = None
+ self._oai_serving_chat: Optional["OpenAIServingChat"] = None
+ self._oai_serving_completion: Optional["OpenAIServingCompletion"] = None
+ self._oai_serving_embedding: Optional["OpenAIServingEmbedding"] = None
+ self._oai_serving_transcription: Optional["OpenAIServingTranscription"] = None
+ self._oai_serving_scores: Optional["ServingScores"] = None
+
+ async def start(self) -> None:
+ """Start the vLLM engine.
+
+ If the engine is already running, do nothing.
+ """
+
+ if self._running:
+ # The engine is already running!
+ logger.info("Skipping engine restart because the engine is already running")
+ return
+
+ from vllm.entrypoints.openai.api_server import init_app_state
+
+ callback = self.llm_config.get_or_create_callback()
+ await callback.run_callback("on_before_node_init")
+ if callback.ctx.run_init_node:
+ await initialize_node(self.llm_config)
+ await callback.run_callback("on_after_node_init")
+
+ (
+ vllm_engine_args,
+ vllm_frontend_args,
+ vllm_engine_config,
+ ) = self._prepare_engine_config(callback.ctx)
+
+ # Apply checkpoint info to the llm_config.
+ # This is needed for capturing model capabilities
+ # (e.g. supports vision, etc.) on the llm_config.
+ config = self.llm_config.get_engine_config()
+ self.llm_config.apply_checkpoint_info(
+ vllm_engine_config.model_config.model,
+ trust_remote_code=config.trust_remote_code,
+ )
+
+ self._engine_client = self._start_async_llm_engine(
+ vllm_engine_args,
+ vllm_engine_config,
+ callback.ctx.placement_group,
+ )
+
+ state = State()
+ # TODO (Kourosh): There might be some variables that needs protection?
+ args = argparse.Namespace(
+ **vllm_frontend_args.__dict__,
+ **vllm_engine_args.__dict__,
+ )
+
+ await init_app_state(
+ engine_client=self._engine_client,
+ # TODO (ahao): remove vllm_config for vllm v1.12
+ vllm_config=vllm_engine_config,
+ state=state,
+ args=args,
+ )
+
+ self._oai_models = state.openai_serving_models
+ self._oai_serving_chat = state.openai_serving_chat
+ self._oai_serving_completion = state.openai_serving_completion
+ self._oai_serving_embedding = state.openai_serving_embedding
+ self._oai_serving_transcription = state.openai_serving_transcription
+ self._oai_serving_scores = state.openai_serving_scores
+
+ self._validate_openai_serving_models()
+ self._validate_engine_client()
+
+ self._running = True
+
+ logger.info("Started vLLM engine.")
+
+ def _validate_openai_serving_models(self):
+ assert self._oai_models is not None, "oai_models is not initialized"
+ assert hasattr(
+ self._oai_models, "lora_requests"
+ ), "oai_models must have a lora_requests attribute"
+ assert hasattr(
+ self._oai_models, "load_lora_adapter"
+ ), "oai_models must have a load_lora_adapter attribute"
+
+ def _validate_openai_serving_chat(self):
+ assert hasattr(
+ self._oai_serving_chat, "create_chat_completion"
+ ), "oai_serving_chat must have a create_chat_completion attribute"
+
+ def _validate_openai_serving_completion(self):
+ assert hasattr(
+ self._oai_serving_completion, "create_completion"
+ ), "oai_serving_completion must have a create_completion attribute"
+
+ def _validate_openai_serving_embedding(self):
+ assert hasattr(
+ self._oai_serving_embedding, "create_embedding"
+ ), "oai_serving_embedding must have a create_embedding attribute"
+
+ def _validate_openai_serving_transcription(self):
+ assert hasattr(
+ self._oai_serving_transcription, "create_transcription"
+ ), "oai_serving_transcription must have a create_transcription attribute"
+
+ def _validate_openai_serving_scores(self):
+ assert hasattr(
+ self._oai_serving_scores, "create_score"
+ ), "oai_serving_scores must have a create_score attribute"
+
+ def _validate_engine_client(self):
+ assert hasattr(
+ self._engine_client, "check_health"
+ ), "engine_client must have a check_health attribute"
+
+ def _prepare_engine_config(
+ self, callback_ctx: CallbackCtx
+ ) -> Tuple["AsyncEngineArgs", "FrontendArgs", "VllmConfig"]:
+ """Prepare the engine config to start the engine.
+
+ Args:
+ callback_ctx: The callback context.
+
+ Returns:
+ A tuple of:
+ engine_args: The vLLM's internal engine arguments that is flattened.
+ frontend_args: The vLLM's internal frontend arguments that is flattened.
+ engine_config: The vLLM's internal engine config that is nested.
+ """
+
+ engine_config: VLLMEngineConfig = self.llm_config.get_engine_config()
+
+ if engine_config.use_gpu:
+ # Create engine config on a task with access to GPU,
+ # as GPU capability may be queried.
+ ref = (
+ ray.remote(
+ num_cpus=0,
+ num_gpus=0.001,
+ accelerator_type=self.llm_config.accelerator_type,
+ )(_get_vllm_engine_config)
+ .options(
+ runtime_env=callback_ctx.runtime_env,
+ scheduling_strategy=PlacementGroupSchedulingStrategy(
+ placement_group=callback_ctx.placement_group,
+ ),
+ )
+ .remote(self.llm_config)
+ )
+ vllm_engine_args, vllm_engine_config = ray.get(ref)
+ else:
+ vllm_engine_args, vllm_engine_config = _get_vllm_engine_config(
+ self.llm_config
+ )
+
+ vllm_frontend_args = FrontendArgs(**engine_config.frontend_kwargs)
+ return vllm_engine_args, vllm_frontend_args, vllm_engine_config
+
+ def _start_async_llm_engine(
+ self,
+ vllm_engine_args: "AsyncEngineArgs",
+ vllm_engine_config: "VllmConfig",
+ placement_group: PlacementGroup,
+ ) -> "EngineClient":
+ """Creates an async LLM engine from the engine arguments."""
+
+ from vllm.v1.engine.async_llm import AsyncLLM
+ from vllm.v1.executor.abstract import Executor
+
+ vllm_engine_config.parallel_config.placement_group = placement_group
+
+ _clear_current_platform_cache()
+
+ custom_stat_loggers = None
+ if self.llm_config.log_engine_metrics:
+ from vllm.v1.metrics.ray_wrappers import RayPrometheusStatLogger
+
+ # V1 AsyncLLM does not yet support add_logger: https://github.com/vllm-project/vllm/issues/17702
+ # Use `disable_log_stats: False` and `log_engine_metrics: False` as
+ # a workaround to enable PrometheusStatLogger instead.
+ custom_stat_loggers = [RayPrometheusStatLogger]
+
+ executor_class = Executor.get_class(vllm_engine_config)
+ logger.info(f"Using executor class: {executor_class}")
+ engine_client = AsyncLLM(
+ vllm_config=vllm_engine_config,
+ executor_class=executor_class,
+ log_stats=not vllm_engine_args.disable_log_stats,
+ stat_loggers=custom_stat_loggers,
+ )
+
+ return engine_client
+
+ async def resolve_lora(self, disk_lora_model: DiskMultiplexConfig):
+ from vllm.entrypoints.openai.protocol import LoadLoRAAdapterRequest
+
+ self._validate_openai_serving_models()
+
+ if disk_lora_model.model_id in self._oai_models.lora_requests:
+ # Lora is already loaded, return
+ return
+
+ lora_request = await self._oai_models.load_lora_adapter( # type: ignore[attr-defined]
+ request=LoadLoRAAdapterRequest(
+ lora_name=disk_lora_model.model_id,
+ lora_path=disk_lora_model.local_path,
+ )
+ )
+
+ if isinstance(lora_request, VLLMErrorResponse):
+ raise ValueError(f"Failed to load lora model: {lora_request.error.message}")
+
+ def _create_raw_request(
+ self,
+ request: Union[
+ CompletionRequest,
+ ChatCompletionRequest,
+ EmbeddingRequest,
+ TranscriptionRequest,
+ ScoreRequest,
+ ],
+ path: str,
+ ) -> Request:
+ scope = {
+ "type": "http",
+ "method": "POST",
+ "path": path,
+ "headers": [(b"x-request-id", getattr(request, "request_id", "").encode())],
+ "query_string": b"",
+ }
+ return Request(scope)
+
+ async def chat(
+ self, request: ChatCompletionRequest
+ ) -> AsyncGenerator[Union[str, ChatCompletionResponse, ErrorResponse], None]:
+ self._validate_openai_serving_chat()
+
+ # TODO (Kourosh): Remove when we upstream request_id attribute to vLLM.
+ # PR: https://github.com/vllm-project/vllm/pull/21009
+ # Create a fake starlette.Request object with the x-request-id header
+ # so that the create_chat_completion API can assign the request_id properly.
+ raw_request = self._create_raw_request(request, "/chat/completions")
+
+ chat_response = await self._oai_serving_chat.create_chat_completion( # type: ignore[attr-defined]
+ request, raw_request=raw_request
+ )
+
+ if isinstance(chat_response, AsyncGenerator):
+ async for response in chat_response:
+ if not isinstance(response, str):
+ raise ValueError(
+ f"Expected create_chat_completion to return a stream of strings, got an item with type {type(response)}"
+ )
+ yield response
+ else:
+ if isinstance(chat_response, VLLMErrorResponse):
+ yield ErrorResponse(error=ErrorInfo(**chat_response.error.model_dump()))
+ else:
+ yield ChatCompletionResponse(**chat_response.model_dump())
+
+ async def completions(
+ self, request: CompletionRequest
+ ) -> AsyncGenerator[Union[str, CompletionResponse, ErrorResponse], None]:
+ self._validate_openai_serving_completion()
+
+ # TODO (Kourosh): Remove when we upstream request_id attribute to vLLM.
+ # PR: https://github.com/vllm-project/vllm/pull/21009
+ # Create a fake starlette.Request object with the x-request-id header
+ # so that the create_completion API can assign the request_id properly.
+ raw_request = self._create_raw_request(request, "/completions")
+
+ completion_response = await self._oai_serving_completion.create_completion( # type: ignore[attr-defined]
+ request,
+ raw_request=raw_request,
+ )
+
+ if isinstance(completion_response, AsyncGenerator):
+ async for response in completion_response:
+ if not isinstance(response, str):
+ raise ValueError(
+ f"Expected create_completion to return a stream of strings, got an item with type {type(response)}"
+ )
+ yield response
+ else:
+ if isinstance(completion_response, VLLMErrorResponse):
+ yield ErrorResponse(
+ error=ErrorInfo(**completion_response.error.model_dump())
+ )
+ else:
+ yield CompletionResponse(**completion_response.model_dump())
+
+ async def embeddings(
+ self, request: EmbeddingRequest
+ ) -> AsyncGenerator[Union[EmbeddingResponse, ErrorResponse], None]:
+ self._validate_openai_serving_embedding()
+
+ # TODO (Kourosh): Remove when upstream is fixed to accept req_id.
+ # Create a fake starlette.Request object with the x-request-id header
+ # so that the create_embedding API can assign the request_id properly.
+ raw_request = self._create_raw_request(request, "/embeddings")
+
+ embedding_response = await self._oai_serving_embedding.create_embedding( # type: ignore[attr-defined]
+ request, raw_request=raw_request
+ )
+
+ if isinstance(embedding_response, VLLMErrorResponse):
+ yield ErrorResponse(
+ error=ErrorInfo(**embedding_response.error.model_dump())
+ )
+ else:
+ yield EmbeddingResponse(**embedding_response.model_dump())
+
+ async def transcriptions(
+ self, request: TranscriptionRequest
+ ) -> AsyncGenerator[Union[str, TranscriptionResponse, ErrorResponse], None]:
+ self._validate_openai_serving_transcription()
+
+ # TODO (Kourosh): Remove when we upstream request_id attribute to vLLM.
+ # PR: https://github.com/vllm-project/vllm/pull/21009
+ # Create a fake starlette.Request object with the x-request-id header
+ # so that the create_transcription API can assign the request_id properly.
+ raw_request = self._create_raw_request(request, "/audio/transcriptions")
+
+ # Extract audio data from the request file
+ audio_data = await request.file.read()
+
+ transcription_response = await self._oai_serving_transcription.create_transcription( # type: ignore[attr-defined]
+ audio_data,
+ request,
+ raw_request=raw_request,
+ )
+
+ if isinstance(transcription_response, AsyncGenerator):
+ async for response in transcription_response:
+ if not isinstance(response, str):
+ raise ValueError(
+ f"Expected create_transcription to return a stream of strings, got an item with type {type(response)}"
+ )
+ yield response
+ else:
+ if isinstance(transcription_response, VLLMErrorResponse):
+ yield ErrorResponse(
+ error=ErrorInfo(**transcription_response.error.model_dump())
+ )
+ else:
+ yield TranscriptionResponse(**transcription_response.model_dump())
+
+ async def score(
+ self, request: ScoreRequest
+ ) -> AsyncGenerator[Union[ScoreResponse, ErrorResponse], None]:
+ self._validate_openai_serving_scores()
+
+ raw_request = self._create_raw_request(request, "/score")
+
+ score_response = await self._oai_serving_scores.create_score(
+ request, raw_request=raw_request
+ )
+
+ if isinstance(score_response, VLLMErrorResponse):
+ yield ErrorResponse(**score_response.model_dump())
+ else:
+ yield ScoreResponse(**score_response.model_dump())
+
+ async def check_health(self) -> None:
+ assert self._engine_client is not None, "engine_client is not initialized"
+
+ try:
+ await self._engine_client.check_health()
+ except BaseException as e:
+ logger.error("Healthcheck failed. The replica will be restarted")
+ raise e from None
+
+ async def reset_prefix_cache(self) -> None:
+ assert self._engine_client is not None, "engine_client is not initialized"
+ await self._engine_client.reset_prefix_cache()
+
+ async def start_profile(self) -> None:
+ assert self._engine_client is not None, "engine_client is not initialized"
+ await self._engine_client.start_profile()
+
+ async def stop_profile(self) -> None:
+ assert self._engine_client is not None, "engine_client is not initialized"
+ await self._engine_client.stop_profile()
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/vllm_models.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/vllm_models.py
new file mode 100644
index 0000000000000000000000000000000000000000..7af47e8b588a14bd81872d16f1b42e706c572fc2
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/engines/vllm/vllm_models.py
@@ -0,0 +1,320 @@
+import copy
+import dataclasses
+import os
+from typing import Any, Dict, List, Literal, Optional
+
+from pydantic import ConfigDict, Field, field_validator, model_validator
+from vllm.engine.arg_utils import AsyncEngineArgs
+from vllm.entrypoints.openai.cli_args import FrontendArgs
+
+from ray.llm._internal.common.base_pydantic import BaseModelExtended
+from ray.llm._internal.common.utils.cloud_utils import CloudMirrorConfig
+from ray.llm._internal.common.utils.import_utils import try_import
+from ray.llm._internal.serve.constants import (
+ ALLOW_NEW_PLACEMENT_GROUPS_IN_DEPLOYMENT,
+ ENV_VARS_TO_PROPAGATE,
+)
+from ray.llm._internal.serve.core.configs.llm_config import (
+ GPUType,
+ LLMConfig,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.util.placement_group import (
+ PlacementGroup,
+ get_current_placement_group,
+ placement_group,
+ placement_group_table,
+)
+
+# The key for the kv_transfer_params in the internal metadata.
+KV_TRANSFER_PARAMS_KEY = "kv_transfer_params"
+vllm = try_import("vllm")
+logger = get_logger(__name__)
+
+
+class BundleConfig(BaseModelExtended):
+ """Configuration for placement group bundle.
+
+ Note: Counts are floats to align with Ray resource typing.
+ """
+
+ CPU: float = Field(default=0.0, ge=0.0, description="Number of CPUs per bundle")
+ GPU: float = Field(default=1.0, ge=0.0, description="Number of GPUs per bundle")
+
+ class Config:
+ extra = "allow" # Allow arbitrary resource types
+
+
+class PlacementGroupConfig(BaseModelExtended):
+ """Configuration for placement group."""
+
+ bundles: List[BundleConfig] = Field(description="List of resource bundles")
+ strategy: Literal["PACK", "SPREAD", "STRICT_PACK", "STRICT_SPREAD"] = Field(
+ default="PACK", description="Placement group strategy"
+ )
+
+ @model_validator(mode="before")
+ @classmethod
+ def validate_bundles_exist(cls, values):
+ if isinstance(values, dict) and "bundles" not in values:
+ raise ValueError("placement_group_config must contain 'bundles'")
+ return values
+
+
+class VLLMEngineConfig(BaseModelExtended):
+ model_config = ConfigDict(
+ use_enum_values=True,
+ )
+
+ model_id: str = Field(
+ description="The identifier for the model. This is the id that will be used to query the model.",
+ )
+ hf_model_id: Optional[str] = Field(
+ None, description="The Hugging Face model identifier."
+ )
+ mirror_config: Optional[CloudMirrorConfig] = Field(
+ None,
+ description="Configuration for cloud storage mirror. This is for where the weights are downloaded from.",
+ )
+ accelerator_type: Optional[GPUType] = Field(
+ None,
+ description="The type of accelerator to use. This is used to determine the placement group strategy.",
+ )
+ placement_group_config: Optional[Dict[str, Any]] = Field(
+ default=None,
+ description=(
+ "Ray placement group configuration for scheduling vLLM engine workers. "
+ "Defines resource bundles and placement strategy for multi-node deployments. "
+ "Defaults to PACK strategy with automatic bundle generation based on TP/PP sizes."
+ ),
+ )
+
+ @field_validator("placement_group_config")
+ @classmethod
+ def validate_placement_group_config(cls, value):
+ if value is None:
+ return None
+ # Validate through PlacementGroupConfig, then dump back to dict
+ validated = PlacementGroupConfig(**value)
+ return validated.model_dump()
+
+ runtime_env: Optional[Dict[str, Any]] = None
+ engine_kwargs: Dict[str, Any] = {}
+ frontend_kwargs: Dict[str, Any] = {}
+
+ @property
+ def actual_hf_model_id(self) -> str:
+ return self.hf_model_id or self.model_id
+
+ @property
+ def trust_remote_code(self) -> bool:
+ return self.engine_kwargs.get("trust_remote_code", False)
+
+ def get_initialization_kwargs(self) -> dict:
+ """
+ Get kwargs that will be actually passed to the LLMInitializer
+ constructor.
+ """
+ engine_kwargs = self.engine_kwargs.copy()
+
+ if "model" in engine_kwargs or "served_model_name" in engine_kwargs:
+ raise ValueError(
+ "model or served_model_name is not allowed in engine_kwargs when using Ray Serve LLM. Please use `model_loading_config` in LLMConfig instead."
+ )
+
+ engine_kwargs["model"] = self.actual_hf_model_id
+ engine_kwargs["served_model_name"] = [self.model_id]
+
+ if (
+ "distributed_executor_backend" in engine_kwargs
+ and engine_kwargs["distributed_executor_backend"] != "ray"
+ ):
+ raise ValueError(
+ "distributed_executor_backend != 'ray' is not allowed in engine_kwargs when using Ray Serve LLM Configs."
+ )
+ else:
+ engine_kwargs["distributed_executor_backend"] = "ray"
+
+ # TODO (Nikhil): Remove this once vLLM fully deprecates disable_log_requests.
+ if "disable_log_requests" in engine_kwargs:
+ logger.warning(
+ "disable_log_requests is set in engine_kwargs, but vLLM "
+ "does not support it. Converting to enable_log_requests."
+ )
+ engine_kwargs["enable_log_requests"] = not engine_kwargs.pop(
+ "disable_log_requests"
+ )
+ elif "enable_log_requests" not in engine_kwargs:
+ engine_kwargs["enable_log_requests"] = False
+
+ return engine_kwargs
+
+ def get_runtime_env_with_local_env_vars(self) -> dict:
+ runtime_env = self.runtime_env or {}
+ runtime_env.setdefault("env_vars", {})
+
+ # Propagate env vars to the runtime env
+ for env_var in ENV_VARS_TO_PROPAGATE:
+ if env_var in os.environ:
+ runtime_env["env_vars"][env_var] = os.getenv(env_var)
+ return runtime_env
+
+ @classmethod
+ def from_llm_config(cls, llm_config: LLMConfig) -> "VLLMEngineConfig":
+ """Converts the LLMConfig to a VLLMEngineConfig."""
+ # Set up the model downloading configuration.
+ hf_model_id, mirror_config = None, None
+ if llm_config.model_loading_config.model_source is None:
+ hf_model_id = llm_config.model_id
+ elif isinstance(llm_config.model_loading_config.model_source, str):
+ hf_model_id = llm_config.model_loading_config.model_source
+ else:
+ # If it's a CloudMirrorConfig (or subtype)
+ mirror_config = llm_config.model_loading_config.model_source
+
+ all_engine_kwargs = llm_config.engine_kwargs.copy()
+ engine_kwargs = {}
+ frontend_kwargs = {}
+
+ # Get field names from dataclasses
+ frontend_field_names = {
+ field.name for field in dataclasses.fields(FrontendArgs)
+ }
+ async_engine_field_names = {
+ field.name for field in dataclasses.fields(AsyncEngineArgs)
+ }
+
+ for key, value in all_engine_kwargs.items():
+ if key in frontend_field_names:
+ frontend_kwargs[key] = value
+ elif key in async_engine_field_names:
+ engine_kwargs[key] = value
+ else:
+ raise ValueError(f"Unknown engine argument: {key}")
+
+ # placement_group_config is already validated and stored as dict in LLMConfig
+ placement_group_config = llm_config.placement_group_config
+
+ return VLLMEngineConfig(
+ model_id=llm_config.model_id,
+ hf_model_id=hf_model_id,
+ mirror_config=mirror_config,
+ accelerator_type=llm_config.accelerator_type,
+ engine_kwargs=engine_kwargs,
+ frontend_kwargs=frontend_kwargs,
+ runtime_env=llm_config.runtime_env,
+ placement_group_config=placement_group_config,
+ )
+
+ def ray_accelerator_type(self) -> str:
+ """Converts the accelerator type to the Ray Core format."""
+ return f"accelerator_type:{self.accelerator_type}"
+
+ @property
+ def tensor_parallel_degree(self) -> int:
+ return self.engine_kwargs.get("tensor_parallel_size", 1)
+
+ @property
+ def pipeline_parallel_degree(self) -> int:
+ return self.engine_kwargs.get("pipeline_parallel_size", 1)
+
+ @property
+ def num_devices(self) -> int:
+ return self.tensor_parallel_degree * self.pipeline_parallel_degree
+
+ @property
+ def placement_strategy(self) -> str:
+ # Use custom strategy if placement_group_config is provided
+ if self.placement_group_config:
+ return self.placement_group_config.get("strategy", "PACK")
+ # Default to PACK (cross-node best-effort placement)
+ # DP deployments overridden to STRICT_PACK in Serve config
+ return "PACK"
+
+ @property
+ def placement_bundles(self) -> List[Dict[str, float]]:
+ if self.placement_group_config:
+ # placement_group_config is validated dict; extract bundles
+ bundles = []
+ for bundle_dict in self.placement_group_config["bundles"]:
+ bundle = bundle_dict.copy()
+ if self.accelerator_type:
+ # Use setdefault to add accelerator hint WITHOUT overriding explicit user values
+ bundle.setdefault(self.ray_accelerator_type(), 0.001)
+ bundles.append(bundle)
+ return bundles
+
+ # Default bundles: GPU-only; replica actor contributes CPU to first bundle via merge
+ bundle = {"GPU": 1}
+
+ if self.accelerator_type:
+ bundle[self.ray_accelerator_type()] = 0.001
+ bundles = [copy.deepcopy(bundle) for _ in range(self.num_devices)]
+
+ return bundles
+
+ @property
+ def use_gpu(self) -> bool:
+ """Returns True if vLLM is configured to use GPU resources."""
+ # Check placement_group_config bundles for explicit GPU specification
+ if self.placement_group_config:
+ bundles = self.placement_group_config.get("bundles", [])
+ if bundles:
+ # If any bundle has GPU > 0, we use GPU
+ return any(bundle.get("GPU", 0) > 0 for bundle in bundles)
+
+ # Default behavior based on accelerator_type
+ if not self.accelerator_type:
+ # By default, GPU resources are used
+ return True
+
+ return self.accelerator_type in (
+ GPUType.NVIDIA_TESLA_V100.value,
+ GPUType.NVIDIA_TESLA_P100.value,
+ GPUType.NVIDIA_TESLA_T4.value,
+ GPUType.NVIDIA_TESLA_P4.value,
+ GPUType.NVIDIA_TESLA_K80.value,
+ GPUType.NVIDIA_TESLA_A10G.value,
+ GPUType.NVIDIA_L4.value,
+ GPUType.NVIDIA_L40S.value,
+ GPUType.NVIDIA_A100.value,
+ GPUType.NVIDIA_H100.value,
+ GPUType.NVIDIA_H200.value,
+ GPUType.NVIDIA_H20.value,
+ GPUType.NVIDIA_A100_40G.value,
+ GPUType.NVIDIA_A100_80G.value,
+ )
+
+ def get_or_create_pg(self) -> PlacementGroup:
+ """Gets or a creates a placement group.
+
+ If we are already in a placement group, return the existing placement group.
+ Else, create a new placement group based on the scaling config.
+ """
+ dp_rank = self.engine_kwargs.get("data_parallel_rank", None)
+ pg = get_current_placement_group()
+ if pg:
+ logger.debug(
+ "Using existing placement group %s, details: %s",
+ pg.id,
+ placement_group_table(pg),
+ )
+ else:
+ if not ALLOW_NEW_PLACEMENT_GROUPS_IN_DEPLOYMENT:
+ raise RuntimeError(
+ "Creating new placement groups is not allowed. "
+ "Change RAYLLM_ALLOW_NEW_PLACEMENT_GROUPS_IN_DEPLOYMENT "
+ "if this is not intended."
+ )
+ name = "" if dp_rank is None else f"dp_{dp_rank}"
+
+ # Use placement_bundles and placement_strategy properties which handle
+ # both custom and default placement group configurations
+ pg = placement_group(
+ bundles=self.placement_bundles,
+ strategy=self.placement_strategy,
+ name=name,
+ )
+
+ logger.info(f"Using new placement group {pg}. {placement_group_table(pg)}")
+ return pg
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..22da68f1f549f3dc51a8621171d838a1ae6f3823
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/__init__.py
@@ -0,0 +1,23 @@
+from ray.llm._internal.common.observability.logging_utils import (
+ disable_datasets_logger,
+ disable_vllm_custom_ops_logger_on_cpu_nodes,
+)
+from ray.llm._internal.common.observability.telemetry_utils import Once
+from ray.llm._internal.serve.observability.logging.setup import (
+ setup_logging,
+)
+
+_setup_observability_once = Once()
+
+
+def _setup_observability():
+ setup_logging()
+ disable_datasets_logger()
+ disable_vllm_custom_ops_logger_on_cpu_nodes()
+
+
+def setup_observability():
+ _setup_observability_once.do_once(_setup_observability)
+
+
+__all__ = ["setup_observability"]
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..b813efb6f447b33864ba22c32fafe87dcc5d95fc
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..914e2a8dce9fccec2e4c4449cb485bd838a24ec0
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/__init__.py
@@ -0,0 +1,39 @@
+import logging
+from typing import Optional
+
+from ray._common.filters import CoreContextFilter
+from ray.serve._private.logging_utils import ServeContextFilter
+
+
+def _setup_logger(logger_name: str):
+ """Setup logger given the logger name.
+
+ This function is idempotent and won't set up the same logger multiple times. It will
+ Also skip the setup if Serve logger is already setup and has handlers.
+ """
+ logger = logging.getLogger(logger_name)
+ serve_logger = logging.getLogger("ray.serve")
+
+ # Skip setup if the logger already has handlers setup or if the parent (Serve
+ # logger) has handlers.
+ if logger.handlers or serve_logger.handlers:
+ return
+
+ # Set up stream handler, which logs to console as plaintext.
+ stream_handler = logging.StreamHandler()
+ stream_handler.addFilter(CoreContextFilter())
+ stream_handler.addFilter(ServeContextFilter())
+ logger.addHandler(stream_handler)
+ logger.setLevel(logging.INFO)
+ logger.propagate = False
+
+
+def get_logger(name: Optional[str] = None):
+ """Get a structured logger inherited from the Ray Serve logger.
+
+ Loggers by default are logging to stdout, and are expected to be scraped by an
+ external process.
+ """
+ logger_name = f"ray.serve.{name}"
+ _setup_logger(logger_name)
+ return logging.getLogger(logger_name)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..5eecfff58760387bba085547636b9a92a174f55b
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/__pycache__/setup.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/__pycache__/setup.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..ba1b0db974de5907d0731a2028679c42dbd474db
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/__pycache__/setup.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/setup.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/setup.py
new file mode 100644
index 0000000000000000000000000000000000000000..3b1915fd2ac62017d00c04d20a323ab191b9234b
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/logging/setup.py
@@ -0,0 +1,28 @@
+import logging
+
+from ray._common.filters import CoreContextFilter
+from ray._common.formatters import JSONFormatter
+from ray.serve._private.logging_utils import ServeContextFilter
+
+
+def _configure_stdlib_logging():
+ """Configures stdlib root logger to make sure stdlib loggers (created as
+ `logging.getLogger(...)`) are using Ray's `JSONFormatter` with Core and Serve
+ context filters.
+ """
+
+ handler = logging.StreamHandler()
+ handler.addFilter(CoreContextFilter())
+ handler.addFilter(ServeContextFilter())
+ handler.setFormatter(JSONFormatter())
+
+ root_logger = logging.getLogger()
+ # NOTE: It's crucial we reset all the handlers of the root logger,
+ # to make sure that logs aren't emitted twice
+ root_logger.handlers = []
+ root_logger.addHandler(handler)
+ root_logger.setLevel(logging.INFO)
+
+
+def setup_logging():
+ _configure_stdlib_logging()
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..1a8ab2bb39395b9be6e1f3123304133f5758b85d
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/event_loop_monitoring.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/event_loop_monitoring.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4962e43d922bcf39c4fc1d48aac1bc94ad3af8ce
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/event_loop_monitoring.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/fast_api_metrics.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/fast_api_metrics.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..a6e3d251409c96c8509cf5f23ab86f4259525d9a
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/fast_api_metrics.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/fastapi_utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/fastapi_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2bf09d64780c47eac75464ba6a70848e7adec3b8
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/fastapi_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/middleware.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/middleware.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..abc85f51faa8f36b7d0d54254097fba3482851a5
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/middleware.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..d18a1c9091d21fe821ba82fe8a1e5233dc7cb4b3
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/__pycache__/utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/event_loop_monitoring.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/event_loop_monitoring.py
new file mode 100644
index 0000000000000000000000000000000000000000..ea139c6ab8f7e175dfe2ced23b0213c60c5327ac
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/event_loop_monitoring.py
@@ -0,0 +1,77 @@
+import asyncio
+import time
+from typing import Dict
+
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.util import metrics
+
+logger = get_logger(__name__)
+
+_METRICS_LOOP_INTERVAL = 5 # 5 seconds
+EVENT_LOOP_LATENCY_HISTOGRAM_BOUNDARIES = [
+ 0.05,
+ 0.1,
+ 0.15,
+ 0.20,
+ 0.25,
+ 0.5,
+ 0.75,
+ 1.0,
+ 1.5,
+ 2.0,
+ 3.0,
+ 5.0,
+ 10.0,
+ 15.0,
+ 20.0,
+ 30.0,
+ 45.0,
+ 60.0,
+ 90.0,
+ 120.0,
+ 150.0,
+ 180.0,
+ 300.0,
+ 600.0,
+]
+
+
+def setup_event_loop_monitoring(
+ loop: asyncio.AbstractEventLoop,
+ scheduling_latency: metrics.Histogram,
+ iterations: metrics.Counter,
+ tasks: metrics.Gauge,
+ tags: Dict[str, str],
+) -> asyncio.Task:
+ return asyncio.create_task(
+ _run_monitoring_loop(
+ loop,
+ schedule_latency=scheduling_latency,
+ iterations=iterations,
+ task_gauge=tasks,
+ tags=tags,
+ )
+ )
+
+
+async def _run_monitoring_loop(
+ loop: asyncio.AbstractEventLoop,
+ schedule_latency: metrics.Histogram,
+ iterations: metrics.Counter,
+ task_gauge: metrics.Gauge,
+ tags: Dict[str, str],
+) -> None:
+ while loop.is_running():
+ iterations.inc(1, tags)
+ num_tasks = len(asyncio.all_tasks())
+ task_gauge.set(num_tasks, tags)
+ yield_time = time.monotonic()
+ await asyncio.sleep(_METRICS_LOOP_INTERVAL)
+ elapsed_time = time.monotonic() - yield_time
+
+ # Historically, Ray's implementation of histograms are extremely finicky
+ # with non-positive values (https://github.com/ray-project/ray/issues/26698).
+ # Technically it shouldn't be possible for this to be negative, add the
+ # max just to be safe.
+ latency = max(0.0, elapsed_time - _METRICS_LOOP_INTERVAL)
+ schedule_latency.observe(latency, tags)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/fast_api_metrics.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/fast_api_metrics.py
new file mode 100644
index 0000000000000000000000000000000000000000..50dc6bb22584109b974f1a8faec3d3256c14acb4
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/fast_api_metrics.py
@@ -0,0 +1,133 @@
+import asyncio
+import os
+from contextlib import asynccontextmanager
+
+from fastapi import FastAPI
+
+from ray.llm._internal.serve.constants import ENABLE_VERBOSE_TELEMETRY
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.llm._internal.serve.observability.metrics.event_loop_monitoring import (
+ EVENT_LOOP_LATENCY_HISTOGRAM_BOUNDARIES,
+ setup_event_loop_monitoring,
+)
+from ray.llm._internal.serve.observability.metrics.fastapi_utils import (
+ FASTAPI_API_SERVER_TAG_KEY,
+ FASTAPI_BASE_HTTP_METRIC_TAG_KEYS,
+ get_app_name,
+)
+from ray.llm._internal.serve.observability.metrics.middleware import (
+ MeasureHTTPRequestMetricsMiddleware,
+)
+from ray.util import metrics
+
+logger = get_logger(__name__)
+
+ray_llm_build_info_gauge = metrics.Gauge(
+ "ray_llm_build_info",
+ description="Metadata about the ray-llm build.",
+ tag_keys=("git_commit",),
+)
+
+
+_HTTP_HANDLER_LATENCY_S_HISTOGRAM_BUCKETS = [
+ 0.01,
+ 0.05,
+ 0.1,
+ 0.25,
+ 0.5,
+ 0.75,
+ 1,
+ 1.5,
+ 2,
+ 5,
+ 10,
+ 30,
+ 60,
+ 120,
+ 300,
+]
+
+
+async def add_fastapi_event_loop_monitoring(app: FastAPI):
+ tags = {FASTAPI_API_SERVER_TAG_KEY: get_app_name(app)}
+ tag_keys = tuple(tags.keys())
+
+ # Store the task handle to prevent it from being garbage collected
+ app.state.fastapi_event_loop_schedule_latency = metrics.Histogram(
+ "fastapi_event_loop_schedule_latency",
+ description="Latency of getting yielded control on the FastAPI event loop in seconds",
+ boundaries=EVENT_LOOP_LATENCY_HISTOGRAM_BOUNDARIES,
+ tag_keys=tag_keys,
+ )
+ app.state.fastapi_event_loop_monitoring_iterations = metrics.Counter(
+ "fastapi_event_loop_monitoring_iterations",
+ description="Number of times the FastAPI event loop has iterated to get anyscale_fastapi_event_loop_schedule_latency.",
+ tag_keys=tag_keys,
+ )
+ app.state.fastapi_event_loop_monitoring_tasks = metrics.Gauge(
+ "fastapi_event_loop_monitoring_tasks",
+ description="Number of outstanding tasks on the FastAPI event loop.",
+ tag_keys=tag_keys,
+ )
+
+ app.state.fastapi_event_loop_schedule_latency_metrics_task = (
+ setup_event_loop_monitoring(
+ asyncio.get_running_loop(),
+ app.state.fastapi_event_loop_schedule_latency,
+ app.state.fastapi_event_loop_monitoring_iterations,
+ app.state.fastapi_event_loop_monitoring_tasks,
+ tags,
+ )
+ )
+
+
+def add_http_metrics_middleware(app: FastAPI):
+ if not ENABLE_VERBOSE_TELEMETRY:
+ logger.debug(
+ "ENABLE_VERBOSE_TELEMETRY is false, not setting up FastAPI telemetry"
+ )
+ return
+
+ logger.info("ENABLE_VERBOSE_TELEMETRY is true, setting up FastAPI telemetry")
+ base_tag_keys = FASTAPI_BASE_HTTP_METRIC_TAG_KEYS
+
+ logger.debug("Setting up FastAPI telemetry")
+
+ app.state.http_requests_metrics = metrics.Counter(
+ "http_requests",
+ description=(
+ "Total number of HTTP requests by status code, handler and method."
+ ),
+ tag_keys=base_tag_keys,
+ )
+ # NOTE: Custom decorators are not applied to histogram-based metrics
+ # to make sure we can keep cardinality of those in check
+ app.state.http_requests_latency_metrics = metrics.Histogram(
+ "http_request_duration_seconds",
+ description="Duration in seconds of HTTP requests.",
+ boundaries=_HTTP_HANDLER_LATENCY_S_HISTOGRAM_BUCKETS,
+ tag_keys=base_tag_keys,
+ )
+
+ app.add_middleware(MeasureHTTPRequestMetricsMiddleware)
+
+ logger.debug("Setting up FastAPI telemetry completed")
+
+
+async def set_ray_llm_build_info():
+ git_commit = os.environ.get("GIT_COMMIT")
+ if git_commit:
+ tags = {"git_commit": git_commit}
+ ray_llm_build_info_gauge.set(1, tags)
+
+
+@asynccontextmanager
+async def metrics_lifespan(app: FastAPI):
+ """Lifespan for a FastAPI app that sets up metrics observability."""
+
+ if ENABLE_VERBOSE_TELEMETRY:
+ await add_fastapi_event_loop_monitoring(app)
+
+ await set_ray_llm_build_info()
+
+ yield
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/fastapi_utils.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/fastapi_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce15928fa42101871b7059e914489df755a4b97a
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/fastapi_utils.py
@@ -0,0 +1,26 @@
+"""This file contains constants and utility functions for FastAPI."""
+
+from fastapi import FastAPI
+
+# These tag keys are used in metrics for the FastAPI app.
+FASTAPI_HTTP_RESPONSE_CODE_TAG_KEY = "code"
+FASTAPI_HTTP_HANDLER_TAG_KEY = "handler"
+FASTAPI_HTTP_METHOD_TAG_KEY = "method"
+FASTAPI_HTTP_PATH_TAG_KEY = "path"
+FASTAPI_HTTP_USER_ID_TAG_KEY = "user_id"
+FASTAPI_API_SERVER_TAG_KEY = "api_server"
+
+FASTAPI_BASE_HTTP_METRIC_TAG_KEYS = (
+ FASTAPI_HTTP_RESPONSE_CODE_TAG_KEY,
+ FASTAPI_HTTP_HANDLER_TAG_KEY,
+ FASTAPI_HTTP_METHOD_TAG_KEY,
+ FASTAPI_HTTP_PATH_TAG_KEY,
+ FASTAPI_HTTP_USER_ID_TAG_KEY,
+ FASTAPI_API_SERVER_TAG_KEY,
+)
+
+
+def get_app_name(app: FastAPI) -> str:
+ """Gets the FastAPI app name."""
+
+ return getattr(app.state, "name", "unknown")
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/middleware.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/middleware.py
new file mode 100644
index 0000000000000000000000000000000000000000..c12ba682e2071b8827dc3a75aa5d8a0b56af4535
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/middleware.py
@@ -0,0 +1,156 @@
+import time
+from asyncio import CancelledError
+from typing import Dict
+
+from fastapi import FastAPI
+from starlette.requests import Request
+from starlette.routing import Match
+from starlette.types import Message
+
+from ray.llm._internal.serve.core.ingress.middleware import (
+ get_request_id,
+ get_user_id,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+from ray.llm._internal.serve.observability.metrics.fastapi_utils import (
+ FASTAPI_API_SERVER_TAG_KEY,
+ FASTAPI_HTTP_HANDLER_TAG_KEY,
+ FASTAPI_HTTP_METHOD_TAG_KEY,
+ FASTAPI_HTTP_PATH_TAG_KEY,
+ FASTAPI_HTTP_RESPONSE_CODE_TAG_KEY,
+ FASTAPI_HTTP_USER_ID_TAG_KEY,
+ get_app_name,
+)
+
+logger = get_logger("ray.serve")
+
+
+class MeasureHTTPRequestMetricsMiddleware:
+ """Measures and stores HTTP request metrics."""
+
+ def __init__(self, app):
+ self.app = app
+
+ async def __call__(self, scope, receive, send):
+ if scope["type"] not in ("http", "websocket"):
+ await self.app(scope, receive, send)
+
+ # If the status_code isn't set by send_wrapper,
+ # we should consider that an error.
+ status_code = 500
+ send_wrapper_failed_exc_info = "Status code was never set by send_wrapper."
+ exception_info = send_wrapper_failed_exc_info
+
+ async def send_wrapper(message: Message) -> None:
+ """Wraps the send message.
+
+ Enables this middleware to access the response headers.
+ """
+
+ nonlocal status_code, exception_info
+ if message["type"] == "http.response.start":
+ status_code = message.get("status", 500)
+
+ # Clear the send_wrapper_failed_exc_info.
+ if exception_info == send_wrapper_failed_exc_info:
+ exception_info = None
+
+ await send(message)
+
+ request = Request(scope)
+ req_id = get_request_id(request)
+ now = time.monotonic()
+ try:
+ logger.info(f"Starting handling of the request {req_id}")
+ await self.app(scope, receive, send_wrapper)
+
+ except CancelledError as ce:
+ status_code = -1
+ exception_info = ce
+ raise
+
+ except BaseException as e:
+ status_code = 500
+ exception_info = e
+ raise
+
+ finally:
+ duration_s = time.monotonic() - now
+
+ tags = _get_tags(request, status_code, request.app)
+ # NOTE: Custom decorators are not applied to histogram-based metrics
+ # to make sure we can keep cardinality of those in check
+ truncated_tags = {
+ **tags,
+ FASTAPI_HTTP_USER_ID_TAG_KEY: "truncated",
+ }
+
+ request.app.state.http_requests_metrics.inc(1, tags)
+ request.app.state.http_requests_latency_metrics.observe(
+ duration_s, truncated_tags
+ )
+
+ extra_context = {
+ "status_code": status_code,
+ "duration_ms": duration_s * 1000,
+ }
+
+ if status_code >= 400:
+ log = logger.error if status_code >= 500 else logger.warning
+ log(
+ f"Handling of the request {req_id} failed",
+ exc_info=exception_info,
+ extra={"ray_serve_extra_fields": extra_context},
+ )
+ elif status_code == -1:
+ logger.info(
+ f"Handling of the request {req_id} have been cancelled",
+ extra={"ray_serve_extra_fields": extra_context},
+ )
+ else:
+ logger.info(
+ f"Handling of the request {req_id} successfully completed",
+ extra={"ray_serve_extra_fields": extra_context},
+ )
+
+
+def _get_route_details(scope):
+ """
+ Function to retrieve Starlette route from scope.
+ TODO: there is currently no way to retrieve http.route from
+ a starlette application from scope.
+ See: https://github.com/encode/starlette/pull/804
+ Args:
+ scope: A Starlette scope
+ Returns:
+ A string containing the route or None
+ """
+ app = scope["app"]
+ route = None
+
+ for starlette_route in app.routes:
+ match, _ = starlette_route.matches(scope)
+ if match == Match.FULL:
+ route = starlette_route.path
+ break
+ if match == Match.PARTIAL:
+ route = starlette_route.path
+ return route
+
+
+def _get_tags(request: Request, status_code: int, app: FastAPI) -> Dict[str, str]:
+ """Generates tags for the request's metrics."""
+
+ route = str(_get_route_details(request.scope)) or "unknown"
+ path = str(request.url.path) or "unknown"
+ method = str(request.method) or "unknown"
+ user_id = str(get_user_id(request) or "unknown")
+
+ return {
+ FASTAPI_API_SERVER_TAG_KEY: get_app_name(app),
+ FASTAPI_HTTP_RESPONSE_CODE_TAG_KEY: str(status_code),
+ FASTAPI_HTTP_PATH_TAG_KEY: path,
+ FASTAPI_HTTP_HANDLER_TAG_KEY: route,
+ FASTAPI_HTTP_METHOD_TAG_KEY: method,
+ FASTAPI_HTTP_USER_ID_TAG_KEY: user_id,
+ }
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/utils.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..9c49cbcf4d0408637ce8bde474ea7b239dc66d43
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/metrics/utils.py
@@ -0,0 +1,144 @@
+import time
+from enum import Enum
+from typing import (
+ AsyncGenerator,
+ Callable,
+ List,
+ Set,
+ TypeVar,
+)
+
+from ray.util import metrics
+
+# Histogram buckets for short-range latencies measurements:
+# Min=1ms, Max=30s
+#
+# NOTE: Number of buckets have to be bounded (and not exceed 30)
+# to avoid overloading metrics sub-system
+SHORT_RANGE_LATENCY_HISTOGRAM_BUCKETS_MS: List[float] = [
+ 1,
+ 5,
+ 10,
+ 25,
+ 50,
+ 100,
+ 150,
+ 250,
+ 500,
+ 1000,
+ 1500,
+ 2500,
+ 5000,
+ 7500,
+ 10000,
+ 20000,
+ 30000,
+]
+
+# Histogram buckets for long-range latencies measurements:
+# Min=10ms, Max=300s
+LONG_RANGE_LATENCY_HISTOGRAM_BUCKETS_MS = [
+ x * 10 for x in SHORT_RANGE_LATENCY_HISTOGRAM_BUCKETS_MS
+]
+
+
+class ClockUnit(int, Enum):
+ ms = 1000
+ s = 1
+
+
+class MsClock:
+ """A clock that tracks intervals in milliseconds"""
+
+ def __init__(self, unit: ClockUnit = ClockUnit.ms):
+ self.reset()
+ self.unit = unit.value
+ self.start_time = time.perf_counter()
+
+ def reset(self):
+ self.start_time = time.perf_counter()
+
+ def interval(self):
+ return (time.perf_counter() - self.start_time) * self.unit
+
+ def reset_interval(self):
+ interval = self.interval()
+ self.reset()
+ return interval
+
+
+T = TypeVar("T")
+
+
+class InstrumentTokenAsyncGenerator:
+ """This class instruments an asynchronous generator.
+
+ It gathers 3 metrics:
+ 1. Time to first time
+ 2. Time between tokens
+ 3. Total completion time
+
+ Usage:
+
+ @InstrumentTokenAsyncGenerator("my_special_fn")
+ async def to_instrument():
+ yield ...
+ """
+
+ all_instrument_names: Set[str] = set()
+
+ def __init__(
+ self, generator_name: str, latency_histogram_buckets: List[float] = None
+ ):
+ self.generator_name = f"rayllm_{generator_name}"
+
+ target_latency_histogram_buckets = (
+ latency_histogram_buckets or SHORT_RANGE_LATENCY_HISTOGRAM_BUCKETS_MS
+ )
+
+ assert (
+ self.generator_name not in self.all_instrument_names
+ ), "This generator name was already used elsewhere. Please specify another one."
+ self.all_instrument_names.add(self.generator_name)
+
+ self.token_latency_histogram = metrics.Histogram(
+ f"{self.generator_name}_per_token_latency_ms",
+ f"Generator metrics for {self.generator_name}",
+ boundaries=target_latency_histogram_buckets,
+ )
+
+ self.first_token_latency_histogram = metrics.Histogram(
+ f"{self.generator_name}_first_token_latency_ms",
+ f"Generator metrics for {self.generator_name}",
+ boundaries=target_latency_histogram_buckets,
+ )
+ self.total_latency_histogram = metrics.Histogram(
+ f"{self.generator_name}_total_latency_ms",
+ f"Generator metrics for {self.generator_name}",
+ boundaries=target_latency_histogram_buckets,
+ )
+
+ def __call__(
+ self, async_generator_fn: Callable[..., AsyncGenerator[T, None]]
+ ) -> Callable[..., AsyncGenerator[T, None]]:
+ async def new_gen(*args, **kwargs):
+ interval_clock = MsClock()
+ total_clock = MsClock()
+ is_first_token = True
+ try:
+ async for x in async_generator_fn(*args, **kwargs):
+ if is_first_token:
+ self.first_token_latency_histogram.observe(
+ total_clock.interval()
+ )
+ interval_clock.reset()
+ is_first_token = False
+ else:
+ self.token_latency_histogram.observe(
+ interval_clock.reset_interval()
+ )
+ yield x
+ finally:
+ self.total_latency_histogram.observe(total_clock.interval())
+
+ return new_gen
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..89ad1f85dac165be7919a52cdf1a52f3d0487d09
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/__pycache__/usage.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/__pycache__/usage.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..07c5a6482053fe9d31371188155dfaaf6214bd55
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/__pycache__/usage.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/usage.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/usage.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5ce141cfc41267c63075f3a37f09455a4aabd99
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/observability/usage_telemetry/usage.py
@@ -0,0 +1,322 @@
+import random
+import time
+from enum import Enum
+from typing import TYPE_CHECKING, Callable, Dict, List, Optional, Sequence
+
+import ray
+from ray import serve
+from ray._common.usage.usage_lib import (
+ get_hardware_usages_to_report,
+ record_extra_usage_tag,
+)
+from ray.llm._internal.common.base_pydantic import BaseModelExtended
+from ray.llm._internal.common.observability.telemetry_utils import DEFAULT_GPU_TYPE
+from ray.llm._internal.common.utils.lora_utils import get_lora_model_ids
+from ray.llm._internal.serve.observability.logging import get_logger
+
+if TYPE_CHECKING:
+ from ray.llm._internal.serve.core.configs.llm_config import LLMConfig
+
+LLM_SERVE_TELEMETRY_NAMESPACE = "llm_serve_telemetry"
+LLM_SERVE_TELEMETRY_ACTOR_NAME = "llm_serve_telemetry"
+
+logger = get_logger(__name__)
+
+
+class TelemetryTags(str, Enum):
+ """Telemetry tags for LLM SERVE."""
+
+ LLM_SERVE_SERVE_MULTIPLE_MODELS = "LLM_SERVE_SERVE_MULTIPLE_MODELS"
+ LLM_SERVE_SERVE_MULTIPLE_APPS = "LLM_SERVE_SERVE_MULTIPLE_APPS"
+ LLM_SERVE_JSON_MODE_MODELS = "LLM_SERVE_JSON_MODE_MODELS"
+ LLM_SERVE_JSON_MODE_NUM_REPLICAS = "LLM_SERVE_JSON_MODE_NUM_REPLICAS"
+ LLM_SERVE_LORA_BASE_MODELS = "LLM_SERVE_LORA_BASE_MODELS"
+ LLM_SERVE_INITIAL_NUM_LORA_ADAPTERS = "LLM_SERVE_INITIAL_NUM_LORA_ADAPTERS"
+ LLM_SERVE_AUTOSCALING_ENABLED_MODELS = "LLM_SERVE_AUTOSCALING_ENABLED_MODELS"
+ LLM_SERVE_AUTOSCALING_MIN_REPLICAS = "LLM_SERVE_AUTOSCALING_MIN_REPLICAS"
+ LLM_SERVE_AUTOSCALING_MAX_REPLICAS = "LLM_SERVE_AUTOSCALING_MAX_REPLICAS"
+ LLM_SERVE_TENSOR_PARALLEL_DEGREE = "LLM_SERVE_TENSOR_PARALLEL_DEGREE"
+ LLM_SERVE_NUM_REPLICAS = "LLM_SERVE_NUM_REPLICAS"
+ LLM_SERVE_MODELS = "LLM_SERVE_MODELS"
+ LLM_SERVE_GPU_TYPE = "LLM_SERVE_GPU_TYPE"
+ LLM_SERVE_NUM_GPUS = "LLM_SERVE_NUM_GPUS"
+
+
+class TelemetryModel(BaseModelExtended):
+ """Telemetry model for LLM Serve."""
+
+ model_architecture: str
+ num_replicas: int
+ use_json_mode: bool
+ use_lora: bool
+ initial_num_lora_adapters: int
+ use_autoscaling: bool
+ min_replicas: int
+ max_replicas: int
+ tensor_parallel_degree: int
+ gpu_type: str
+ num_gpus: int
+
+
+@ray.remote(
+ name=LLM_SERVE_TELEMETRY_ACTOR_NAME,
+ namespace=LLM_SERVE_TELEMETRY_NAMESPACE,
+ num_cpus=0,
+ lifetime="detached",
+)
+class TelemetryAgent:
+ """Named Actor to keep the state of all deployed models and record telemetry."""
+
+ def __init__(self):
+ self.models: List[TelemetryModel] = []
+ self.record_tag_func = record_extra_usage_tag
+
+ def _update_record_tag_func(self, record_tag_func: Callable) -> None:
+ """This method is only used in tests to record the telemetry tags to a different
+ object than Ray's default `record_extra_usage_tag` function."""
+ self.record_tag_func = record_tag_func
+
+ def _reset_models(self):
+ """This method is only used in tests to clean up the models list."""
+ self.models = []
+
+ def _multiple_models(self) -> str:
+ unique_models = {model.model_architecture for model in self.models}
+ return "1" if len(unique_models) > 1 else "0"
+
+ @staticmethod
+ def _multiple_apps() -> str:
+ try:
+ serve_status = serve.status()
+ except ray.exceptions.ActorDiedError:
+ # This only happens in a workspace and when multiple Serve sessions are
+ # used. Since telemetry agent is long running, it might be connecting to the
+ # previous Serve session, which is already dead. In this case, we need to
+ # call `serve.shutdown()` before the telemetry agent can re-connect to the
+ # existing Serve session.
+ serve.shutdown()
+ serve_status = serve.status()
+ return "1" if len(serve_status.applications) > 1 else "0"
+
+ def _json_mode_models(self) -> str:
+ return ",".join(
+ [model.model_architecture for model in self.models if model.use_json_mode]
+ )
+
+ def _json_mode_num_deployments(self) -> str:
+ return ",".join(
+ [str(model.num_replicas) for model in self.models if model.use_json_mode]
+ )
+
+ def _lora_base_nodes(self) -> str:
+ return ",".join(
+ [model.model_architecture for model in self.models if model.use_lora]
+ )
+
+ def _lora_initial_num_adaptors(self) -> str:
+ return ",".join(
+ [
+ str(model.initial_num_lora_adapters)
+ for model in self.models
+ if model.use_lora
+ ]
+ )
+
+ def _autoscaling_enabled_models(self) -> str:
+ return ",".join(
+ [model.model_architecture for model in self.models if model.use_autoscaling]
+ )
+
+ def _autoscaling_min_replicas(self) -> str:
+ return ",".join(
+ [str(model.min_replicas) for model in self.models if model.use_autoscaling]
+ )
+
+ def _autoscaling_max_replicas(self) -> str:
+ return ",".join(
+ [str(model.max_replicas) for model in self.models if model.use_autoscaling]
+ )
+
+ def _model_architectures(self) -> str:
+ return ",".join([model.model_architecture for model in self.models])
+
+ def _tensor_parallel_degree(self) -> str:
+ return ",".join([str(model.tensor_parallel_degree) for model in self.models])
+
+ def _num_replicas(self) -> str:
+ return ",".join([str(model.num_replicas) for model in self.models])
+
+ def _gpu_type(self) -> str:
+ return ",".join([model.gpu_type for model in self.models])
+
+ def _num_gpus(self) -> str:
+ return ",".join([str(model.num_gpus) for model in self.models])
+
+ def generate_report(self) -> Dict[str, str]:
+ return {
+ TelemetryTags.LLM_SERVE_SERVE_MULTIPLE_MODELS: self._multiple_models(),
+ TelemetryTags.LLM_SERVE_SERVE_MULTIPLE_APPS: self._multiple_apps(),
+ TelemetryTags.LLM_SERVE_JSON_MODE_MODELS: self._json_mode_models(),
+ TelemetryTags.LLM_SERVE_JSON_MODE_NUM_REPLICAS: self._json_mode_num_deployments(),
+ TelemetryTags.LLM_SERVE_LORA_BASE_MODELS: self._lora_base_nodes(),
+ TelemetryTags.LLM_SERVE_INITIAL_NUM_LORA_ADAPTERS: self._lora_initial_num_adaptors(),
+ TelemetryTags.LLM_SERVE_AUTOSCALING_ENABLED_MODELS: self._autoscaling_enabled_models(),
+ TelemetryTags.LLM_SERVE_AUTOSCALING_MIN_REPLICAS: self._autoscaling_min_replicas(),
+ TelemetryTags.LLM_SERVE_AUTOSCALING_MAX_REPLICAS: self._autoscaling_max_replicas(),
+ TelemetryTags.LLM_SERVE_MODELS: self._model_architectures(),
+ TelemetryTags.LLM_SERVE_TENSOR_PARALLEL_DEGREE: self._tensor_parallel_degree(),
+ TelemetryTags.LLM_SERVE_NUM_REPLICAS: self._num_replicas(),
+ TelemetryTags.LLM_SERVE_GPU_TYPE: self._gpu_type(),
+ TelemetryTags.LLM_SERVE_NUM_GPUS: self._num_gpus(),
+ }
+
+ def record(self, model: Optional[TelemetryModel] = None) -> None:
+ """Record telemetry model."""
+ from ray._common.usage.usage_lib import TagKey
+
+ if model:
+ self.models.append(model)
+
+ for key, value in self.generate_report().items():
+ try:
+ self.record_tag_func(TagKey.Value(key), value)
+ except ValueError:
+ # If the key doesn't exist in the TagKey enum, skip it.
+ continue
+
+
+def _get_or_create_telemetry_agent() -> TelemetryAgent:
+ """Helper to get or create the telemetry agent."""
+ try:
+ telemetry_agent = ray.get_actor(
+ LLM_SERVE_TELEMETRY_ACTOR_NAME, namespace=LLM_SERVE_TELEMETRY_NAMESPACE
+ )
+ except ValueError:
+ from ray._common.constants import HEAD_NODE_RESOURCE_NAME
+ from ray.util.scheduling_strategies import PlacementGroupSchedulingStrategy
+
+ telemetry_agent = TelemetryAgent.options(
+ # Ensure the actor is created on the head node.
+ resources={HEAD_NODE_RESOURCE_NAME: 0.001},
+ # Ensure the actor is not scheduled with the existing placement group.
+ scheduling_strategy=PlacementGroupSchedulingStrategy(placement_group=None),
+ ).remote()
+
+ return telemetry_agent
+
+
+def _retry_get_telemetry_agent(
+ max_retries: int = 5, base_delay: float = 0.1
+) -> TelemetryAgent:
+ max_retries = 5
+ base_delay = 0.1
+
+ telemetry_agent = None
+ for attempt in range(max_retries):
+ try:
+ telemetry_agent = _get_or_create_telemetry_agent()
+ return telemetry_agent
+ except ValueError as e:
+ # Due to race conditions among multiple replicas, we may get:
+ # ValueError: Actor with name 'llm_serve_telemetry' already
+ # exists in the namespace llm_serve_telemetry
+ logger.info(
+ "Attempt %s/%s to get telemetry agent failed", attempt + 1, max_retries
+ )
+ if attempt == max_retries - 1:
+ raise e
+
+ # Exponential backoff with jitter
+ exponential_delay = base_delay * (2**attempt)
+ jitter = random.uniform(0, 0.5)
+ delay = exponential_delay + jitter
+ # Max total wait time is ~3.5 seconds for 5 attempts.
+ time.sleep(delay)
+
+
+def _push_telemetry_report(model: Optional[TelemetryModel] = None) -> None:
+ """Push telemetry report for a model."""
+ telemetry_agent = _retry_get_telemetry_agent()
+ assert telemetry_agent is not None
+ ray.get(telemetry_agent.record.remote(model))
+
+
+class HardwareUsage:
+ """Hardware usage class to report telemetry."""
+
+ def __init__(self, get_hardware_fn: Callable = get_hardware_usages_to_report):
+ self._get_hardware_fn = get_hardware_fn
+
+ def infer_gpu_from_hardware(self) -> str:
+ """Infer the GPU type from the hardware when the accelerator type on llm config is
+ not specified.
+
+ Iterate through all the hardware recorded on the cluster and return the first
+ ray-compatible accelerator as the GPU type used for the deployment. If not, return
+ `UNSPECIFIED` as the default GPU type.
+ """
+ from ray.llm._internal.serve.core.configs.llm_config import GPUType
+
+ all_accelerator_types = [t.value for t in GPUType]
+ gcs_client = ray.experimental.internal_kv.internal_kv_get_gcs_client()
+ hardwares = self._get_hardware_fn(gcs_client)
+ for hardware in hardwares:
+ if hardware in all_accelerator_types:
+ return hardware
+
+ return DEFAULT_GPU_TYPE
+
+
+def push_telemetry_report_for_all_models(
+ all_models: Optional[Sequence["LLMConfig"]] = None,
+ get_lora_model_func: Callable = get_lora_model_ids,
+ get_hardware_fn: Callable = get_hardware_usages_to_report,
+):
+ """Push telemetry report for all models."""
+ if not all_models:
+ return
+
+ for model in all_models:
+ use_lora = (
+ model.lora_config is not None
+ and model.lora_config.dynamic_lora_loading_path is not None
+ )
+ initial_num_lora_adapters = 0
+ if use_lora:
+ lora_model_ids = get_lora_model_func(
+ dynamic_lora_loading_path=model.lora_config.dynamic_lora_loading_path,
+ base_model_id=model.model_id,
+ )
+ initial_num_lora_adapters = len(lora_model_ids)
+
+ use_autoscaling = model.deployment_config.get("autoscaling_config") is not None
+ num_replicas, min_replicas, max_replicas = 1, 1, 1
+ if use_autoscaling:
+ from ray.serve.config import AutoscalingConfig
+
+ autoscaling_config = AutoscalingConfig(
+ **model.deployment_config["autoscaling_config"]
+ )
+ num_replicas = (
+ autoscaling_config.initial_replicas or autoscaling_config.min_replicas
+ )
+ min_replicas = autoscaling_config.min_replicas
+ max_replicas = autoscaling_config.max_replicas
+
+ engine_config = model.get_engine_config()
+ hardware_usage = HardwareUsage(get_hardware_fn)
+
+ telemetry_model = TelemetryModel(
+ model_architecture=model.model_architecture,
+ num_replicas=num_replicas,
+ use_json_mode=True,
+ use_lora=use_lora,
+ initial_num_lora_adapters=initial_num_lora_adapters,
+ use_autoscaling=use_autoscaling,
+ min_replicas=min_replicas,
+ max_replicas=max_replicas,
+ tensor_parallel_degree=engine_config.tensor_parallel_degree,
+ gpu_type=model.accelerator_type or hardware_usage.infer_gpu_from_hardware(),
+ num_gpus=engine_config.num_devices,
+ )
+ _push_telemetry_report(telemetry_model)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__init__.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__init__.py
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/__init__.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/__init__.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..cd1125c6ab9920f94014135528d55fa5d49b01d3
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/__init__.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/batcher.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/batcher.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..aa158859cd2bdc54a7f044ce327644a941556796
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/batcher.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/lora_serve_utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/lora_serve_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..61c5f98017545b7937d8ee9b178014976e9a5a5b
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/lora_serve_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/node_initialization_utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/node_initialization_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..2904d9b95b2ddabdec2af643cd24137276717e7a
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/node_initialization_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/registry.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/registry.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..f2b0d3b8e48e1e49ae042df13f52bce2e6d41028
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/registry.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/server_utils.cpython-312.pyc b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/server_utils.cpython-312.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..18d4aebc4558ea83211b006116cb4d94b94bd9a1
Binary files /dev/null and b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/__pycache__/server_utils.cpython-312.pyc differ
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/batcher.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/batcher.py
new file mode 100644
index 0000000000000000000000000000000000000000..1b2a941cb178e59165f190a9216ad660e4e9f319
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/batcher.py
@@ -0,0 +1,111 @@
+import asyncio
+from typing import AsyncGenerator, Generic, Iterable, List, Optional, TypeVar
+
+from ray.llm._internal.serve.constants import (
+ MODEL_RESPONSE_BATCH_TIMEOUT_MS,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+
+logger = get_logger(__name__)
+
+T = TypeVar("T")
+
+
+class Batcher(Generic[T]):
+ """This class batches multiple responses from a generator into a list of
+ single responses, at some time interval.
+
+ Args:
+ generator: the async generator that this class pulls responses
+ from.
+ interval_ms: the interval at which this class yields the current batch.
+ If None, this class will batch all responses from the generator
+ together and yield the entire batch once.
+ """
+
+ def __init__(
+ self,
+ generator: AsyncGenerator[T, None],
+ interval_ms: Optional[float] = MODEL_RESPONSE_BATCH_TIMEOUT_MS,
+ ):
+ self.generator = generator
+ self.queue: asyncio.Queue = asyncio.Queue()
+
+ if interval_ms is None:
+ self.interval_s = None
+ else:
+ self.interval_s = interval_ms / 1000
+
+ if interval_ms == 0:
+ return
+
+ self.done_event: asyncio.Event = asyncio.Event()
+
+ # We are okay with this task getting cancelled (to propagate cancellations)
+ self.read_task = asyncio.create_task(self.read())
+
+ def _merge_results(self, results: List[T]) -> Iterable[T]:
+ return results
+
+ async def stream(self) -> AsyncGenerator[Iterable[T], None]:
+ """Drain from the queue every interval_ms and yield the merged results"""
+
+ if self.interval_s == 0:
+ async for item in self.generator:
+ yield [item]
+
+ return
+
+ try:
+ while True:
+ # Wait for the interval or until we finish, whichever is faster.
+ # We use an event to avoid asyncio.wait_for cancelling the real task on timeout.
+ try:
+ if self.interval_s is None:
+ await self.done_event.wait()
+ else:
+ await asyncio.wait_for(
+ self.done_event.wait(), timeout=self.interval_s
+ )
+ except asyncio.TimeoutError:
+ pass
+
+ # Get all elements from the queue
+ results, is_done = self.check_done_and_drain()
+
+ # If there are results, merge and yield them
+ if results:
+ output = self._merge_results(results)
+ yield output
+
+ # If the read task is done, exit the stream task
+ if is_done:
+ # Raise exception, if any
+ self.read_task.result()
+ break
+ finally:
+ # If the stream task is done, make sure to exit the read task
+ if not self.read_task.done():
+ self.read_task.cancel()
+
+ def check_done_and_drain(self):
+ results = self.drain_queue()
+ return results, self.read_task.done()
+
+ async def read(self):
+ """Read from the generator and put into the queue in a tight loop"""
+ try:
+ async for x in self.generator:
+ self.queue.put_nowait(x)
+ finally:
+ self.done_event.set()
+
+ def drain_queue(self):
+ """Drain all results currently in the queue"""
+ results = []
+ try:
+ while True:
+ results.append(self.queue.get_nowait())
+ except asyncio.QueueEmpty:
+ pass
+ return results
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/lora_serve_utils.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/lora_serve_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..eb15658a39ba32d3ef0bb80c20ce193e1ab02d91
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/lora_serve_utils.py
@@ -0,0 +1,233 @@
+"""
+Serve-specific LoRA utilities that use generic abstractions from lora_utils.py.
+
+This module provides serve-specific functionality while using the generic
+LoRA abstractions from common/lora_utils.py. This ensures clean separation
+between generic and serve-specific concerns.
+"""
+
+import asyncio
+import json
+import os
+from typing import Any, Dict, Optional
+
+from fastapi import HTTPException
+
+from ray.llm._internal.common.constants import LORA_ADAPTER_CONFIG_NAME
+from ray.llm._internal.common.models import global_id_manager, make_async
+from ray.llm._internal.common.utils.cloud_utils import (
+ LoraMirrorConfig,
+)
+from ray.llm._internal.common.utils.lora_utils import (
+ CLOUD_OBJECT_MISSING,
+ clean_model_id,
+ clear_directory,
+ get_base_model_id,
+ get_lora_id,
+ get_object_from_cloud,
+ retry_with_exponential_backoff,
+ sync_files_with_lock,
+)
+from ray.llm._internal.serve.core.configs.llm_config import (
+ DiskMultiplexConfig,
+ LLMConfig,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+
+logger = get_logger(__name__)
+
+
+async def get_lora_finetuned_context_length(bucket_uri: str) -> Optional[int]:
+ """Gets the sequence length used to tune the LoRA adapter.
+
+ Return: Returns the max sequence length for the adapter, if it exists.
+
+ Raises: HTTPException if the LoRA adapter config file isn't available
+ in the cloud storage repository.
+ """
+ if bucket_uri.endswith("/"):
+ bucket_uri = bucket_uri.rstrip("/")
+ object_uri = f"{bucket_uri}/{LORA_ADAPTER_CONFIG_NAME}"
+
+ object_str_or_missing_message = await get_object_from_cloud(object_uri)
+
+ if object_str_or_missing_message is CLOUD_OBJECT_MISSING:
+ raise HTTPException(
+ 404,
+ f"Unable to find LoRA adapter config file "
+ f'"{LORA_ADAPTER_CONFIG_NAME}" in folder {bucket_uri}. '
+ "Check that the file exists and that you have read permissions.",
+ )
+ else:
+ adapter_config_str = object_str_or_missing_message
+ adapter_config = json.loads(adapter_config_str)
+ return adapter_config.get("max_length")
+
+
+async def download_multiplex_config_info(
+ model_id: str, base_path: str
+) -> tuple[str, Optional[int]]:
+ """Downloads info needed to create a multiplex config.
+
+ Downloads objects using cloud storage provider APIs.
+
+ Returns: 2-tuple containing
+ 1. A bucket_uri for the bucket containing LoRA weights and config.
+ 2. The maximum LoRA sequence length.
+
+ Raises: HTTPException if the LoRA adapter config file isn't available
+ in the cloud storage repository.
+ """
+ bucket_uri = f"{base_path}/{model_id}"
+ ft_context_length = await get_lora_finetuned_context_length(bucket_uri)
+ return bucket_uri, ft_context_length
+
+
+async def get_lora_model_metadata(
+ model_id: str, llm_config: LLMConfig
+) -> Dict[str, Any]:
+ """Get the lora model metadata for a given model id and llm config.
+
+ This is used to get the metadata for the model with the given model id.
+ """
+ # Note (genesu): `model_id` passed is a lora model id where it's in a form of
+ # base_model_id:suffix:id
+ base_model_id = get_base_model_id(model_id)
+ lora_id = get_lora_id(model_id)
+ base_path = llm_config.lora_config.dynamic_lora_loading_path
+
+ # Examples of the variables:
+ # model_id: "meta-llama/Meta-Llama-3.1-8B-Instruct:my_suffix:aBc1234"
+ # base_path: "s3://ray-llama-weights"
+ # bucket_uri: "s3://ray-llama-weights/my_suffix:aBc1234"
+ (
+ bucket_uri,
+ ft_context_length,
+ ) = await download_multiplex_config_info(lora_id, base_path)
+
+ return {
+ "model_id": model_id,
+ "base_model_id": base_model_id,
+ "max_request_context_length": ft_context_length,
+ # Note (genesu): `bucket_uri` affects where the lora weights are downloaded
+ # from remote location.
+ "bucket_uri": bucket_uri,
+ }
+
+
+async def get_lora_mirror_config(
+ model_id: str,
+ llm_config: LLMConfig,
+) -> LoraMirrorConfig:
+ """Get LoRA mirror configuration for serve-specific LLM config."""
+ metadata = await get_lora_model_metadata(model_id, llm_config)
+
+ return LoraMirrorConfig(
+ lora_model_id=model_id,
+ bucket_uri=metadata["bucket_uri"],
+ max_total_tokens=metadata["max_request_context_length"],
+ sync_args=None,
+ )
+
+
+class LoraModelLoader:
+ """Download LoRA weights from remote storage and manage disk cache.
+
+ This class is serve-specific as it depends on DiskMultiplexConfig and
+ other serve-specific concepts.
+ """
+
+ def __init__(
+ self,
+ lora_root: Optional[str] = None,
+ download_timeout_s: Optional[float] = None,
+ max_tries: int = 1,
+ ):
+ self.lora_root = lora_root or "/tmp/ray/llm/lora/cache"
+ self.disk_cache: Dict[str, DiskMultiplexConfig] = {}
+ self.active_syncing_tasks: Dict[str, asyncio.Task[DiskMultiplexConfig]] = {}
+ if download_timeout_s is not None and download_timeout_s <= 0:
+ raise ValueError(
+ f"download_timeout_s must be None or >0, got {download_timeout_s}"
+ )
+ self.download_timeout_s = download_timeout_s
+ if max_tries < 1:
+ raise ValueError(f"max_tries must be >=1, got {max_tries}")
+ self.max_tries = max_tries
+
+ async def load_model_from_config(
+ self, lora_model_id: str, llm_config
+ ) -> DiskMultiplexConfig:
+ """Load a LoRA model by first fetching its mirror config from S3."""
+ lora_mirror_config = await get_lora_mirror_config(lora_model_id, llm_config)
+ return await self.load_model(lora_model_id, lora_mirror_config)
+
+ async def load_model(
+ self, lora_model_id: str, lora_mirror_config: LoraMirrorConfig
+ ) -> DiskMultiplexConfig:
+ """Load a LoRA model."""
+ if lora_model_id in self.disk_cache:
+ return self.disk_cache[lora_model_id]
+
+ if lora_model_id not in self.active_syncing_tasks:
+ task = asyncio.create_task(self._load_model_async(lora_mirror_config))
+ task.add_done_callback(
+ lambda result: self.active_syncing_tasks.pop(lora_model_id, None)
+ )
+ self.active_syncing_tasks[lora_model_id] = task
+ else:
+ task = self.active_syncing_tasks[lora_model_id]
+
+ disk_config = await asyncio.shield(task)
+ self.disk_cache[lora_model_id] = disk_config
+ return disk_config
+
+ async def _load_model_async(
+ self, lora_mirror_config: LoraMirrorConfig
+ ) -> DiskMultiplexConfig:
+ return await self._load_model(lora_mirror_config)
+
+ @make_async
+ def _load_model(self, lora_mirror_config: LoraMirrorConfig) -> DiskMultiplexConfig:
+ return self._load_model_sync(lora_mirror_config)
+
+ @make_async
+ def clear_cache(self):
+ """Clear the disk cache."""
+ clear_directory(self.lora_root)
+
+ def _model_dir_path(self, model_id: str) -> str:
+ """Construct the path for the lora weight."""
+ lora_id = get_lora_id(clean_model_id(model_id))
+ path = os.path.join(self.lora_root, lora_id)
+ os.makedirs(path, exist_ok=True)
+ return path
+
+ def _download_lora(self, lora_mirror_config: LoraMirrorConfig) -> str:
+ """Download LoRA weights using generic download primitives."""
+ model_local_path = self._model_dir_path(lora_mirror_config.lora_model_id)
+ sync_files_with_lock(
+ lora_mirror_config.bucket_uri,
+ model_local_path,
+ timeout=self.download_timeout_s,
+ )
+ return model_local_path
+
+ def _load_model_sync(
+ self, lora_mirror_config: LoraMirrorConfig
+ ) -> DiskMultiplexConfig:
+ """Load a model from the given mirror configuration."""
+ download_with_retries = retry_with_exponential_backoff(
+ max_tries=self.max_tries,
+ exception_to_check=Exception,
+ )(lambda config: self._download_lora(config))
+
+ local_path = download_with_retries(lora_mirror_config)
+ return DiskMultiplexConfig.model_validate(
+ {
+ "model_id": lora_mirror_config.lora_model_id,
+ "max_total_tokens": lora_mirror_config.max_total_tokens,
+ "local_path": local_path,
+ "lora_assigned_int_id": global_id_manager.next(),
+ }
+ )
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/node_initialization_utils.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/node_initialization_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..ce1d04a5fe73a4179a2a97c28a3c69b3ce932e34
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/node_initialization_utils.py
@@ -0,0 +1,75 @@
+import asyncio
+from typing import Optional
+
+import ray
+from ray.llm._internal.common.utils.download_utils import (
+ download_model_files,
+)
+from ray.llm._internal.common.utils.import_utils import try_import
+from ray.llm._internal.serve.core.configs.llm_config import LLMConfig
+from ray.llm._internal.serve.observability.logging import get_logger
+
+torch = try_import("torch")
+transformers = try_import("transformers")
+
+logger = get_logger(__name__)
+
+
+def initialize_remote_node(llm_config: LLMConfig) -> Optional[str]:
+
+ callback = llm_config.get_or_create_callback()
+ engine_config = llm_config.get_engine_config()
+
+ local_path = download_model_files(
+ model_id=engine_config.actual_hf_model_id,
+ mirror_config=engine_config.mirror_config,
+ download_model=callback.ctx.worker_node_download_model,
+ download_extra_files=True,
+ callback=callback,
+ )
+
+ # Validate that the binary exists
+ if local_path and local_path != engine_config.actual_hf_model_id:
+ engine_config.hf_model_id = local_path
+
+ return local_path
+
+
+async def initialize_node(llm_config: LLMConfig):
+ """Implements node initialization for LLM engines.
+
+ Downloads model, tokenizer, and extra files as necessary.
+ """
+ # Get callback instance (if configured) with context information
+ callback = llm_config.get_or_create_callback()
+ ctx = callback.ctx
+ pg_table = ray.util.placement_group_table(ctx.placement_group)
+
+ node_set = set(pg_table["bundles_to_node_id"].values())
+ download_tasks = []
+ for node_id in node_set:
+ node_affinity_strategy = (
+ ray.util.scheduling_strategies.NodeAffinitySchedulingStrategy(
+ node_id=node_id,
+ soft=False,
+ )
+ )
+ download_tasks.append(
+ ray.remote(initialize_remote_node).options(
+ num_cpus=1,
+ scheduling_strategy=node_affinity_strategy,
+ runtime_env=ctx.runtime_env,
+ )
+ )
+
+ logger.info("Running tasks to download model files on worker nodes")
+ paths = await asyncio.gather(
+ *[download_task.remote(llm_config) for download_task in download_tasks]
+ )
+
+ # assume that all paths are the same
+ assert paths, "No paths returned from download_model_files"
+ assert (
+ len(set(paths)) == 1
+ ), "Paths returned from download_model_files are not the same"
+ llm_config.get_engine_config().hf_model_id = paths[0]
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/registry.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/registry.py
new file mode 100644
index 0000000000000000000000000000000000000000..e20bf5fd5d57cc47aff731386d81737e2c4da311
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/registry.py
@@ -0,0 +1,328 @@
+"""Generic registry for LLM serving components using Ray's internal KV store.
+
+This module provides a reusable registry mechanism that enables components to be
+registered in the driver process and accessed across all Ray processes in the cluster,
+including Ray Serve child processes.
+
+Similar to RLlib/Tune's registry but with a fixed global prefix for cross-job access.
+"""
+
+import importlib
+from typing import Any, Callable
+
+import ray._private.worker as worker
+import ray.cloudpickle as pickle
+from ray.experimental.internal_kv import (
+ _internal_kv_del,
+ _internal_kv_exists,
+ _internal_kv_get,
+ _internal_kv_initialized,
+ _internal_kv_put,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+
+logger = get_logger(__name__)
+
+
+# Fixed prefix for cross-job accessibility (Serve deployments run in different jobs)
+_SERVE_REGISTRY_PREFIX = "serve_global"
+
+
+def _make_key(category: str, name: str) -> bytes:
+ """Generate a binary key for the KV store.
+
+ Args:
+ category: The component category (e.g., "kv_connector_backend")
+ name: The component name
+
+ Returns:
+ The key to use for storing the value
+ """
+ return (
+ b"LLMServeRegistry:"
+ + _SERVE_REGISTRY_PREFIX.encode("ascii")
+ + b":"
+ + category.encode("ascii")
+ + b"/"
+ + name.encode("ascii")
+ )
+
+
+def _create_loader(value: Any) -> Callable[[], Any]:
+ """Create a loader callable for a value.
+
+ Handles both direct objects/classes and string paths for lazy loading.
+
+ Args:
+ value: Either:
+ - A class, object, or callable (returns lambda: value)
+ - A string in format "module_path:class_name" (creates import loader)
+
+ Returns:
+ A callable that returns the value when called
+
+ Raises:
+ ValueError: If value is a string but doesn't have the correct format
+ """
+ if isinstance(value, str):
+ if ":" not in value:
+ raise ValueError(
+ f"Invalid format for string value: '{value}'. "
+ f"Expected format: 'module_path:class_name' or a class/object."
+ )
+ module_path, class_name = value.rsplit(":", 1)
+ # Create a loader callable that imports on demand
+ def loader():
+ module = importlib.import_module(module_path)
+ return getattr(module, class_name)
+
+ return loader
+ else:
+ # For direct objects/classes, create a simple loader
+ return lambda: value
+
+
+class ComponentRegistry:
+ """Generic registry for LLM serving components using Ray's internal KV store.
+
+ This registry enables components to be registered in the driver process and
+ accessed across all Ray processes in the cluster, including Ray Serve child processes.
+
+ Similar to RLlib/Tune's registry but with a fixed global prefix for cross-job access.
+
+ **Usage Pattern:**
+ This registry is designed for a "register once, read many" pattern:
+ - Components are typically registered in the driver process before deployment
+ - Ray Serve replicas read from the KV store during initialization
+ - Once a component is resolved and cached in a process, subsequent `get()` calls return the cached value without checking the KV store for updates
+
+ Example:
+ # Create a registry for a component category
+ registry = ComponentRegistry("my_component")
+
+ # Register a component
+ registry.register("my_component", MyComponentClass)
+
+ # Get a registered component
+ component = registry.get("my_component")
+
+ # Check if registered
+ if registry.contains("my_component"):
+ ...
+ """
+
+ def __init__(self, category: str):
+ """Initialize a registry for a specific component category.
+
+ Args:
+ category: The category name (e.g., "kv_connector_backend")
+ """
+ self.category = category
+ self._loader_cache: dict[str, Callable[[], Any]] = {}
+ self._resolved_cache: dict[str, Any] = {}
+ self._pending: dict[str, bytes] = {}
+
+ def register(self, name: str, value: Any) -> None:
+ """Register a component.
+
+ Args:
+ name: The name to register under
+ value: The component to register. Can be:
+ - A class, object, or callable (serialized directly)
+ - A string in format "module_path:class_name" (lazy-loaded via import)
+
+ Raises:
+ ValueError: If the component is already registered. Use unregister() first if you need to change the registration.
+
+ Examples:
+ # Register a class directly
+ registry.register("MyClass", MyClass)
+
+ # Register via module path (lazy loading)
+ registry.register("MyClass", "my.module:MyClass")
+ """
+ # Prevent double registration to avoid cache inconsistencies
+ if self.contains(name):
+ raise ValueError(
+ f"{self.category} '{name}' is already registered. "
+ f"Use unregister() first if you need to change the registration."
+ )
+
+ # Create a loader callable (handles both direct values and string paths)
+ loader = _create_loader(value)
+
+ # Serialize the loader callable
+ serialized = pickle.dumps(loader)
+
+ # Store loader in cache
+ self._loader_cache[name] = loader
+
+ # Store in KV store if Ray is initialized, otherwise queue for later
+ if _internal_kv_initialized():
+ try:
+ key = _make_key(self.category, name)
+ _internal_kv_put(key, serialized, overwrite=True)
+ logger.debug(f"Registered {self.category} '{name}' in KV store")
+ except Exception as e:
+ logger.warning(
+ f"Failed to register {self.category} '{name}' in KV store: {e}",
+ exc_info=True,
+ )
+ self._pending[name] = serialized
+ else:
+ self._pending[name] = serialized
+
+ def get(self, name: str) -> Any:
+ """Get a registered component.
+
+ Args:
+ name: The name of the component
+
+ Returns:
+ The registered component. If registered with a string path,
+ returns the imported class/object. If registered directly,
+ returns the original value.
+
+ Raises:
+ ValueError: If the component is not registered
+ """
+ # Check resolved cache first.
+ if name in self._resolved_cache:
+ return self._resolved_cache[name]
+
+ loader = self._loader_cache.get(name)
+ # If not in local loader cache, try fetching from KV store.
+ if loader is None and _internal_kv_initialized():
+ try:
+ key = _make_key(self.category, name)
+ serialized = _internal_kv_get(key)
+ if serialized is not None:
+ loader = pickle.loads(serialized)
+ # Cache the loader for future gets.
+ self._loader_cache[name] = loader
+ logger.debug(f"Loaded {self.category} '{name}' from KV store")
+ except Exception as e:
+ logger.warning(
+ f"Failed to load {self.category} '{name}' from KV store: {e}",
+ exc_info=True,
+ )
+
+ if loader is not None:
+ value = loader()
+ self._resolved_cache[name] = value
+ return value
+
+ # Not found
+ raise ValueError(
+ f"{self.category} '{name}' not found. "
+ f"Registered: {list(self._loader_cache.keys())}"
+ )
+
+ def contains(self, name: str) -> bool:
+ """Check if a component is registered.
+
+ Args:
+ name: The name to check
+
+ Returns:
+ True if registered, False otherwise
+ """
+ if name in self._loader_cache:
+ return True
+
+ if _internal_kv_initialized():
+ try:
+ key = _make_key(self.category, name)
+ return _internal_kv_exists(key)
+ except Exception as e:
+ logger.warning(
+ f"Failed to check if {self.category} '{name}' exists in KV store: {e}",
+ exc_info=True,
+ )
+ return False
+
+ return False
+
+ def unregister(self, name: str) -> None:
+ """Unregister a component.
+
+ Removes the component from local cache, pending registrations, and KV store.
+
+ Args:
+ name: The name of the component to unregister
+ """
+ # Remove from local caches
+ if name in self._loader_cache:
+ del self._loader_cache[name]
+ if name in self._resolved_cache:
+ del self._resolved_cache[name]
+
+ # Remove from pending if present
+ if name in self._pending:
+ del self._pending[name]
+
+ # Remove from KV store if Ray is initialized
+ if _internal_kv_initialized():
+ try:
+ key = _make_key(self.category, name)
+ _internal_kv_del(key)
+ logger.debug(f"Unregistered {self.category} '{name}' from KV store")
+ except Exception as e:
+ logger.warning(
+ f"Failed to unregister {self.category} '{name}' from KV store: {e}",
+ exc_info=True,
+ )
+
+ def flush_pending(self) -> None:
+ """Flush pending registrations to KV store.
+
+ This is called automatically when Ray initializes via _post_init_hooks.
+ """
+ if not _internal_kv_initialized() or not self._pending:
+ return
+
+ for name, serialized in self._pending.items():
+ try:
+ key = _make_key(self.category, name)
+ _internal_kv_put(key, serialized, overwrite=True)
+ logger.debug(
+ f"Flushed pending registration for {self.category} '{name}'"
+ )
+ except Exception as e:
+ logger.warning(
+ f"Failed to flush {self.category} '{name}': {e}", exc_info=True
+ )
+
+ self._pending.clear()
+
+
+# Global registry instances for different component categories
+_registries: dict[str, ComponentRegistry] = {}
+
+
+def get_registry(category: str) -> ComponentRegistry:
+ """Get or create a registry for a component category.
+
+ Args:
+ category: The component category name
+
+ Returns:
+ The ComponentRegistry instance for this category
+ """
+ if category not in _registries:
+ _registries[category] = ComponentRegistry(category)
+ return _registries[category]
+
+
+def _flush_all_registries():
+ """Flush all pending registrations to KV store.
+
+ This is registered as a Ray post-init hook to ensure registrations
+ made before Ray initialization are available across processes.
+ """
+ for registry in _registries.values():
+ registry.flush_pending()
+
+
+if _flush_all_registries not in worker._post_init_hooks:
+ worker._post_init_hooks.append(_flush_all_registries)
diff --git a/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/server_utils.py b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/server_utils.py
new file mode 100644
index 0000000000000000000000000000000000000000..dfeaad8e94e34c44f8de3c591c03f5ea5d25bb41
--- /dev/null
+++ b/lib/python3.12/site-packages/ray/llm/_internal/serve/utils/server_utils.py
@@ -0,0 +1,137 @@
+import asyncio
+import traceback
+from functools import partial
+from typing import Awaitable, Callable, TypeVar
+
+from fastapi import HTTPException, status
+from httpx import HTTPStatusError as HTTPXHTTPStatusError
+from pydantic import ValidationError as PydanticValidationError
+
+from ray import serve
+from ray.llm._internal.serve.core.configs.openai_api_models import (
+ ErrorInfo,
+ ErrorResponse,
+ OpenAIHTTPException,
+)
+from ray.llm._internal.serve.observability.logging import get_logger
+
+logger = get_logger(__name__)
+
+T = TypeVar("T")
+
+
+def make_async(_func: Callable[..., T]) -> Callable[..., Awaitable[T]]:
+ """Take a blocking function, and run it on in an executor thread.
+
+ This function prevents the blocking function from blocking the asyncio event loop.
+ The code in this function needs to be thread safe.
+ """
+
+ def _async_wrapper(*args, **kwargs) -> asyncio.Future:
+ loop = asyncio.get_event_loop()
+ func = partial(_func, *args, **kwargs)
+ return loop.run_in_executor(executor=None, func=func)
+
+ return _async_wrapper
+
+
+def extract_message_from_exception(e: Exception) -> str:
+ # If the exception is a Ray exception, we need to dig through the text to get just
+ # the exception message without the stack trace
+ # This also works for normal exceptions (we will just return everything from
+ # format_exception_only in that case)
+ message_lines = traceback.format_exception_only(type(e), e)[-1].strip().split("\n")
+ message = ""
+ # The stack trace lines will be prefixed with spaces, so we need to start from the bottom
+ # and stop at the last line before a line with a space
+ found_last_line_before_stack_trace = False
+ for line in reversed(message_lines):
+ if not line.startswith(" "):
+ found_last_line_before_stack_trace = True
+ if found_last_line_before_stack_trace and line.startswith(" "):
+ break
+ message = line + "\n" + message
+ message = message.strip()
+ return message
+
+
+def _extract_message(e):
+ if isinstance(e, OpenAIHTTPException) and e.internal_message is not None:
+ internal_message = e.internal_message
+ else:
+ internal_message = extract_message_from_exception(e)
+
+ if isinstance(e, HTTPException):
+ message = e.detail
+ elif isinstance(e, OpenAIHTTPException):
+ message = e.message
+ else:
+ message = internal_message
+
+ return internal_message, message
+
+
+def get_response_for_error(
+ e: Exception,
+ request_id: str,
+) -> ErrorResponse:
+ if isinstance(e, HTTPException):
+ status_code = e.status_code
+ elif isinstance(e, OpenAIHTTPException):
+ status_code = e.status_code
+ elif isinstance(e, PydanticValidationError):
+ status_code = 400
+ elif isinstance(e, HTTPXHTTPStatusError):
+ status_code = e.response.status_code
+ else:
+ # Try to get the status code attribute from exception,
+ # if not present, fallback to generic 500
+ status_code = int(
+ getattr(e, "status_code", status.HTTP_500_INTERNAL_SERVER_ERROR)
+ )
+
+ log = logger.error if status_code >= 500 else logger.warning
+ log(
+ f"Encountered failure while handling request {request_id}",
+ exc_info=e,
+ extra={"ray_serve_extra_fields": {"status_code": status_code}},
+ )
+
+ if status_code == status.HTTP_500_INTERNAL_SERVER_ERROR:
+ internal_message = message = "Internal Server Error"
+ exc_type = "InternalServerError"
+ else:
+ internal_message, message = _extract_message(e)
+ exc_type = e.__class__.__name__
+
+ # TODO make this more robust
+ if "(Request ID: " not in message:
+ message += f" (Request ID: {request_id})"
+
+ if "(Request ID: " not in internal_message:
+ internal_message += f" (Request ID: {request_id})"
+
+ error_info = ErrorInfo(
+ message=f"Message: {message}, Internal exception: {internal_message}, original exception: {str(e)}",
+ code=status_code,
+ type=exc_type,
+ )
+ error_response = ErrorResponse(error=error_info)
+ return error_response
+
+
+def get_serve_request_id() -> str:
+ """Get request id from serve request context."""
+ context = serve.context._serve_request_context.get()
+ if context is not None:
+ return context.request_id
+ return ""
+
+
+def get_model_request_id(model: str):
+ return model + "-" + get_serve_request_id()
+
+
+def replace_prefix(model: str) -> str:
+ """Replace -- with / in model name to handle slashes within the URL path segment"""
+ return model.replace("--", "/")