diff --git a/.github/workflows/test_models.yml b/.github/workflows/test_models.yml
index bf22f14a11..7210598574 100644
--- a/.github/workflows/test_models.yml
+++ b/.github/workflows/test_models.yml
@@ -46,6 +46,23 @@ jobs:
CHECK_OOB: 0
GMMU: 0
run: tools/op.sh test -v openpilot/sunnypilot/modeld_v2/tests/test_convert_upstream_pkl.py
+ - name: Resolve commaai's tinygrad pin, which their precompiled chestnut model is built and converted with
+ id: upstream-tg
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ read -r sha url < <(gh api repos/commaai/openpilot/contents/tinygrad_repo --jq '.sha + " " + .submodule_git_url')
+ echo "commaai/openpilot master pins $url@$sha, ours is $(git rev-parse HEAD:tinygrad_repo)"
+ dir="$RUNNER_TEMP/upstream_tinygrad"
+ git init -q "$dir" && git -C "$dir" fetch -q --depth 1 "$url" "$sha" && git -C "$dir" checkout -q FETCH_HEAD
+ echo "path=$dir" >> "$GITHUB_OUTPUT"
+ - name: Convert a pickled USB+AMD jit under commaai's tinygrad, as the chestnut model build does
+ env:
+ DEV: MOCKUSB+AMD
+ CHECK_OOB: 0
+ GMMU: 0
+ PYTHONPATH: ${{ steps.upstream-tg.outputs.path }}:${{ github.workspace }}
+ run: tools/op.sh test -v openpilot/sunnypilot/modeld_v2/tests/test_convert_upstream_pkl.py
- name: Where tinygrad moved, for the helper or failure point named above
if: failure() && github.event_name == 'pull_request'
run: |
diff --git a/docs/CARS.md b/docs/CARS.md
index d3a4371735..1c1a4d8a12 100644
--- a/docs/CARS.md
+++ b/docs/CARS.md
@@ -4,7 +4,7 @@
A supported vehicle is one that just works when you install a comma device. All supported cars provide a better experience than any stock system. Supported vehicles reference the US market unless otherwise specified.
-# 351 Supported Cars
+# 356 Supported Cars
|Make|Model|Supported Package|ACC|No ACC accel below|No ALC below|Steering Torque|Resume from stop|
Hardware Needed
|Video|Setup Video|
|---|---|---|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
@@ -139,6 +139,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Ioniq Plug-in Hybrid 2020-22|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Kona 2020|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|6 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Kona 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai O connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
+|Hyundai|Kona (without HDA II) 2024-25|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Kona Electric 2018-21|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Kona Electric 2022-23|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai O connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Kona Electric (with HDA II, Korea only) 2023|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai R connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |
||
@@ -153,18 +154,21 @@ A supported vehicle is one that just works when you install a comma device. All
|Hyundai|Sonata 2018-19|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Sonata 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |
||
|Hyundai|Sonata Hybrid 2020-23|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
+|Hyundai|Sonata Hybrid (without HDA II) 2024-26|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Staria 2023|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Tucson 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|19 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Tucson 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Tucson 2023-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Tucson Diesel 2019|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai L connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Tucson Hybrid 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
+|Hyundai|Tucson Hybrid (without HDA II) 2025-26|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Tucson Plug-in Hybrid 2024|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai N connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Hyundai|Veloster 2019-20|Smart Cruise Control (SCC)|Stock|5 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Jeep|Grand Cherokee 2016-18|Adaptive Cruise Control (ACC)|Stock|0 mph|9 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |
||
|Jeep|Grand Cherokee 2019-21|Adaptive Cruise Control (ACC)|Stock|0 mph|39 mph|[](##)|[](##)|Parts
- 1 FCA connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Kia|Carnival 2022-24|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai A connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Kia|Carnival (China only) 2023|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai K connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
+|Kia|Carnival Hybrid (with HDA II) 2025-26|Highway Driving Assist II|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai Q connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Kia|Ceed 2019-21|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai E connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Kia|Ceed Plug-in Hybrid 2021|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Kia|EV6 (Southeast Asia only) 2022-24|All|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai P connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
@@ -190,6 +194,7 @@ A supported vehicle is one that just works when you install a comma device. All
|Kia|Niro Plug-in Hybrid 2020|Smart Cruise Control (SCC)|Stock|0 mph|32 mph|[](##)|[](##)|Parts
- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Kia|Niro Plug-in Hybrid 2021|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai D connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Kia|Niro Plug-in Hybrid 2022|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai F connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
+|Kia|Niro Plus EV 2024|Smart Cruise Control (SCC)|openpilot available[1](#footnotes)|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Kia|Optima 2017|Advanced Smart Cruise Control|Stock|0 mph|32 mph|[](##)|[](##)|Parts
- 1 Hyundai B connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Kia|Optima 2019-20|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai G connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
|Kia|Optima Hybrid 2019|Smart Cruise Control (SCC)|Stock|0 mph|0 mph|[](##)|[](##)|Parts
- 1 Hyundai H connector
- 1 OBD-C cable (2 ft)
- 1 comma four
- 1 comma power v3
- 1 harness box
- 1 mount
Buy Here |||
diff --git a/opendbc_repo b/opendbc_repo
index 2c92f2b61b..5d982111cf 160000
--- a/opendbc_repo
+++ b/opendbc_repo
@@ -1 +1 @@
-Subproject commit 2c92f2b61bc5ed44ef1524dbd85d86fbcd1c739f
+Subproject commit 5d982111cf3cccdb294cddbf3484899ffb824469
diff --git a/openpilot/sunnypilot/modeld_v2/convert_upstream_pkl.py b/openpilot/sunnypilot/modeld_v2/convert_upstream_pkl.py
index 9ae62f0f5d..1f61471639 100755
--- a/openpilot/sunnypilot/modeld_v2/convert_upstream_pkl.py
+++ b/openpilot/sunnypilot/modeld_v2/convert_upstream_pkl.py
@@ -66,47 +66,75 @@ def _patched(buf, topo) -> tuple[bytearray, dict]:
return blob, rows
-def _evaluate(u, env):
+def _packed_args(program) -> dict[int, int]:
+ from tinygrad.device import TinyELF
+ params = getattr(program.arg, "params", None) or ()
+ return {off: p.arg.slot for (off, _), p in zip(TinyELF.iter_sig(tuple((None, p.addrspace, p.dtype, ()) for p in params)), params, strict=True)}
+
+
+def _outer_slot(u, packed: dict[int, int]) -> int | None:
+ from tinygrad.uop.ops import Ops
+ if u.op is Ops.PARAM:
+ return None if packed else u.arg.slot
+ if u.op.name == "REFPARAM":
+ return u.arg.slot
+ if packed and u.op is Ops.LOAD and u.src[0].op is Ops.INDEX and _root(u.src[0].src[0]).op is Ops.PARAM:
+ index = _evaluate(u.src[0].src[1], {}, packed)
+ return None if index is None else packed.get(index * u.dtype.itemsize)
+ return None
+
+
+def _evaluate(u, env, packed: dict[int, int]):
from tinygrad.uop.ops import GroupOp, Ops, exec_alu
if u.op in (Ops.AFTER, Ops.CAST, Ops.BITCAST):
- return _evaluate(u.src[0], env)
+ return _evaluate(u.src[0], env, packed)
if u.op is Ops.CONST:
return u.arg
- if u.op in (Ops.PARAM, Ops.RANGE):
- return env.get(u.arg.slot if u.op is Ops.PARAM else u)
+ if u.op is Ops.RANGE:
+ return env.get(u)
+ if (slot := _outer_slot(u, packed)) is not None:
+ return env.get(slot)
if u.op is Ops.LOAD:
- table, index = _root(u.src[0].src[0]), _evaluate(u.src[0].src[1], env)
+ table, index = _root(u.src[0].src[0]), _evaluate(u.src[0].src[1], env, packed)
return struct.unpack_from(u.dtype.fmt, table.arg, index * u.dtype.itemsize)[0] if table.op is Ops.BINARY and index is not None else None
- if u.op in GroupOp.ALU and None not in (values := [_evaluate(x, env) for x in u.src]):
+ if u.op in GroupOp.ALU and None not in (values := [_evaluate(x, env, packed) for x in u.src]):
return exec_alu(u.op, u.dtype, values, *([False] if "truncate_output" in inspect.signature(exec_alu).parameters else []))
return None
def _input_pokes(submit, kernargs) -> dict[int, tuple[int, int]]:
from tinygrad.uop.ops import Ops
- outer = dict(enumerate(submit.src[1:]))
+ outer, packed = dict(enumerate(submit.src[1:])), _packed_args(submit.src[0])
kernargs_params = {i for i, a in outer.items() if a.op is Ops.GETADDR and _root(a.src[0]) is kernargs}
inputs = {i: _root(a.src[0]).arg.slot for i, a in outer.items() if a.op is Ops.GETADDR and _root(a.src[0]).op is Ops.PARAM}
pokes = {}
for call in (u for u in submit.src[0].src[0].toposort() if u.op is Ops.CALL and len(u.src) > 3):
_, address, value, *_ = call.src[1:]
- used = list({x.arg.slot for x in value.toposort() if x.op is Ops.PARAM} & inputs.keys())
- if len(used) != 1 or not kernargs_params & {x.arg.slot for x in address.toposort() if x.op is Ops.PARAM}:
+ used = list({slot for x in value.toposort() if (slot := _outer_slot(x, packed)) is not None} & inputs.keys())
+ if len(used) != 1 or not kernargs_params & {_outer_slot(x, packed) for x in address.toposort()}:
continue
ranges = list(dict.fromkeys(x for x in (*address.toposort(), *value.toposort()) if x.op is Ops.RANGE))
for trip in itertools.product(*[range(int(r.vmax) + 1) for r in ranges]):
env = dict.fromkeys(outer, 0) | dict(zip(ranges, trip, strict=True))
- if (offset := _evaluate(address, env)) % 8 == 0:
- pokes[offset] = (inputs[used[0]], _evaluate(value, env))
+ if (offset := _evaluate(address, env, packed)) % 8 == 0:
+ pokes[offset] = (inputs[used[0]], _evaluate(value, env, packed))
return pokes
-def _elf(image: bytes, rodata: int) -> bytes:
- names, offset, data, headers = b"\0.text\0.rodata\0.shstrtab\0", 64, b"", []
- for name, kind, address, content in [(0, 0, 0, b""), (1, 1, ALIGNMENT, image[:rodata]), (7, 1, ALIGNMENT + rodata, image[rodata:]), (15, 3, 0, names)]:
- headers.append(struct.pack(" bytes:
+ spans = [(".text", 0, rodata), (".rodata", rodata, len(image))] if entry < rodata else \
+ [(".data", 0, rodata), (".rodata", rodata, entry), (".text", entry, len(image))]
+ sections, names = [(0, 0, 0, 0, b"")], b"\0"
+ for name, start, end in (span for span in spans if span[1] < span[2]):
+ sections.append((len(names), 1, 6 if name == ".text" else 2, ALIGNMENT + start, image[start:end]))
+ names += name.encode() + b"\0"
+ sections.append((len(names), 3, 0, 0, names + b".shstrtab\0"))
+ offset, data, headers = 64, b"", []
+ for name, kind, flags, address, content in sections:
+ headers.append(struct.pack(" tuple[dict, memoryview]:
@@ -155,7 +183,7 @@ def convert(jit, kwargs: dict, frame: dict, target) -> tuple[dict, memoryview]:
if image_alloc not in programs:
image = bytes(_patched(image_alloc, topo)[0])
rodata = next(r for r in range(0, len(image) - 24, 64) if r + struct.unpack_from(" str | None:
+ match = _DECIMAL_VERSION_REGEX.match(word.strip('()[]{}.,;:'))
+ if match:
+ return match.group(1).upper() + match.group(2)
+ stripped = re.sub(r'[^a-zA-Z0-9]', '', word)
+ return stripped.upper() if _VERSION_REGEX.match(stripped) else None
+
+
+def _truncate(short_name: str) -> str:
+ return short_name[:8].rstrip('.')
+
+
def create_short_name(full_name: str) -> str:
- # Remove parentheses and extract alphanumeric words
+ # Remove parentheses and extract alphanumeric words, keeping decimals for versioning
clean_name = re.sub(r'\([^)]*\)', '', full_name)
- words = [re.sub(r'[^a-zA-Z0-9]', '', word) for word in clean_name.split() if re.sub(r'[^a-zA-Z0-9]', '', word)]
+ words = [re.sub(r'[^a-zA-Z0-9.]', '', word) for word in clean_name.split()]
+ words = [word for word in words if word]
if len(words) == 1:
- return words[0][:8].upper()
+ return _truncate((_version(words[0]) or words[0]).upper())
- # Handle special case: Name + Version (e.g., "Word A1" -> "WordA1")
- if len(words) == 2 and re.match(r'^[A-Za-z]\d+$', words[1]):
- return (words[0] + words[1])[:8].upper()
+ # Handle special case: Name + Version (e.g., "Word A1" -> "WordA1", "Model v1.1" -> "ModelV1.1")
+ if len(words) == 2 and re.match(r'^[A-Za-z]?\d+$', words[1]):
+ return _truncate((words[0] + (_version(words[1]) or words[1])).upper())
result = ""
for word in words:
# Version or number patterns
- if (re.match(r'^\d+[a-zA-Z]+$', word) or
- re.match(r'^\d+[vVbB]\d+$', word) or
- re.match(r'^[vVbB]\d+$', word) or
- re.match(r'^\d{4}$', word)):
- result += word.upper()
+ version = _version(word)
+ if version:
+ result += version
# All uppercase abbreviations (2-3 letters)
elif re.match(r'^[A-Z]{2,3}$', word):
result += word
@@ -45,7 +60,7 @@ def create_short_name(full_name: str) -> str:
result += word
else:
result += word[0].upper()
- return result[:8]
+ return _truncate(result)
def create_pkl_name(full_name: str) -> str: