speedy-utils 2.0.1__tar.gz → 2.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- speedy_utils-2.0.1/src/speedy_utils/common/__init__.py → speedy_utils-2.0.2/.codex +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/CLAUDE.md +30 -10
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/PKG-INFO +32 -1
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/README.md +30 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/integration_test.py +13 -13
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/docs/GENERATE_QUICKREF.md +10 -2
- speedy_utils-2.0.2/docs/MIGRATION_THINKING.md +73 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/docs/TOKENIZATION.md +31 -4
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/examples/generate_example.py +17 -17
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/pyproject.toml +7 -1
- speedy_utils-2.0.2/pyrightconfig.json +8 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/bug_simple.py +1 -1
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/test.py +1 -1
- speedy_utils-2.0.2/scripts/test_locals.py +17 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/datasets_utils/viz_chat.py +87 -51
- speedy_utils-2.0.2/src/llm_utils/__init__.py +75 -0
- speedy_utils-2.0.2/src/llm_utils/lm/llm.py +863 -0
- speedy_utils-2.0.2/src/llm_utils/lm/llm_qwen3.py +705 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/llm_signature.py +1 -1
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/lm_base.py +4 -4
- speedy_utils-2.0.2/src/llm_utils/lm/utils.py +71 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/scripts/sp_chat.py +4 -1
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/__imports.py +9 -2
- speedy_utils-2.0.2/src/speedy_utils/__init__.py +227 -0
- {speedy_utils-2.0.1/src/speedy_utils/scripts → speedy_utils-2.0.2/src/speedy_utils/common}/__init__.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/utils_error.py +2 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/utils_io.py +5 -2
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/_multi_process.py +228 -16
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/common.py +1 -1
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/dataset_sharding.py +1 -1
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/thread.py +208 -55
- speedy_utils-2.0.2/src/speedy_utils/scripts/__init__.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/vision_utils/__init__.py +7 -7
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/vision_utils/io_utils.py +36 -57
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/vision_utils/plot.py +2 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/llm_utils/test_llm_call_contract.py +139 -9
- speedy_utils-2.0.2/tests/llm_utils/test_llm_mixins.py +835 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/llm_utils/test_llm_signature.py +2 -1
- speedy_utils-2.0.2/tests/llm_utils/test_openai_memoize_completion_cache.py +116 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/llm_utils/test_sp_chat.py +3 -1
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_process.py +37 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_process_update.py +14 -4
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_thread.py +6 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_tokenization.py +48 -4
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_viz_chat.py +101 -5
- speedy_utils-2.0.2/tools/uv_test.sh +9 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/uv.lock +35 -1
- speedy_utils-2.0.1/docs/MIGRATION_THINKING.md +0 -36
- speedy_utils-2.0.1/scripts/test_locals.py +0 -18
- speedy_utils-2.0.1/src/llm_utils/__init__.py +0 -26
- speedy_utils-2.0.1/src/llm_utils/lm/llm.py +0 -655
- speedy_utils-2.0.1/src/llm_utils/lm/llm_qwen3.py +0 -468
- speedy_utils-2.0.1/src/llm_utils/lm/utils.py +0 -38
- speedy_utils-2.0.1/src/speedy_utils/__init__.py +0 -209
- speedy_utils-2.0.1/tests/llm_utils/test_llm_mixins.py +0 -346
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.githooks/pre-commit +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.githooks/pre-push +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/prompts/improveParallelErrorHandling.prompt.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/caching-utilities/SKILL.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/caching-utilities/examples/caching_example.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/dataset-processing-multiprocessing/CONFIG_REFERENCE.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/dataset-processing-multiprocessing/SKILL.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/dataset-processing-multiprocessing/example_tokenize_pack.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/edit-llm-inference-style/SKILL.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/io-utilities/SKILL.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/io-utilities/examples/io_example.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/llm-integration/SKILL.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/llm-integration/examples/llm_example.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/multi-threading-processing/SKILL.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/ray-distributed-computing/SKILL.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/skill-creation/SKILL.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/vision-utilities/SKILL.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/skills/vision-utilities/examples/vision_example.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.github/workflows/publish.yml +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.gitignore +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/.pre-commit-config.yaml +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/AGENTS.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/bumpversion.sh +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/chainlit.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/debug_generate_response.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/debug_n_param.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/debug_n_structure.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/multi_thread.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_decode_api.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_endpoints.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_generate.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_generate_endpoint.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_qwen3_generate.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/debug/test_qwen3_generate.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/docs/TOKENIZATION_IMPLEMENTATION.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/examples/llm_public_api_example.ipynb +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/examples/model_specific_thinking_example.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/examples/tokenization_example.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/examples/vision_utils_example.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/experiments/exp1/dockerfile +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/experiments/exp1/run_in_docker.sh +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/experiments/exp1/test.png +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/notebooks/llm_utils/llm_as_a_judge.ipynb +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/notebooks/test.ipynb +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/notebooks/test_multi_thread.ipynb +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/ruff.toml +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/debug_import_time.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/deploy.sh +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/imports.sh +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/install_hooks.sh +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/test_error_handling.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/test_import_time_vision.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/scripts/test_ray_mp.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/setup.cfg +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/datasets_utils/convert_to_arrow.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/chat_format/__init__.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/chat_format/display.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/chat_format/transform.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/chat_format/utils.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/group_messages.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/__init__.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/base_prompt_builder.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/openai_memoize.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/llm_utils/lm/signature.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/clock.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/function_decorator.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/logger.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/notebook_utils.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/patcher.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/report_manager.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/utils_cache.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/utils_misc.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/common/utils_print.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/__init__.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/multi_worker/process.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/scripts/kill_mpython.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/scripts/mpython.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/scripts/openapi_client_codegen.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/speedy_utils/scripts/prefetch_large_model.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/src/vision_utils/README.md +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/import_all.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/import_time_report.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/llm_utils/test_openai_proxy_localhost.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/sample_objects.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_import_time.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_logger.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_logger_format.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_memoize_typing.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_mpython.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_multi_process.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_multi_thread.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_multithread_error_trace.py +0 -0
- {speedy_utils-2.0.1 → speedy_utils-2.0.2}/tests/test_prefetch_large_model.py +0 -0
|
File without changes
|
|
@@ -9,7 +9,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
|
|
9
9
|
uv sync
|
|
10
10
|
|
|
11
11
|
# Run tests
|
|
12
|
-
uv run pytest
|
|
12
|
+
uv run pytest -n 32
|
|
13
13
|
|
|
14
14
|
# Run a single test file
|
|
15
15
|
uv run pytest tests/test_thread.py
|
|
@@ -60,21 +60,41 @@ def some_function():
|
|
|
60
60
|
|
|
61
61
|
| Module | Import Time | Strategy |
|
|
62
62
|
|--------|-------------|----------|
|
|
63
|
-
| `torch` | ~5s | Lazy import
|
|
64
|
-
| `matplotlib` | ~
|
|
65
|
-
| `pandas` | ~1.
|
|
66
|
-
| `IPython` | ~1s | Lazy
|
|
63
|
+
| `torch` | ~5s | Lazy import inside functions |
|
|
64
|
+
| `matplotlib` | ~1.3s cumulative | Lazy via `__getattr__` in `__init__.py` |
|
|
65
|
+
| `pandas` | ~1.9s cumulative | Lazy via `__getattr__` in `__init__.py` |
|
|
66
|
+
| `IPython` | ~2.1s cumulative | Lazy via `__getattr__` in `__init__.py` |
|
|
67
67
|
|
|
68
|
-
###
|
|
68
|
+
### Rule: Never top-level import heavy modules in `__init__.py`
|
|
69
|
+
|
|
70
|
+
Only a short `__getattr__` block (keyed on module name) is allowed for heavy
|
|
71
|
+
modules. All internal `speedy_utils.*` imports must remain direct top-level
|
|
72
|
+
imports. External heavy modules (`pandas`, `matplotlib`, `IPython`, `torch`)
|
|
73
|
+
must **never** appear as top-level imports in any `__init__.py`.
|
|
69
74
|
|
|
70
75
|
```python
|
|
71
|
-
|
|
76
|
+
# BAD — in __init__.py
|
|
77
|
+
import pandas as pd # adds ~1.3 s
|
|
78
|
+
|
|
79
|
+
# GOOD — in __init__.py
|
|
80
|
+
# pd is declared in _HEAVY dict, resolved by __getattr__ on first access
|
|
72
81
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
82
|
+
# GOOD — inside a function anywhere in the package
|
|
83
|
+
def load_dataframe(path):
|
|
84
|
+
import pandas as pd # only pays cost when called
|
|
85
|
+
return pd.read_csv(path)
|
|
76
86
|
```
|
|
77
87
|
|
|
88
|
+
### Rule: Heavy lazy names must NOT be in `__all__`
|
|
89
|
+
|
|
90
|
+
`from speedy_utils import *` resolves every name in `__all__`, which triggers
|
|
91
|
+
`__getattr__` for each heavy name and defeats the lazy-load strategy. Only
|
|
92
|
+
include names that are directly importable at module load time in `__all__`.
|
|
93
|
+
Heavy names (`np`, `pd`, `matplotlib`, `plt`, `get_ipython`, `HTML`, `display`,
|
|
94
|
+
`BaseModel`) must be kept out of `__all__`; they remain accessible via explicit
|
|
95
|
+
attribute access (`speedy_utils.pd`) or explicit import
|
|
96
|
+
(`from speedy_utils import pd`).
|
|
97
|
+
|
|
78
98
|
## Architecture
|
|
79
99
|
|
|
80
100
|
### Package Structure
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: speedy-utils
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.2
|
|
4
4
|
Summary: Fast and easy-to-use package for data science
|
|
5
5
|
Project-URL: Homepage, https://github.com/anhvth/speedy
|
|
6
6
|
Project-URL: Repository, https://github.com/anhvth/speedy
|
|
@@ -24,6 +24,7 @@ Requires-Dist: cachetools
|
|
|
24
24
|
Requires-Dist: chainlit>=2.0
|
|
25
25
|
Requires-Dist: datasets
|
|
26
26
|
Requires-Dist: debugpy
|
|
27
|
+
Requires-Dist: dill
|
|
27
28
|
Requires-Dist: fastcore
|
|
28
29
|
Requires-Dist: fastprogress
|
|
29
30
|
Requires-Dist: freezegun
|
|
@@ -648,6 +649,36 @@ answer = message.content
|
|
|
648
649
|
reasoning = getattr(message, "reasoning_content", None)
|
|
649
650
|
```
|
|
650
651
|
|
|
652
|
+
For custom staged generation with explicit tags, `Qwen3LLM` also exposes
|
|
653
|
+
`complete_until()`:
|
|
654
|
+
|
|
655
|
+
```python
|
|
656
|
+
from llm_utils import Qwen3LLM
|
|
657
|
+
|
|
658
|
+
llm = Qwen3LLM(model="Qwen/Qwen3-0.6B")
|
|
659
|
+
|
|
660
|
+
memory_state = llm.complete_until(
|
|
661
|
+
[{"role": "user", "content": "Solve this in stages"}],
|
|
662
|
+
"<memory>",
|
|
663
|
+
stop="</memory>",
|
|
664
|
+
max_tokens=256,
|
|
665
|
+
)
|
|
666
|
+
|
|
667
|
+
think_state = llm.complete_until(
|
|
668
|
+
[{"role": "user", "content": "Solve this in stages"}],
|
|
669
|
+
memory_state.assistant_prompt_prefix + "\n<think_efficient>",
|
|
670
|
+
stop="</think_efficient>",
|
|
671
|
+
max_tokens=512,
|
|
672
|
+
)
|
|
673
|
+
|
|
674
|
+
final_state = llm.complete_until(
|
|
675
|
+
[{"role": "user", "content": "Solve this in stages"}],
|
|
676
|
+
think_state.assistant_prompt_prefix,
|
|
677
|
+
stop="<|im_end|>",
|
|
678
|
+
max_tokens=512,
|
|
679
|
+
)
|
|
680
|
+
```
|
|
681
|
+
|
|
651
682
|
#### Conversation History
|
|
652
683
|
|
|
653
684
|
Inspect previous conversations:
|
|
@@ -597,6 +597,36 @@ answer = message.content
|
|
|
597
597
|
reasoning = getattr(message, "reasoning_content", None)
|
|
598
598
|
```
|
|
599
599
|
|
|
600
|
+
For custom staged generation with explicit tags, `Qwen3LLM` also exposes
|
|
601
|
+
`complete_until()`:
|
|
602
|
+
|
|
603
|
+
```python
|
|
604
|
+
from llm_utils import Qwen3LLM
|
|
605
|
+
|
|
606
|
+
llm = Qwen3LLM(model="Qwen/Qwen3-0.6B")
|
|
607
|
+
|
|
608
|
+
memory_state = llm.complete_until(
|
|
609
|
+
[{"role": "user", "content": "Solve this in stages"}],
|
|
610
|
+
"<memory>",
|
|
611
|
+
stop="</memory>",
|
|
612
|
+
max_tokens=256,
|
|
613
|
+
)
|
|
614
|
+
|
|
615
|
+
think_state = llm.complete_until(
|
|
616
|
+
[{"role": "user", "content": "Solve this in stages"}],
|
|
617
|
+
memory_state.assistant_prompt_prefix + "\n<think_efficient>",
|
|
618
|
+
stop="</think_efficient>",
|
|
619
|
+
max_tokens=512,
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
final_state = llm.complete_until(
|
|
623
|
+
[{"role": "user", "content": "Solve this in stages"}],
|
|
624
|
+
think_state.assistant_prompt_prefix,
|
|
625
|
+
stop="<|im_end|>",
|
|
626
|
+
max_tokens=512,
|
|
627
|
+
)
|
|
628
|
+
```
|
|
629
|
+
|
|
600
630
|
#### Conversation History
|
|
601
631
|
|
|
602
632
|
Inspect previous conversations:
|
|
@@ -23,8 +23,8 @@ print()
|
|
|
23
23
|
print('✓ Test 2: Generate from text')
|
|
24
24
|
result = lm.generate('The answer is', max_tokens=10, temperature=0.5)
|
|
25
25
|
print(f' Input: "The answer is"')
|
|
26
|
-
print(f' Output: {result
|
|
27
|
-
assert
|
|
26
|
+
print(f' Output: {result.text[:50]}')
|
|
27
|
+
assert result.text is not None
|
|
28
28
|
print()
|
|
29
29
|
|
|
30
30
|
# Test 3: Generate from token IDs
|
|
@@ -32,10 +32,10 @@ print('✓ Test 3: Generate from token IDs')
|
|
|
32
32
|
input_ids = lm.encode('Python is')
|
|
33
33
|
result = lm.generate(input_ids, max_tokens=15, return_token_ids=True)
|
|
34
34
|
print(f' Input IDs: {input_ids}')
|
|
35
|
-
print(f' Output: {result
|
|
36
|
-
print(f' Output IDs: {result
|
|
37
|
-
assert
|
|
38
|
-
assert
|
|
35
|
+
print(f' Output: {result.text[:50]}')
|
|
36
|
+
print(f' Output IDs: {getattr(result, "token_ids", "N/A")}')
|
|
37
|
+
assert result.text is not None
|
|
38
|
+
assert hasattr(result, "token_ids") and result.token_ids is not None
|
|
39
39
|
print()
|
|
40
40
|
|
|
41
41
|
# Test 4: Multiple generations
|
|
@@ -43,7 +43,7 @@ print('✓ Test 4: Multiple generations (n=3)')
|
|
|
43
43
|
results = lm.generate('AI is', max_tokens=10, n=3, temperature=0.8)
|
|
44
44
|
print(f' Input: "AI is"')
|
|
45
45
|
for i, r in enumerate(results, 1):
|
|
46
|
-
print(f' {i}. {r
|
|
46
|
+
print(f' {i}. {r.text[:40]}')
|
|
47
47
|
# Note: Server may return fewer choices than requested
|
|
48
48
|
assert isinstance(results, list)
|
|
49
49
|
assert len(results) >= 1
|
|
@@ -53,8 +53,8 @@ print()
|
|
|
53
53
|
print('✓ Test 5: Temperature control')
|
|
54
54
|
low_temp = lm.generate('1 + 1 =', max_tokens=5, temperature=0.1, seed=42)
|
|
55
55
|
high_temp = lm.generate('1 + 1 =', max_tokens=5, temperature=1.5, seed=123)
|
|
56
|
-
print(f' Low temp (0.1): {low_temp
|
|
57
|
-
print(f' High temp (1.5): {high_temp
|
|
56
|
+
print(f' Low temp (0.1): {low_temp.text[:30]}')
|
|
57
|
+
print(f' High temp (1.5): {high_temp.text[:30]}')
|
|
58
58
|
print()
|
|
59
59
|
|
|
60
60
|
# Test 6: Stop sequences
|
|
@@ -66,16 +66,16 @@ result = lm.generate(
|
|
|
66
66
|
temperature=0.7,
|
|
67
67
|
)
|
|
68
68
|
print(f' Input: "Count: 1, 2, 3,"')
|
|
69
|
-
print(f' Output (stop at ", 6"): {result
|
|
69
|
+
print(f' Output (stop at ", 6"): {result.text[:50]}')
|
|
70
70
|
print()
|
|
71
71
|
|
|
72
72
|
# Test 7: Seed reproducibility
|
|
73
73
|
print('✓ Test 7: Seed reproducibility')
|
|
74
74
|
r1 = lm.generate('Test', max_tokens=10, seed=999, temperature=0.8)
|
|
75
75
|
r2 = lm.generate('Test', max_tokens=10, seed=999, temperature=0.8)
|
|
76
|
-
print(f' Run 1: {r1
|
|
77
|
-
print(f' Run 2: {r2
|
|
78
|
-
print(f' Match: {r1
|
|
76
|
+
print(f' Run 1: {r1.text[:40]}')
|
|
77
|
+
print(f' Run 2: {r2.text[:40]}')
|
|
78
|
+
print(f' Match: {r1.text == r2.text}')
|
|
79
79
|
print()
|
|
80
80
|
|
|
81
81
|
print('=' * 60)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Summary
|
|
4
4
|
|
|
5
|
-
The `LLM.generate()` method provides a HuggingFace Transformers-style interface for
|
|
5
|
+
The `LLM.generate()` method provides a HuggingFace Transformers-style interface for prompt continuation via the completions API. For chat assistant-turn generation, use `LLM.chat_completion()`.
|
|
6
6
|
|
|
7
7
|
## Basic Signature
|
|
8
8
|
|
|
@@ -16,9 +16,17 @@ lm.generate(
|
|
|
16
16
|
) -> dict | list[dict]
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
+
## Which method to use
|
|
20
|
+
|
|
21
|
+
- `generate()`: Continue a raw prompt through the completions API.
|
|
22
|
+
- `chat_completion()`: Generate the next assistant turn from chat messages.
|
|
23
|
+
- `pydantic_parse()`: Parse structured output from the chat completions API.
|
|
24
|
+
|
|
25
|
+
`enable_thinking` is primarily a chat-path control. `generate()` forwards it only for backends that support the same flag on the completions endpoint.
|
|
26
|
+
|
|
19
27
|
## Common Use Cases
|
|
20
28
|
|
|
21
|
-
### 1. Simple
|
|
29
|
+
### 1. Simple Prompt Continuation
|
|
22
30
|
```python
|
|
23
31
|
result = lm.generate('Hello world', max_tokens=50)
|
|
24
32
|
print(result['text'])
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Qwen3LLM Thinking Guide
|
|
2
|
+
|
|
3
|
+
`Qwen3LLM` is the only model-specific thinking helper kept in this repository.
|
|
4
|
+
It supports prefix continuation for Qwen3-style reasoning traces.
|
|
5
|
+
It also includes a raw prefix-conditioned helper for custom staged tag flows.
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
from llm_utils import Qwen3LLM
|
|
11
|
+
|
|
12
|
+
llm = Qwen3LLM(client=8000)
|
|
13
|
+
|
|
14
|
+
message = llm.chat_completion(
|
|
15
|
+
[{"role": "user", "content": "Solve x^2 + 2x + 1 = 0"}],
|
|
16
|
+
thinking_max_tokens=32,
|
|
17
|
+
content_max_tokens=128,
|
|
18
|
+
)
|
|
19
|
+
print(message)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Custom Prefix Flows
|
|
23
|
+
|
|
24
|
+
Use `complete_until()` when you want to generate a custom tagged block and stop
|
|
25
|
+
at a specific closing tag instead of the built-in `<think>...</think>` flow.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from llm_utils import Qwen3LLM
|
|
29
|
+
|
|
30
|
+
llm = Qwen3LLM(client=8000)
|
|
31
|
+
|
|
32
|
+
memory_state = llm.complete_until(
|
|
33
|
+
[{"role": "user", "content": "Plan the answer in stages"}],
|
|
34
|
+
"<memory>",
|
|
35
|
+
stop="</memory>",
|
|
36
|
+
max_tokens=128,
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
think_state = llm.complete_until(
|
|
40
|
+
[{"role": "user", "content": "Plan the answer in stages"}],
|
|
41
|
+
memory_state.assistant_prompt_prefix + "\n<think_efficient>",
|
|
42
|
+
stop="</think_efficient>",
|
|
43
|
+
max_tokens=256,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
final_state = llm.complete_until(
|
|
47
|
+
[{"role": "user", "content": "Plan the answer in stages"}],
|
|
48
|
+
think_state.assistant_prompt_prefix,
|
|
49
|
+
stop="<|im_end|>",
|
|
50
|
+
max_tokens=256,
|
|
51
|
+
)
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Behavior
|
|
55
|
+
|
|
56
|
+
- `chat_completion()` continues from a partial assistant prefix.
|
|
57
|
+
- The default prefix starts a `<think>` block.
|
|
58
|
+
- `thinking_max_tokens` caps the reasoning phase.
|
|
59
|
+
- `content_max_tokens` caps the final answer phase.
|
|
60
|
+
- `complete_until()` continues from a raw assistant prefix and stops on custom
|
|
61
|
+
tag boundaries.
|
|
62
|
+
- The returned `assistant_prompt_prefix` includes the generated text and the
|
|
63
|
+
matched stop token when generation ends on a stop sequence.
|
|
64
|
+
|
|
65
|
+
## Notes
|
|
66
|
+
|
|
67
|
+
- Use `n=1`; prefix continuation is single-path only.
|
|
68
|
+
- The returned object is an OpenAI `ChatCompletionMessage`.
|
|
69
|
+
- The intermediate prefix state is internal; the returned value is an OpenAI
|
|
70
|
+
`ChatCompletionMessage`.
|
|
71
|
+
- The returned message includes `call_count`, which is `1` when the model
|
|
72
|
+
finishes in one staged call and `2` when it needs a reasoning call plus a
|
|
73
|
+
follow-up content call.
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# Tokenization and Generation Support in LLM
|
|
2
2
|
|
|
3
|
-
The `LLM` class
|
|
3
|
+
The `LLM` class includes built-in tokenization plus two distinct generation styles:
|
|
4
|
+
- `generate()` for prompt continuation via the completions API
|
|
5
|
+
- `chat_completion()` for assistant-turn generation via the chat completions API
|
|
4
6
|
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
9
|
- **encode()**: Convert text to token IDs
|
|
8
10
|
- **decode()**: Convert token IDs back to text
|
|
9
|
-
- **generate()**: HuggingFace-style
|
|
11
|
+
- **generate()**: HuggingFace-style prompt continuation with token-level control
|
|
12
|
+
- **chat_completion()**: Chat-based assistant-turn generation from messages
|
|
13
|
+
- **pydantic_parse()**: Structured chat output parsing
|
|
10
14
|
- Support for special tokens (BOS, EOS, etc.)
|
|
11
15
|
- Optional token string output for debugging
|
|
12
16
|
|
|
@@ -94,6 +98,14 @@ def generate(
|
|
|
94
98
|
- `dict`: Generation result with 'text', 'token_ids', 'finish_reason' (if n=1)
|
|
95
99
|
- `list[dict]`: List of generation results (if n>1)
|
|
96
100
|
|
|
101
|
+
### Which method to use
|
|
102
|
+
|
|
103
|
+
- Use `generate()` when you already have a raw prompt string and want the completions API to continue it with more tokens.
|
|
104
|
+
- Use `chat_completion()` when you want the chat completions API to produce the next assistant turn from messages.
|
|
105
|
+
- Use `pydantic_parse()` when you want structured output from the chat completions API.
|
|
106
|
+
|
|
107
|
+
`enable_thinking` is mainly meaningful on the chat-based paths. `generate()` passes it through for backends that honor `extra_body.chat_template_kwargs.enable_thinking` on the completions endpoint, but that support is backend-specific.
|
|
108
|
+
|
|
97
109
|
## Usage Examples
|
|
98
110
|
|
|
99
111
|
### Basic Encoding/Decoding
|
|
@@ -169,9 +181,9 @@ result = lm.decode(combined)
|
|
|
169
181
|
print(result) # 'Helloworld'
|
|
170
182
|
```
|
|
171
183
|
|
|
172
|
-
##
|
|
184
|
+
## Prompt Continuation with `generate()`
|
|
173
185
|
|
|
174
|
-
The `generate()` method provides a low-level interface similar to HuggingFace Transformers
|
|
186
|
+
The `generate()` method provides a low-level prompt-continuation interface similar to HuggingFace Transformers. It uses the completions API rather than the chat completions API.
|
|
175
187
|
|
|
176
188
|
### Basic Text Generation
|
|
177
189
|
|
|
@@ -184,6 +196,21 @@ result = lm.generate(
|
|
|
184
196
|
print(result['text']) # ' Paris.'
|
|
185
197
|
```
|
|
186
198
|
|
|
199
|
+
## Chat Assistant-Turn Generation
|
|
200
|
+
|
|
201
|
+
Use `chat_completion()` when you want the next assistant message from a list of chat messages:
|
|
202
|
+
|
|
203
|
+
```python
|
|
204
|
+
message = lm.chat_completion(
|
|
205
|
+
[
|
|
206
|
+
{"role": "system", "content": "Be concise."},
|
|
207
|
+
{"role": "user", "content": "What is the capital of France?"},
|
|
208
|
+
],
|
|
209
|
+
temperature=0.2,
|
|
210
|
+
)
|
|
211
|
+
print(message.content) # 'Paris.'
|
|
212
|
+
```
|
|
213
|
+
|
|
187
214
|
### Working with Token IDs
|
|
188
215
|
|
|
189
216
|
```python
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
"""Example: Using the
|
|
1
|
+
"""Example: Using the prompt-continuation `generate()` method.
|
|
2
2
|
|
|
3
|
-
This demonstrates the low-level generation interface
|
|
4
|
-
|
|
3
|
+
This demonstrates the low-level completions-style generation interface
|
|
4
|
+
for continuing a raw prompt, similar to HuggingFace Transformers.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
from llm_utils.lm import LLM
|
|
@@ -10,7 +10,7 @@ from llm_utils.lm import LLM
|
|
|
10
10
|
lm = LLM(client=8000)
|
|
11
11
|
|
|
12
12
|
print('=' * 70)
|
|
13
|
-
print('
|
|
13
|
+
print('Prompt Continuation Examples')
|
|
14
14
|
print('=' * 70)
|
|
15
15
|
print()
|
|
16
16
|
|
|
@@ -23,7 +23,7 @@ result = lm.generate(
|
|
|
23
23
|
temperature=0.8,
|
|
24
24
|
)
|
|
25
25
|
print(f"Prompt: 'Write a haiku about coding:'")
|
|
26
|
-
print(f"Generated:\n{result
|
|
26
|
+
print(f"Generated:\n{result.text}")
|
|
27
27
|
print()
|
|
28
28
|
|
|
29
29
|
# Example 2: Working with token IDs
|
|
@@ -40,8 +40,8 @@ result = lm.generate(
|
|
|
40
40
|
temperature=0.7,
|
|
41
41
|
return_token_ids=True, # Get token IDs back
|
|
42
42
|
)
|
|
43
|
-
print(f'Output IDs: {result
|
|
44
|
-
print(f'Output text: {result
|
|
43
|
+
print(f'Output IDs: {result.token_ids}')
|
|
44
|
+
print(f'Output text: {result.text}')
|
|
45
45
|
print()
|
|
46
46
|
|
|
47
47
|
# Example 3: Temperature sampling
|
|
@@ -52,12 +52,12 @@ prompt = 'The best way to learn programming is'
|
|
|
52
52
|
# Low temperature (more deterministic)
|
|
53
53
|
result_low = lm.generate(prompt, max_tokens=40, temperature=0.1)
|
|
54
54
|
print(f'Temperature 0.1 (deterministic):')
|
|
55
|
-
print(f' {result_low
|
|
55
|
+
print(f' {result_low.text[:100]}...')
|
|
56
56
|
|
|
57
57
|
# High temperature (more creative)
|
|
58
58
|
result_high = lm.generate(prompt, max_tokens=40, temperature=1.5)
|
|
59
59
|
print(f'Temperature 1.5 (creative):')
|
|
60
|
-
print(f' {result_high
|
|
60
|
+
print(f' {result_high.text[:100]}...')
|
|
61
61
|
print()
|
|
62
62
|
|
|
63
63
|
# Example 4: Top-k and Top-p sampling
|
|
@@ -71,7 +71,7 @@ result = lm.generate(
|
|
|
71
71
|
top_p=0.95, # Nucleus sampling
|
|
72
72
|
repetition_penalty=1.2, # Reduce repetition
|
|
73
73
|
)
|
|
74
|
-
print(f'Generated: {result
|
|
74
|
+
print(f'Generated: {result.text}')
|
|
75
75
|
print()
|
|
76
76
|
|
|
77
77
|
# Example 5: Multiple generations (like num_return_sequences)
|
|
@@ -84,7 +84,7 @@ results = lm.generate(
|
|
|
84
84
|
n=4, # Generate 4 different completions
|
|
85
85
|
)
|
|
86
86
|
for i, result in enumerate(results, 1):
|
|
87
|
-
print(f'{i}. {result
|
|
87
|
+
print(f'{i}. {result.text}')
|
|
88
88
|
print()
|
|
89
89
|
|
|
90
90
|
# Example 6: Controlled generation with stop sequences
|
|
@@ -96,7 +96,7 @@ result = lm.generate(
|
|
|
96
96
|
temperature=0.7,
|
|
97
97
|
stop=['\n\n', 'Instructions:'], # Stop at these sequences
|
|
98
98
|
)
|
|
99
|
-
print(f'Generated:\n{result
|
|
99
|
+
print(f'Generated:\n{result.text}')
|
|
100
100
|
print()
|
|
101
101
|
|
|
102
102
|
# Example 7: Reproducible generation with seed
|
|
@@ -109,10 +109,10 @@ result1 = lm.generate(prompt, max_tokens=10, temperature=0.8, seed=42)
|
|
|
109
109
|
result2 = lm.generate(prompt, max_tokens=10, temperature=0.8, seed=42)
|
|
110
110
|
result3 = lm.generate(prompt, max_tokens=10, temperature=0.8, seed=123)
|
|
111
111
|
|
|
112
|
-
print(f'Seed 42 (run 1): {result1
|
|
113
|
-
print(f'Seed 42 (run 2): {result2
|
|
114
|
-
print(f'Seed 123: {result3
|
|
115
|
-
print(f'Results match: {result1
|
|
112
|
+
print(f'Seed 42 (run 1): {result1.text}')
|
|
113
|
+
print(f'Seed 42 (run 2): {result2.text}')
|
|
114
|
+
print(f'Seed 123: {result3.text}')
|
|
115
|
+
print(f'Results match: {result1.text == result2.text}')
|
|
116
116
|
print()
|
|
117
117
|
|
|
118
118
|
# Example 8: Token count estimation
|
|
@@ -132,7 +132,7 @@ result = lm.generate(
|
|
|
132
132
|
max_tokens=min(100, available_tokens),
|
|
133
133
|
temperature=0.7,
|
|
134
134
|
)
|
|
135
|
-
generated_tokens = lm.encode(result
|
|
135
|
+
generated_tokens = lm.encode(result.text)
|
|
136
136
|
print(f'Generated tokens: {len(generated_tokens)}')
|
|
137
137
|
print(f'Total tokens used: {len(prompt_tokens) + len(generated_tokens)}')
|
|
138
138
|
print()
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "speedy-utils"
|
|
3
|
-
version = "2.0.
|
|
3
|
+
version = "2.0.2"
|
|
4
4
|
description = "Fast and easy-to-use package for data science"
|
|
5
5
|
authors = [{ name = "AnhVTH", email = "anhvth.226@gmail.com" }]
|
|
6
6
|
readme = "README.md"
|
|
@@ -23,6 +23,7 @@ dependencies = [
|
|
|
23
23
|
"pydantic",
|
|
24
24
|
"tqdm",
|
|
25
25
|
"cachetools",
|
|
26
|
+
"dill",
|
|
26
27
|
"ruff",
|
|
27
28
|
"bump2version",
|
|
28
29
|
"json-repair",
|
|
@@ -143,3 +144,8 @@ python_classes = ["Test*"]
|
|
|
143
144
|
python_functions = ["test_*"]
|
|
144
145
|
# Exclude debug and example directories from test discovery
|
|
145
146
|
norecursedirs = ["debug", "examples", "experiments", "notebooks", "scripts", ".git", "__pycache__"]
|
|
147
|
+
|
|
148
|
+
[dependency-groups]
|
|
149
|
+
dev = [
|
|
150
|
+
"pytest-xdist>=3.8.0",
|
|
151
|
+
]
|
|
@@ -10,7 +10,7 @@ def demo_1_simple_error():
|
|
|
10
10
|
def process_item(x):
|
|
11
11
|
# Intentional error - calling a list
|
|
12
12
|
my_list = [1, 2, 3]
|
|
13
|
-
return my_list(x)
|
|
13
|
+
return my_list(x) # type: ignore[operator]
|
|
14
14
|
|
|
15
15
|
# try:
|
|
16
16
|
multi_thread(process_item, range(300), workers=100, progress=True)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from speedy_utils import multi_process
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def process_data(item):
|
|
5
|
+
# Mix of user variables and imported modules
|
|
6
|
+
data = {"value": item}
|
|
7
|
+
multiplier = 2
|
|
8
|
+
|
|
9
|
+
# This will cause an error
|
|
10
|
+
denominator = 0
|
|
11
|
+
final = data["value"] * multiplier / denominator
|
|
12
|
+
return final
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if __name__ == "__main__":
|
|
16
|
+
inputs = range(5)
|
|
17
|
+
multi_process(process_data, inputs, backend="mp")
|